第3章 进程管理
进程
进程就是处于执行期的程序(目标码存放在某种存储介质上),但进程并不仅仅局限于一段可执行程序代码
执行线程,简称线程,是在进程中活动的对象
在传统的Linux系统中,一个进程只包含一个线程,但现在的系统中,包含多个线程的多线程程序司空见惯
而在Linux系统中线程只不过是一种特殊的进程罢了
在现代操作系统中,进程提供两种虚拟机制:
- 虚拟处理器
-
虚拟内存
在现代Linux内核中,fork()实际上是由clone()系统调用实现的。
进程描述符及任务结构
内核把进程的列表存放在叫做任务队列的双向循环链表中
链表中的每项都是类型为task_struct、称为进程描述符的结构,该结构定义在<linux/sched.h>文件中。
进程描述符中包含一个具体进程的所有信息
分配进程描述符
Linux以通过slab分配器分配task_struct结构,这样能达到对到对象复用和缓存着色的目的,这样做是为了让那些像x86那样寄存器较少的硬件体系结结构只要通过栈指针就可以估算出他的位置
每个任务的thread_info结构在他的内核栈的尾端分配
进程描述符的存放
内核通过一个唯一的进程标识值或PID来标识每个进程,内核把每个进程的PID存放在它们各自的进程描述符中
在内核中,访问任务通常需要获得指向其task_struct的指针,实际上,内核中大部分处理进程程描述符的速度就显得尤为重要
进程状态
进程描述符中state域描述了进程的当前状态。
五种进程状态:
- 运行
- 可中断
- 不可中断
- 被其他进程跟踪的进程
-
停止
内核需要经常调整某个进程的状态:set_task_state(task,state)函数
进程上下文
系统调用和异常处理程序是对内核明确定义的接口
进程只有通过这些接口才能陷入内核执行——对内核的所有访问都必须通过这些接口
进程家族树
在Unⅸ系统的进程之间存在—个明显的继承关系,在Linux系统中也是如此,所有的进程都是PID为1的init进程的后代
内核在系统启动的最后阶段启动init进程
该进程读取系统的初始化脚本并执行其他的相关程序,最终完成系统启动的整个过程
进程创建
Linux的fork()使用写时拷贝页实现,写时拷贝是一种可以推迟甚至免除拷贝数据的技术,内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝
只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝,也就是说资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享,这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行在页根本不会被写入的情况下它们就无须复制了
fork()
copy_process()完成的工作:
- 调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同,此时,子进程和父进程的描述符是完全相同的
- 检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制
- 子进程着手使自己与父进程区别开来,进程描述符内的许多成员都要被清0或设为初始值
- 子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行
- copy_process()调用copy_flags以更新task_struct的flags成员
- 调用alloc_pid()为新进程分配一个有效的PID
- 根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等
在一般情况下,这些资源会被给定进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里
vfork()
Linux实现线程的从内核的角度来说,它并没有线程这个概念,Linux把所有的线程都当做进程来实现,内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程,相反,线程仅仅被视为―个与其他进程共享某些资源的进程,每个线程都拥有唯一隶属于自己task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)
进程的创建与普通进程的创建类似,只不过在调用clone()时需要传递一些参数标志来指明所需要共享的资源
当一个进程终结时,内核必须释放它所占有的资源并把这一不幸告知其父进程
删除进程描述符:调用release_task函数
孤儿进程造成的进退维谷
如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白地耗费内存,前面的部分已经有所暗示于这个问题,解决方法是给子进程在当前线程组内找—个线程作为父亲,如果不行就让init做它们的父进程
总结
---------
我们学到了操作系统中的核心概念——进程,我们它为何如此重要,以及进程与线程之间的关系,讨论了Linux如何存放和表示进程,如何创建进程,如何把新的执行映像装入到地址空间,如何表示进程的层次关系,父进程又是如何收集其后代的信息以及进程最终如何消亡。