1. 进程和线程

1.1 定义

进程是处于运行状态的程序和相关资源的总称,是资源分配的最小单位。

线程是进程的内部的一个执行序列,是CPU调度的最小单位。有一段可执行程序代码。

有一段进程专用的系统堆栈空间和系统空间堆栈。

有进程描述符,用于描述进程的相关信息。

有独立的存储空间,也就是专有的用户空间,相应的又会有用户空间堆栈。

Linux系统对于线程实现非常特殊,他并不区分线程和进程,线程只是一种特殊的进程罢了。从上面四点要素来看,拥有前三点而缺第四点要素的就是线程,如果完全没有第四点的用户空间,那就是系统线程,如果是共享用户空间,那就是用户线程。

1.2 主要区别

进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

总结:linux中,进程和线程唯一区别是有没有独立的地址空间。

2. 进程描述符及任务结构

32位机器上,大约有1.7KB,进程描述符完整描述一个正在执行的进程的所有信息。

任务队列(双向循环链表)

struct task_struct {

volatile long state; // -1为不可运行, 0为可运行, >0为已中断

int lock_depth; // 锁的深度

unsigned int policy; // 调度策略:一般有FIFO,RR,CFS

pid_t pid; // 进程标识符,用来代表一个进程

struct task_struct *parent; // 父进程

struct list_head children; // 子进程

struct list_head sibling; // 兄弟进程

}

2.1 分配进程描述符

2.1.1 slab分配器

linux采用slab分配器分配task_struct结构

目的:对象复用和缓存着色。

slab分配器动态生成task_struct,只需在栈底(相对于向下增长的栈)或栈顶(相对于向上增长的栈)创建一个新结构struct thread_info。

2.1.2 进程描述符存放

PID最大值默认为32768(short int 短整形的最大值)可通过修改/proc/sys/kernel/pid_max提高上限。

current宏查找当前正在运行进程的进程描述符。

x86系统中,current把栈指针后13个有效位屏蔽掉,用来计算出thread_info的偏移。

current_thread_info函数movl $-8192,%eax

andl %esp,%eax

2.1.3 进程状态TASK_RUNNING:1. 正在执行 2. 在运行队列中等待执行

TASK_INTERRUPTIBLE:阻塞(可中断)

TASK_UNINTERRUPTIBLE:阻塞(不可中断)

\_\_TASK_TRACED:被其他进程跟踪的进程

\_\_TASK_STOPPED:进程停止

陷入内核执行系统调用

异常处理程序

2.1.4 进程家族树

init进程所有进程都是PID为1的init进程的后代

内核在系统启动的最后阶段启动init进程。

init进程目的:读取系统的初始化脚本,并执行其他的相关程序,最终完成系统启动的整个过程。

task_struct中记录父子进程parent指针(指向父进程)

children子进程链表

3. 进程创建

其他操作系统提供产生(spawn)进程机制,首先在新地址空间里创建进程,读入可执行文件,最后开始执行。

UNIX将上述机制流程分成两步fork()和exec()fork()拷贝当前进程创建一个子进程

exec()负责读取可执行文件,并将其入地址空间

3.1 写时拷贝(copy-on-write)

使地址空间上的页的拷贝推迟到实际发生写入的时候才进行。

原理:如果有进程试图修改一个页,就会产生一个缺页中断。内核处理缺页中断的方式就是对该页进行一次透明复制。这时会清除页面的COW属性,表示着它不再被共享。

3.2 fork()函数

fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。

在现在linux内核中,fork()实际上是由clone()系统调用实现的

3.2.1 copy_process()函数dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct与当前进程相同。父子进程描述符是完全相同的。(分配空间)

检查并确保新创建这个进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。(检查边界)

子进程与父进程区别开。进程描述符的许多成员都要被清0或设初始值,那些不是继承来的进程描述符的成员,主要是统计信息。task_struct中的大多数数据都依然未被修改。(子进程初始化)

子进程的状态被设置为TASK_UNINTERRUPTIBLE(不可中断,阻塞状态),以保证它不会投入运行。(设置子进程状态)

copy_process()调用copy_flags()以更新task_struct的flags成员。(设置标志位)表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0

表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置

调用alloc_pid()为新进程分配一个有效的PID。(为子进程分配pid)

根据传递给clone()的参数,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。一般情况下,这些资源会被给定的进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里。(将资源参数标志赋值给结构体)

copy_process()做扫尾工作并返回一个指向子进程的指针,再回到do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。(返回子进程指针,并唤醒子进程执行)

