拥有操作系统就是为了运行用户程序。因此,进程管理就是所有操作系统的心脏所在。
3.1进程
进程
概念:进程就是出于执行期的程序。
资源:不仅仅是一段可执行程序代码,通常还要包含其他资源,如打开的文件,挂起的信号,内核内部数据等
相关调用:fork()、exec()、wait()
线程
概念:进程中活动的对象
资源:每个现场都拥有一个独立的程序计数器、进程栈和一组进程寄存器
区别:对Linux而言,线程只不过是一种特殊的进程罢了
3.2 进程描述符及任务结构
任务队列:内核将进程的列表存放在叫作任务队列的双向循环链表中。链表的每一项都是类型为task_struct的结构。
进程描述符(task_struct):包含一个具体进程的所有信息
进程描述符的存放
thread_info中含有task_struct指针。进程中的内核栈和thread_info定义在union中,内核栈从高地址到低地址,thread_info从低地址到高地址。
寻址thread_info
访问进程描述符是一个重要的频繁操作,先要寻址thread_info,再从thread_info中的task中找到task_struct,所以寻址thread_info的速度很重要
如何寻址thread_info
如果栈的大小为4(8)KB,thread_info地址在最低处,所以将栈顶指针esp的后12(13)位屏蔽即可得thread_info地址
进程状态
五种状态
状态转移
状态设置
set_task_state(task, state);
等价于
task->state = state;
内核空间
只有系统调用、异常处理程序才能陷入内核。
进程家族树
继承关系
所有的进程都是PID为1的init进程的后代。
访问
每个task-struct都包含一个指向其父进程task_struct、叫做parent的指针,还包含一个称为children的子进程链表。
任务队列本来就是一个双向循环链表,可以从任意一个task_struct到其他的task-struct
3.3进程创建
fork()通过拷贝当前进程创建一个子进程。子进程和父进程的区别很小:PID(每个进程唯一)、PPID(父进程进程号)等
写时拷贝
目的:拷贝完数据,新的进程立即执行新的映像,拷贝就浪费了,所以需要写时拷贝
概念:只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。
fork
Linux通过clone()系统调用实现fork()。
具体流程见书3.3.2
3.4 线程在Linux中的实现
实现:在LInux中,线程仅仅被视为一个与其他进程共享某些资源的进程,Linux把所有的线程都当做进程来实现。
原因:Linux中的进程本身就很轻量级,所以无需另外实现轻量级的线程
例子:Linux创建四个线程:创建四个进程并分配四个普通的task_struct结构。建立四个进程时指定他们共享某些资源。
创建线程
线程的创建和普通进程的创建类似。只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源。
clone()的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类。
内核线程
功能:内核需要在后台执行一些操作,这种任务可以通过内核线程完成,即独立运行在内核空间的标准进程。
区别:与普通进程的区别在于没有独立的地址空间,只在内核空间运行,从来不切换到用户空间。
具体流程:见书3.4.2
3.5进程终结
终结任务大部分要靠do_exit(),具体流程见书3.5
终结后:该进程为唯一使用者的那些资源都被释放掉,内核补课运行,并处于EXIT_ZOMBIE退出状态。占用的所有内存就是内核栈、thread_info、和tast_struct结构。
终结后进程存在的唯一目的:
向父进程提供信息。父进程检索到信息后,或者通知内核那是无关的信息后,由进程所持有的剩余内存被释放,归还给系统使用。
删除进程描述符
调用do_exit()后,线程已经为僵尸进程,不能运行了,但是系统还是保留了它的进程描述符。这样做可以让系统有办法在子进程终结后仍然能获得它的信息。父进程获得已终结的子进程的信息后(通过wait()),子进程的task_struct才被释放。
孤儿进程
概念:父进程在子进程之前退出
解决方法:在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程。具体代码及流程见书。