linux中的PCB(进程控制块) :task_struct(进程描述符)

linux中的进程控制块task_struct

进程就是处于执行期的程序,线程是进程中的活动的对象。每个线程都拥有一个独立的程序计数器(PC)、栈、和一组寄存器。内核调用的对象是线程而不是进程。
在Linux中,线程的实现非常独特,从内核的角度讲,linux并没有线程的概念。Linux把所有的线程都单过进程来实现,内核没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程,每个线程都拥有自己的task_struct。进程和线程都是通过系统调用clone()创建的,只不过传给系统调用的参数不同而已,我们知道统一进程下的不同线程是共享一些资源的,因此这些参数就设置了需要共享的资源,参数如下所示。
linux中的PCB(进程控制块) :task_struct(进程描述符)
linux中的PCB(进程控制块) :task_struct(进程描述符)

进程描述符 task_struct

内核把进程的列表存放在叫做任务队列的双向循环链表中。链表中每一项都是task_struct类型,称为进程描述符。task_struct结构相对较大,在32位机器上大约有1.7KB。进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有其他更多信息
在x86上,Linux通过slab分配器动态生成task_struct结构,所以只需在内核栈栈尾创建一个新的结构struct thread_info。结构中的task域存放的是指向该任务实际task_struct的指针(因为x86上没有多余的寄存器,寄存器太少了。如果寄存器足够的话可以将task_struct保存在寄存器中)

linux中的PCB(进程控制块) :task_struct(进程描述符)

进程描述符中的内容

进程描述符task_struct包含了进程相关的所有信息:
linux中的PCB(进程控制块) :task_struct(进程描述符)

1. 进程状态

进程描述符宏的state字段描述了进程当前所处的状态,系统中的每个进程必然处于五中状态中的一种:
TASK_RUNNING(运行)—— 进程是可执行的,它或者正在执行,或者在运行队列中等待执行。这是进程在用户空间或在内核空间中执行的唯一的可能的状态。
TASK_INTERRUPTIBLE(可中断)——进程正在睡眠(阻塞),等待某些条件的达成。处于此状态的进程也会因为接收到信号而提前被唤醒并准备投入运行(将进程状态放回到TASK_RUNNING)。
TASK_UNINTERRUPTIBLE(不可中断)——就算是接收到信号也不会被唤醒。除了这点以外和可中断状态一样。(很少用到,一个例子是当进程打开一个设备文件,相应的驱动程序开始探测相应的硬件设备时会用到这种状态,探测完成以前,设备驱动程序不能被中断,否则,硬件设备会处于不可预知的状态。)
__TASK_TRACED——被其他进程跟踪的进程,例如通过ptrace对调试程序进行跟踪。
__TASK_STOPPED(停止)——进程停止执行。进程没有投入运行也不能投入运行。通常这种状态发生在接收到SIGSTOPSIGTSTPSIGTTINSIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都为使进程进入这种状态。

2. PID

PID存放在进程描述符的pid字段中,PID被顺序编号,每个新建的进程的PID通常是前一个进程的PID+1。Linux引入线程组的表示,线程组id存放在进程描述符的tgid字段,同一个线程组的所有线程使用该线程组的领头线程pid作为tgid。getpid()系统调用返回的是线程的tgid,因此想要获取线程的pid,只能通过syscall(SYS_gettid)来获取。

3. 父子信息

进程描述符还存放了进程相关的亲属关系,在如下字段:

  1. real_parent:指向父进程的描述符,如果不存在,就指向进程1(init)的描述符
  2. parent: 父进程,
  3. children: 子进程链表的头部,
  4. silbling: 指向兄弟进程链表中的下一个或上一个元素

4. thread

尽管每个进程可以拥有属于自己的地址空间,但所有进程必须共享CPU寄存器,因此,在恢复一个进程的执行之前,内核必须确保每个寄存器装入了挂进程时的值。进程恢复执行前必须装入寄存器的一组数据称为硬件上下文。每次进程切换时,被替换进程的硬件上下问必须保存在某个地方,这个地方就是进程描述符中的thread字段,它指向的类型是thread_struct。在这个数据结构包含的字段涉及大部分CPU寄存器,但不包括诸如eax、ebx等通用寄存器,它们的值保存在内核栈中(而进程描述符是动态分配的,并不在内核栈中,内核栈只是保存了一个指向进程描述符的指针

5. mm(内存描述符)

我们知道每个进程都有自己的地址空间,而这个mm字段就存放了与进程地址空间有关的全部信息。mm字段的指向的类型是mm_struct。所有进程都有自己的内存描述符,同时所有进程的内存描述符组织在一个链表中,链表头为进程0的内存描述符的mmlist字段。关于进程的地址空间,详见(进程的地址空间)。

通俗地说

linux中的进程控制块(PCB)是task_struct,包含了进程相关的所有信息,包括进程当前的状态进程的标识符pid进程的亲属信息进程切换时的硬件上下文进程的地址空间等等。每个线程(linux中进程和线程的区分并不明显)对应一个task_struct,存放在内核栈的尾端,内核通过任务队列(一个循环双向链表)来组织所有线程的进程描述符。

参考文献

  1. Linux内核设计与实现 第三版
  2. 深入理解Linux内核