现在的位置: 首页 > 综合 > 正文

笔记(进程篇 详解 上)–深入理解linux内核

2013年09月22日 ⁄ 综合 ⁄ 共 4395字 ⁄ 字号 评论关闭

一、进程状态

进程描述符中的state字段描述了进程当前所处的状态,它有一组标志组成,每个标志描述一种可能的进程状态,这些状态是互斥的,也就是说在同一时刻,只能设置一个状态

1)运行状态:TASK_RUNNING,进程要么在CPU上运行,要么准备执行

2)可中断的等待状态:TASK_INTERRUPTIBLE,进程被挂起,直到收到某个消息,唤醒该进程

3)不可中断的等待状态:TASK_UNINTERRUPTIBLE ,当等待的条件为真时,并不能改变当前进程的状态

4)暂停状态:TASK_STOPPED,当执行的进程收到暂停的信号时,进入暂停状态

5)跟踪状态:TASK_TRACED,进程的执行由debugger程序暂停,当一个进程被另一个进程监控时,任何信号都可以把这个进程设置为跟踪状态

还有两个状态既可以存放在进程描述符的state字段中,也可以放在exit_state字段中,从这两个进程的名字可以看出,只有进程终止时,才有可能进入这两种状态

6)僵死状态:EXIT_ZOMBIE,进程的执行被终止,但是,父进程还没有发布wait4()和waitpid()系统调用来返回有关死亡进程的信息。之前,内核不能丢弃包含在死进程描述符中的数据,父进程还有可能需要它

7)僵死撤销状态:EXIT_DEAD,最终状态,父进程已经发布了wait4()和waitpid()系统调用,因而系统由系统删除,为了防止其他执行线程也在同一进程上执行wait()系统调用,所以把进程的状态由僵死状态改完僵死撤销状态

State字段的值通常用一个简单的赋值语句设置:

p->state=TASK_RUNNING;

内核也使用set_task_state和set_current_state()宏,他们分别设置指定进程的状态和当前进程的状态。

二、进程描述符

   

三、标记一个线程

    进程和进程描述符之间有一个非常严格的一一对应关系,这使得32位进程描述符地址标记进程成为一种方便的方式,进程描述符指针指向这个地址,内核对进程的大部分引用时通过进程描述符指针进行的。

PID也可以标记一个进程(在类unix操作系统中),PID是进程标识符,存放在进程描述符的pid字段中,PID被顺序编号,新创建的进程是前一创建进程的PID+1,pID的值有一个上限,当达到这个上限后,就必须开始循环使用已闲置的最小PID号。由于循环使用PID编号,内核必须管理一个pidmap__array位图来表示当前已分配的PID号和闲置的PID号。

POSIX 1003.1c标准规定一个多线程应用程序中的所有线程必须拥有相同的PID。一个多线程应用程序的所有线程共享相同的PID。

四、进程描述符处理

   thread_info数据结构和进程内核栈存放在两个连续的页框中,,如图所示,thread_info结构从0x015fa000地址开始存放,而进程内核栈从ox015fc000地址处开始存放,esp寄存器指向地址为0x015ff878的当前栈顶。内核使用alloc_thread_info和free_thrad_info宏分配和释放存储thread_info结构和内核栈的内存区

五、linux内核中的双向链表

   Linux内核定义了list_head数据结构,字段next和prev分别表示通用双向链表向前和向后的指针元素。

LIST——HEAD(list_name):创建新链表

list_add(n,p):把n指向的元素插入p指向的特定元素之后

list_add_tail(n,p),把n指向的元素插入到p指向的特定元素之前

list_del(p):删除p指向的元素

list——empty(p) 检查由第一个元素的地址p所指向的链表是否为空

 

六、TASK_RUNNING下的进程链表

早先的linux版本把所有的可运行进程都放在同一个运行队列中(链表),由于维持这个链表按优先级排序时开销过大,因此,早期的调度程序不得在选择最佳可运行进程时扫描整个队列

     linux2.6实现方法有些不同,其目的是在固定时间内选出最佳可运行进程,与队列中的进程数目无关。

     提高调度运行速度的方法是建立多个可运行进程链表,每种进程优先权对应一个不同的链表。如果某个进程的优先权等于k,就把该进程放在优先级为k的进程链表中。

    内核必须为系统中每个运行队列保存大量的数据,不过运行队列的主要数据结构还是组成运行队列的进程描述符链表,所有这些链表都是有哟个单独的prio_array_数据结构实现,其字段说明如下:

   int      nr_active     链表中进程描述符的数量

  unsigned long[5] bitmap 优先权位图:当且仅当某个优先权的进程链表不为空时设置相应的位标志

 struct list_head[15]  queue 140优先权队列的头结点

