《Linux内核设计与实现》读书笔记之进程管理

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

     Linux内核把进程存放在叫做任务队列的双向循环链表中。链表中的每一项都是类型为task_struct,称为进程描述符的结构。进程描述符包含一个具体进程的所有信息。

     task_struct相对较大,在32位的机器长,它大约有1.7K字节。进程描述符中的数据能完整的描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态,还有其他更多信息(参见下图)。

技术分享

     内核在每个进程的内核栈底创建一个struct thread_info结构,使它记录下相应进程的task_struct的位置。如下图所示:

技术分享

     结构体thread_info的定义如下:

技术分享


2.进程状态

     进程描述符中的state域描述了进程当前的状态,该域的值为下列五种之一:

1.TASK_RUNNING(运行):进程是可执行的;或者它正在执行,或者处于运行队列中等待执行。

2.TASK_INTERRUPTIBLE(可中断):进程正在睡眠(阻塞),等待某些条件达成。

3.TASK_UNINTERRUPTIBLE(不可中断):除了不会因为接受到信号而被唤醒从而投入运行外,这个状态与可打断状态一样(使用的较少)。

4.TASK_ZOMBIE(僵死):该进程已经结束了,但是其父进程还没有调用wait4()系统调用。为了父进程能够获知它的消息,子进程的进程描述符仍然被保留着。一旦父进程调用了wait4(),进程描述符就会立即被释放。

5.TASK_STOPPED(停止):进程停止执行;进程没有投入运行也不能投入运行。通常这种状态发生在接受到SIGSTOP、SIGTSTP等信号的时候。

     进程状态之间的转移图如下所示:

技术分享


3.进程家族树

     Linux系统的进程之间存在一个明显的继承关系,在Linux系统中也是如此。所有的进程都是PID为1的init进程的后代。系统中的每个进程必有一个父进程。相应的,每个进程也可以拥有零个或多个子进程。进程间的关系包含在进程描述符中。每个task_struct都包含一个指向其父进程的task_struct指针parent,还包含一个称为children的子进程链表,由此即构成了Linux系统中的进程家族树。


4.进程创建

     Linux创建进程分为两个步骤:(1)fork();(2)exec()。首先fork()通过拷贝当前进程创建一个子进程。子进程与父进程的唯一区别仅仅在于PID、PPID(父进程ID)和某些资源与统计量(例如挂起的信号,它没有必要被继承)。exec()函数负责读取可执行文件并将其载入地址空间开始执行。

     传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率底下,因为它拷贝的数据也许并不共享。Linux的fork()采用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个拷贝。只有共享的数据需要写入时,数据才会被复制。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。采用这种方法fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。

     fork()的主要工作是由copy_process()完成的,copy_process()做的事情如下:

技术分享


5.线程在Linux中的实现

     Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视作一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的task_struct,所以在内核中,它看起来就像是一个普通进程。


5.进程终结

     进程终结大部分的工作都是由do_exit()函数来完成,它做的事情如下:

技术分享技术分享

     至此,与进程相关联的所有资源都被释放掉了(假设进程是这些资源的唯一使用者)。进程不可运行并且处于TASK_ZOMBIE状态。它所占用的所有资源就是内核栈、thread_info和task_struct结构。此时这些资源还没有被释放的唯一目的就是向它的父进程提供信息。父进程检索到信息后,有进程所持有的剩余内存被释放,归还给系统使用。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。