Chinaunix首页 | 论坛 | 博客
  • 博客访问: 971566
  • 博文数量: 442
  • 博客积分: 1146
  • 博客等级: 少尉
  • 技术积分: 1604
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-04 12:52
个人简介

123

文章分类

全部博文(442)

文章存档

2017年(3)

2016年(15)

2015年(132)

2014年(52)

2013年(101)

2012年(110)

2011年(29)

分类: LINUX

2013-09-23 13:07:38

原文地址:进程的组织 作者:chenjifeng

内核在创建了很多进程后,如何管理组织这些进程就显得尤为重要了,要不然你的内核中就会充满许多无组织的进程。从task_struct中的几个字段就可以知道内核是如何组织管理这些进程的。

//仅仅列出部分字段

struct task_struct {
 struct list_head run_list;
 struct list_head tasks;
 struct task_struct *real_parent; /*real parent process (when being debugged) */
 struct task_struct *parent; /* parent process */
 struct list_head children; /* list of my children */
 struct list_head sibling; /* linkage in my parent's children list */
 struct task_struct *group_leader; /* threadgroup leader */
 /* PID/PID hash table linkage. */
 struct pid_link pids[PIDTYPE_MAX];
 struct list_head thread_group;
};

字段:tasks:
进程在内核中是以PCB的形式存在的,那么一个操作系统有很多进程,即就是很多PCB同时存在,那么如何有效的管理进程的组织方式,显然是一个很重要的问题,最简单的是将这些PCB直接用双向链表链起来,这样就可以遍历内核中所有的进程了,内核也确实这样做了,task_struct 结构中的tasks字段就是用来连接所有进程描述符的。在这个链中,链表头是init_task描述符,它是所谓的0进程或者swapper进程的描述符,init_task.tasks.prev字段指向链表中最后插入的进程描述符的tasks字段。用图来简单的表示下:

可以用宏for_each_process(p)实现进程链的遍历,此宏定义如下:
define for_each_process(p) \

for (p = &init_task ; (p = next_task(p)) != &init_task ; )
p首先指向0进程的描述符,然后在指向下一个进程描述符,在指向下一个描述符时用到了宏next_task(p),这个宏是用来找寻p描述符的下一个进程描述符的。定义如下:
#define next_task(p) \

list_entry(rcu_dereference((p)->tasks.next),  struct task_struct,

tasks)
又出现了一个宏list_entry( ),这个我在前面的文章中介绍过,这里不再多说。
虽然这样可以访问到系统中的任何一个进程,但是其效率是很低的,这对于内核来说绝对不能容忍,所以内核大师们用到了其他的办法。
字段run_list:
在内核中有一个队列是可运行队列,即就是保存了进程状态为:TASK_RUNNING的进程PCB,这个队列就是run_list字段,内核为每个优先级都设置了一个可运行队列,即如果一个进程的优先级为k(取值范围是0到139),那么内核就将此进程链入到优先权为k的可运行队列中,这样CPU可以从高优先级可可运行队列去选择要运行的进程,这样提高了效率,但为此却要把所有可运行的进程队列拆分成140个不同的队列。这些队列有一个单独的数据结构prio_array_t数据结构 来实现,定义如下(摘自linux2.6.11):

typedef struct prio_array prio_array_t;
struct prio_array {
         unsigned int nr_active;
         unsigned long bitmap[BITMAP_SIZE];
         struct list_head queue[MAX_PRIO];
};

其中的nr_active为链表中进程描述符的数量,bitmap是优先权位图,当且仅当某个优先权的进程链表不为空时设置相应的为标志。queue是140个优先权队列的头节点。以上的组织方式在2.6.11中是如此,但在2.6.23中却只有将运行队列的这种组织方式取消了,只有实时进程才有这种组织结构,我的理解是这样作增强了内核的实时性。
字段 children、 sibling和thread_group:
这三个字段分别是进程的子进程链表、兄弟链表和线程组链表。可以根据children和sibling字段找到一个进程家族的进程。进程在创建后必须处于某一个组中,通过thread_group来形成一个进程组。
字段real_parent、parent和group_leader:
real_parent指向了创建此进程的描述符,如果此进程的父进程不存在就指向进程1(init)的描述符,如用户在shell运行一个后台程序,当用户退出shell 时,此后台进程就会成为init的子进程。parent指向了当前父进程,于real_parent通常相同,但有时不同,如当另一个进程发出监控此进程的ptrace()系统调用请求时,跟踪进程会将被跟踪进程的parent字段改为跟踪进程的描述符,当跟踪结束时再将parent改回来,即改为real_parent字段的值。group_leader指向线程组领头进程的描述符。
在很多情况下内核必须能很快的找到对应的进程描述符,如杀死一个进程,杀死一组进程,这些都需要内核在很短的时间内找到这些进程描述符。为了达到这个目的,内核采用了哈希表结构来实现。在内核中有四个哈希表,为什么需要三个哈希表,是因为进程描述符包含了表示不同类型PID的字段,而每种类型的PID需要它自己的哈希表,比如想找到一个进程的PID或者一个进程组的PID。这四个哈希表在内核初始化期间被动态地分配了空间,并把他们的地址存入pid_hash数组中。pid_hash初始化在kernel/pid.c中的pidhash_inti()函数中,其中为pid_hash申请空间保存四个哈希表的操作是:

 pid_hash = alloc_bootmem(pidhash_size * sizeof(*(pid_hash)));
四个哈希表类型被定义成了枚举类型,即PIDTYPE_PID=0,PIDTYPE_PGID=1依次往下。
enum pid_type
{
        PIDTYPE_PID,
        PIDTYPE_PGID,
        PIDTYPE_SID,
        PIDTYPE_MAX
};
看下面的图可以帮助理解,现在的内核已经没有PIDTYPE_TGID那一项了。

阅读(864) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~