七、进程间的关系

程序创建的进程具有父子关系,如果一个进程创建了多个进程时,这些子进程之间具有兄弟关系。在进程描述符中引入几个字段表示这些关系,进程0和1是有内核创建的,进程1是所有进程的祖先。

P的进程描述符中表示进程亲属关系的字段的描述

real_parent:指向了创建P的进程描述符,如果P的父进程不存在,就指向进程1的进程描述符。

parent:指向P的当前父进程,一般与real_parent保持一致

children:链表的头部,该链表所有元素都是由P创建的

sibling:指向兄弟进程链表中的下一个元素或者前一个元素的指针,这些兄弟元素都是由P创建的

建立非亲属关系的进程描述符字段:

Group_leader:P所在进程组的领头进程的描述符指针

signal->pgrp:P所在进程组的领头进程的PID

tgid: P所在线程组的领头进程的PID

signal->session:p的登录会话领头进程的PID

ptrace_children:链表的头,该链表包含所有被debugger程序跟踪的P的子进程

八、等待队列

  等待队列有很多用途,尤其用在中断处理、进程同步以及定时。等待队列实现了再事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。因此,等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。

等待队列由双向链表实现,其元素包括指向进程描述符的指针,每个等待队列都有一个等待队列头,等待队列头是一个类型为wait_queue_head_t的数据结构

Struct __wait_queque_head

{

      Spinlock_tlock;

      Structlist_head task_list;

};

Typedefstruct __wait_queque_headwait_queque_head_t;

同步是通过等待队列头的lock自旋达到的,task_list字段是等待进程链表的头

等待队列链表中的元素类型:

Struct __wait_queue

{

      Unsigned intflags;

      Structtask_struct * task;

     Wait_queue_func_t func;

      Structlist_head task_list;

};

Typedefstruct  __wait_queuewait_queue_t;

等待队列链表中的每个元素代表一个睡眠进程,该进程等待某一事件的发生,它的描述符放在task字段中,task_list字段中包含的是指针,由这个指针把一个元素连接到等待相同事件的进程链表中。

有两种睡眠进程:互斥进程(等待队列元素的flags字段为1)由内核有选择的唤醒,而非互斥进程(flags字段为0),总是由内核在事件发生时唤醒。

 

 九、等待队列的操作

  可以使用DECLARE_WAIT_QUEUE_HEAD(name)宏定义一个新等待队列的头,它静态地声明一个叫name的等待队列的头变量并对该变量的lock和task_list字段进行初始化。函数init_waitqueue_head()可以用来初始化动态分配的等待队列的头变量。

函数init_waitqueue_entry(p,q),如下所示初始化wait_queue_t结构的变量q:

q->flags=0;

q->task=p;

q->func=default_wake_function;

非互斥进程P将由default_wake_function()唤醒。

一旦定义了一个元素,必须把它插入到等待队列,add_wait_queque()函数把一个非互斥进程插入到等待队列链表的第一个位置,add_wait_queue_exclusive()函数把一个互斥进程插入到等待队列链表的最后一个位置。Remove_wait_queue()函数从等待队列链表中删除一个元素,waitqueue_active()函数检查一个给定的等待队列是否为空。

要等待特定条件的进程可以调用如下任何一个函数:

1,sleep_on()对当前进程进行操作

void sleep_on(wait_queue_head_t *wq)

{

      Wait_queue_twait;

      Init_waitqueue_entry(&wait,current);

      Current->state=TASK_UNINTERRUPTIBLE;

      Add_wait_queue(wq,&wait);

      Schedule();

      Remove_wait_queue(wq,&wait);

}

2,interruptible_sleep_on()与sleep_on()函数是一样的,但稍有不同,前者将当前进程状态设置为TASK_INTERRUPTIBLE,后者设置为TASK_UNINTERRUPTIBLE

3,sleep_on_timeout()和interruptible_sleep_on_timeout(),允许调用者定义个时间间隔,过了时间间隔后,进程将由内核唤醒。

4,prepare_to_wait(),prepare_to_wait_exclusive(),finish_wait()

5,wait_event和wait_event_interruptible宏使他们的调用进程在等待队列上睡眠,一直到修改了条件为止。

内核通过任何一个下面的宏唤醒等待队列中的进程并把他们的状态设置为TASK_RUNNING

Wake_up,wake_up_nr,wake_up_all,wake_up_interruptible,wake_up_locked等等。

当唤醒某一一个进程时,由于所有的互斥进程都在双向链表的尾部,而所有的非互斥进程都在链表的开始位置,所以函数总是先唤醒非互斥进程然后再唤醒互斥进程。

 

抱歉!评论已关闭.