3.1 进程
处于执行期的程序。
进程就是正在执行的程序代码的实时结果。内核需要有效而又透明地管理所有细节。
执行线程(简称线程):在进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。
内和调度的对象是线程,而不是进程。
进程提供两种虚拟机制:虚拟处理器和虚拟内存。
3.2 进程描述符及任务结构
Linux通过slab分配器分配task_struct结构。
每个任务的thread_info结构在它的内核栈的尾端分配。结构中task域中存放的是指向该任务实际task_struct的指针。
内核通过一个唯一的进程标识值或PID来标识每个进程。
内核把每个进程的PID存放在它们各自的进程描述符中。
进程描述符中的state域描述了进程的当前状态。
内核调整某个进程的状态:
Set_task_state(task, state);
该函数将指定的进程设置为指定的状态。
当一个程序执行了系统调用或者触发了某个异常,它就陷入了内核空间。此时,我们称内核“代表进程执行”并处于进程上下文中。
除非在此间隙有更高优先级的进程需要执行并由调度器做出了相应调整,否则在内核退出的时候,程序恢复在用户空间会继续执行。
3.3 进程创建
Unix采用与众不同的实现方式,它把上述步骤分解到两个单独的函数中去执行:fork()、exec()。
1.fork()通过拷贝当前进程创建一个子进程。复制父进程的页表以及给子进程创建唯一的进程描述符。
Linux通过clone()系统调用实现fork()。
子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec()。子进程不能向地址空间写入。
3.4 线程在Linux中的实现
线程机制支持并发程序设计技术,在多处理器系统上,它能保证真正的并行处理。
创建进程在调用clone()的时候需要传递一些参数标志来指明需要共享的资源。
内核线程和普通的进程间的区别在于内核线程没有独立的地址空间。它们只在内和空间运行,从来不切换到用户空间去。
内核进程和普通进程一样,可以被调度,也可以被抢占。
3.5 进程终结
任务大部分要靠do_exit()来完成。
在调用了do_exit()之后,尽管线程已经不能再运行了,但是系统还保留了它的进程描述符。
进程终结时所需的清理工作和进程描述符的删除被分开执行。
Wait()这一族函数是通过唯一的一个系统调用wait4()来实现的。
当最终需要释放进程描述符时,release_task()会被调用。
当一个进程被跟踪时,它的临时父亲设定为调试进程。此时如果它的父进程退出了,系统会为它和它的所有兄弟重新找一个父进程。
解决办法:在一个单独的被ptrace跟踪的子进程链表中搜索相关的兄弟进程——用两个相对较小的链表减轻了遍历带来的消耗。