linux管理进程的链表

  linux2.6.11的内核中,为了方便管理linux的进程,主要建了5种linux链表。每个链表节点之间的互联有两种方式,一种是hash节点之间的互联,通过hlist_node的数据结构来实现;另一种就是list_head类型的数据结构来互联。看linux内核的人对这两种类型的数据结构肯定是不会陌生的,因为它们在linux内核中无处不在。

 

1 进程直接的互连

  通过任务描述符结构task_struct结构中的tasks成员来实现各个节点之间的互连,它是list_head类型。这个链表是一个循环的双向链表,开始的时候只有init_task这一个进程,它是内核的第一个进程,它的初始化是通过静态分配内存,"手动"(其它的进程初始化都是通过动态分配内存初始化的)进行的,每新建一个进程,就通过SET_LINKS宏将该进程的task_struct结构加入到这条双向链表中,不过要注意的是如果一个进程新建一个线程(不包括主线程),也就是轻量级进程,它是不会加入该链表的。通过宏for_each_process可以从init_task开始遍历所有的进程。

 

2 TASK_RUNNING状态的进程链表

  为了能让调度程序在固定的时间内选出”最佳“可运行的进程,与队列中可运行的进程数无关,建立了多个可运行进程链表,每个优先级对应一个,总共有140个。linux内核定义了一个prio_array_t类型的结构体来管理这140个链表。每个可运行的进程都在这140个链表中的一个,通过进程描述符结构中的run_list来实现,它也是一个list_head类型。enqueue_task是把进程描述符插入到某个可运行链表中,dequeue_task则从某个可运行链表中删除该进程描述符。TASK_RUNNING状态的prio_array_t类型的结构体是runqueue结构的arrays[1]成员。

 

3 进程间的关系

  linux进程间的关系有两种,一种是父进程与子进程间的父子关系,一种是进程同属一个父进程的兄弟关系。linux中是通过进程描述符中的children和sibling来实现这种关系的,它们都是list_head类型的。children的next指向的是该进程最新的子进程,prev指向的是该该进程最老的子进程,sibling的next指向的是它父进程中比它更老的子进程,prev指向的是它父进程中比它更新的子进程。最新子进程的slibling.prev指向的是父进程,最老子进程的slibling.next也是指向父进程。这样通过children和sibling实现了一个循环的双向链表,该双向链表以父进程描述符为头节点。

进程间亲属关系

 

4 pidhash链表

  为了通过pid找到进程的描述符,如果直接遍历进程间互联的链表来查找进程id为pid的进程描述符显然是低效的,所以为了更为高效的查找,linux内核使用了4个hash散列表来加快查找,之所以使用4个散列表,是为了能根据不同的pid类型来查找进程描述符,它们分别是进程的pid,线程组领头进程的pid,进程组领头进程的pid,会话领头进程的pid。每个类型的散列表中是通过宏pid_hashfn(x)来进行散列值的计算的。每个进程都可能同时处于这是个散列表中,所以在进程描述符中有一个类型为pid结构的pids成员,通过它可以将进程加入散列表中,pid结构中包含解决散列冲突的pid_chain成员,它是hlist_node类型的,还有一个是将相同pid链起来的pid_list,它是list_head类型。

pid散列表

 

5 等待队列

  linux把等待同一个事件发生或资源的进程都链接在一起形成一个带头节点的双向链表。等待队列的头是用类型wait_queue_head_t描述,里面包含了list_head类型的task_list成员。等待队列中节点的类型用wait_queue_t描述,该结构里有task_struct类型的指针task和list_head类型的task_list成员。为什么不像前面4个队列中一样,将list_head类型的task_list成员放到进程的描述符里来形成链表呢?原因是linux等待队列太多了,每个事件,每个资源都可以形成一个等待队列,一个进程还可以等待多个事件的发生,所以通过一个单独的类型来形成队列是需要的。linux通过sleep_on函数来将某个进程加入到某个等待队列中和从等待队列中删除。调用sleep_on的进程都会主动让出cpu进入等待状态,可以通过wake_up来唤醒某个等待状态的进程。

posted @ 2013-10-18 19:19  在于思考  阅读(3888)  评论(0编辑  收藏  举报