注:内核有意让子进程先执行,并非总能如此,因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销。因为父进程先执行,可能往地址空间写入。

3.3 vfork函数

vfork()和fork()区别:vfork()不拷贝父进程的页表项。

vfork():子进程作为父进程的一个单独线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec(),子进程不能向地址空间写入。

4. 线程创建

线程创建和进程创建基本一致,通过调用clone()函数传递的参数标志,指明需要共享的资源。

创建线程clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

// CLONE_VM : 地址空间

// CLONE_FS : 文件系统

// CLONE_FILES : 文件描述符

// CLONE_SIGHAND : 信号处理程序及被阻断的信号

创建进程(等同fork()函数)clone(SIGCHLD,0);

创建进程(等同vfork()函数)clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)

4.1 内核线程

内核线程只在内核空间执行,从不切换到用户空间。

内核线程和普通进程的区别:内核线程没有独立的地址空间。(task_struct的mm指针被设置为NULL)

内核线程只能由其他内核线程创建,通过kthreadd内核线程衍生出所有新的内核线程。(kthreadd是所有内核线程的祖宗)

4.1.1 kthreadd内核线程

kthreadd内核线程是在内核初始化时被创建,循环执行kthreadd函数,它的作用是管理调度其它的内核线程。

kthreadd函数的作用是运行kthread_create_list全局链表中维护的kthread。可以调用kthread_create函数创建一个kthread,它会被加入到kthread_create_list链表中,同时kthread_create函数会唤醒kthreadd_task。kthreadd在执行kthread会调用老的接口,kthreadd内核线程在运行kthread时,会调用老接口kernel_thread,它会运行一个名为“kthread”的内核线程,去运行创建kthread,被执行的kthread会从kthread_create_list链表中删除,并且kthreadd会不断地调用scheduler让出CPU,这个线程不能关闭。

创建内核线程,不运行

kthread_create函数(源代码 | linux/kthread.h | v5.4)是通过clone()系统调用,创建一个内核线程,但新创建的线程处于不可运行状态。kthread_create(threadfn, data, namefmt, arg...)

创建内核线程,并运行

kthread_run函数(源代码 | linux/kthread.h | v5.4),通过调用kthread_create函数创建内核线程,然后调用wake_up_process()进行唤醒。#define kthread_run(threadfn, data, namefmt, ...) \

({ \

struct task_struct *__k \

= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \

if (!IS_ERR(__k)) \

wake_up_process(__k); \

__k; \

})

内核线程停止int kthread_stop(struct task_struct *k);

5. 进程终结

释放所占用的资源,并告知父进程。

一般来说,进程的析构是自身引起的,它发生在进程调用exit()系统调用的时候。

既可以显式地调用exit()这个系统调用,也可以隐性地从某个程序的主函数返回。(C语言编辑器会在main()函数的返回点后面放置调用exit代码)

终结的任务大部分都靠do_exit()()

5.1 do_exit()函数将task_struct中标志成员设置成PF_EXITING

调用del_timer_sync()删除任一内核定时器。确保没有定时器在排队,也没有定时器处理程序在运行。

如果BSD的记账功能是开启的,do_exit()调用acct_update_integrals()来输出记账信息。

调用exit_mm()函数释放进程占用的mm_struct,如果没有别的进程同时使用它们(也就是说,这个地址空间没有被共享),就彻底释放它们。

调用sem__exit()函数,如果进程排队等待IPC信号,它则离开队列。

调用exit_files()和exit_fs()分别递减文件描述符,文件系统数据引用计数,如果其中某个引用计数的数值降为零,那就不用代表没有进程在使用相应的资源,此时可以释放。

把存放在task_struct的exit_code()成员中的任务退出代码置为由exit()提供的退出代码,或者去完成任何其他有内核机制规定的退出动作。退出代码存放在这里供父进程随时检索。

调用exit_notify向父进程发送信号,给子进程重新找养父(其他线程或init进程),并将存放在task_struct结构中的exit_state设置为EXIT_ZOMBIE。

do_exit调用schedule()切换到新的进程,因为处于EXIT_ZOMBIE状态的进程不会被调度,所以这是进程所执行的最后一段代码,do_exit()永不返回。

5.2 wait族函数

wait族函数都是通过唯一但很复杂的一个系统调用wait4()来实现的,挂起调用它的进程,直到其中的一个子进程退出,此时函数会返回子进程的PID。此外,调用此函数时提供的指针会包含子函数的退出代码。

linux kernel 进程管理,Linux内核 | 进程管理相关推荐

  1. Linux kernel pwn notes(内核漏洞利用学习)

    前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...

  2. linux kernel 培训,《Linux Kernel培训课件》.pdf

    <Linux Kernel培训课件>.pdf Introduction to Linux Kernel Subsystems 中科信软高级技术培训中心- Objectives  Unde ...

  3. Linux Kernel - Debug Guide (Linux内核调试指南 )

    linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 ...

  4. Linux Kernel系列 - 黄牛X内核代码凝视

    Hanks.Wang - 专注于操作系统与移动安全研究.Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM    Mail - byhank ...

  5. g++ linux 编译开栈_使用 linux kernel +busybox 定制linux系统

    写在开头: 本来是想使用linux kernel +busybox 制作一个教程的,后来快要结束的时候,死活找不到硬盘,我了解很多文章都有类似的,但是没有谈到硬盘找不到问题,最后历经艰辛,终于把问题解 ...

  6. linux kernel 最新版本,linux kernel 最新版本4.10正式发布 附下载地址

    欢迎分享本新闻到大家的qq群.qq空间.微信群.微博等. 正如大家所预期的,大神Linus Torvalds今天发布了Linux Kernel 4.10的正式版,新版中带来了大量改善,强化了安全功能, ...

  7. android linux kernel VS standard linux kernel

    在kernel子目录下存放的就是Android的Linux Kernel了, 通过和标准的Linux 2.6.25 Kernel的对比,我们可以发现,其主要增加了以下的内容: 1. 基于ARM架构增加 ...

  8. 面具busybox模块_使用 linux kernel +busybox 定制linux系统

    目的: 了解linux的启动过程 主要内容: 1.grub 是启动程序的bootloader 2.linux-kernel 是linux的开源内核 3.busybox 是linux的工具集合 启动顺序 ...

  9. linux kernel 文件夹,Linux kernel 文件夹说明

    Linux 内核代码的目录结构 arch: 包含和硬件体系相关的的代码,每种硬件平台占一个相应的目录,如i386,arm, arm64, powerpc,mips等. block: 块设备驱动程序I/ ...

  10. linux kernel 调度,在Linux中,实时调度_kernel_开发99编程知识库

    在實時調度,fifo和RR不具有完全相同的含義他們在非實時調度. 但是,進程總是在FIFO方式中選擇的時間量程SCHED_FIFO不限制不同的時間量程SCHED_RR . schED_FIFO進程不能 ...

最新文章

  1. CoFun 1612 单词分组(容斥)
  2. Atcoder Educational DP Contest 题解
  3. 语法分析实验ll算法c语言,实验5LL语法分析程序的设计与实现(C语言).doc
  4. MapReduce Job集群提交过程源码跟踪及分析
  5. 对JDBC操作数据库的简单封装
  6. java 栈的用法_让Java程序员再次生机勃勃,还是技术的力量
  7. Microsoft visual studio关闭安全检查
  8. “世界百位名人”诠释上海世博会城市主题
  9. 【数据分享】历次人口普查数据(一普到七普)
  10. Spring Cloud Alibaba入门简介
  11. 编程语言-什么是低级语言?
  12. python 自动换ip_python实现自动更换ip的方法
  13. android10及以上通话录音
  14. vue 视频上传组件
  15. 定时开关机实现原理-Android4.4/6.0
  16. You-Get,多网站视频下载工具,非常方便
  17. 分享两个音乐播放地址
  18. java饲养员喂动物_你真的想当一名动物饲养员?
  19. 美光科技任命高云松担任大中华区政府事务副总裁
  20. 知识图谱在计算机安全的应用,基于知识图谱的计算机领域胜任力研究与应用

热门文章

  1. android 自定义多边形,Android:自定义view之Canvas绘制图形
  2. 计算机辅助的开发方法,基于计算机辅助设计技术(TCAD)的工艺开发
  3. [javaweb] servlet介绍与servlet的继承关系 和 service 方法 (一)
  4. 2014 ecb,_it’s_easy_as_123(修改bmp文件头) 攻防世界;
  5. python使用内置方法和修饰器方法获取类名、函数名
  6. Python四道面试题
  7. Python-类型注解(3.5引入)
  8. python中的作用域以及内置函数globals()-全局变量、locals()-局部变量
  9. mysql编码无效_mysql的严格模式与无效字符编码问题
  10. Linux ubuntu 修改终端【普通用户、root用户】命令(命令行)配色(颜色)(命令行自动计数)(/etc/profile)(~/bashrc)(source命令)