等待队列
进程转入休眠状态等待某个特定事件,该事件发生时该进程就会被唤醒。实现这个技术的是把该事件和等待队列联系起来。需要将转入休眠状态的进程插入队列中。当事件发生后,内核遍历该队列,唤醒休眠任务让他投入运行状态,任务负责将自己从等待队列中删除。 等待队列在内核中广泛存在。
Wait _queue结构
Include/linux/wait.h
struct wait_queue {
struct task_struct * task;
struct wait_queue * next;
};
Task:是task_struct结构的指针,它代表的是一个进程。
Next:它指向的是下一个wait_queue进程。
Sched.h
struct task_struct {
/* these are hardcoded - don't touch */
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
unsigned long signal;
unsigned long blocked; /* bitmap of masked signals */
unsigned long flags; /* per process flags, defined below */
int errno;
int debugreg[8]; /* Hardware debugging registers */
struct exec_domain *exec_domain;
/* various fields */
struct linux_binfmt *binfmt;
struct task_struct *next_task, *prev_task;
struct sigaction sigaction[32];
unsigned long saved_kernel_stack;
unsigned long kernel_stack_page;
int exit_code, exit_signal;
unsigned long personality;
int dumpable:1;
int did_exec:1;
int pid,pgrp,tty_old_pgrp,session,leader;
int groups[NGROUPS];
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->p_pptr->pid)
*/
struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
struct wait_queue *wait_chldexit; /* for wait4() */
unsigned short uid,euid,suid,fsuid;
unsigned short gid,egid,sgid,fsgid;
unsigned long timeout;
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
long utime, stime, cutime, cstime, start_time;
struct rlimit rlim[RLIM_NLIMITS];
unsigned short used_math;
char comm[16];
/* file system info */
int link_count;
struct tty_struct *tty; /* NULL if no tty */
/* ipc stuff */
struct sem_undo *semundo;
struct sem_queue *semsleeping;
/* ldt for this task - used by Wine. If NULL, default_ldt is used */
struct desc_struct *ldt;
/* tss for this task */
struct thread_struct tss;
/* filesystem information */
struct fs_struct fs[1];
/* open file information */
struct files_struct files[1];
/* memory management info */
struct mm_struct mm[1];
};
wait_event
16840:通过使用这个宏,内核代码能够使当前执行的进程在等待队列 wq 中等待直至给定
condition(可能是任何的表达式)得到满足。
16842:如果条件已经为真,当前进程显然也就无需等待了。
16844:否则,进程必须等待给定条件转变为真。这可以通过调用__wait_event来实现(16824
行),我们将在下一节介绍它。由于 __wait_event 已经同 wait_event 分离,已知条
件为假的部分内核代码可以直接调用__wait_queue,而不用通过宏来进行冗余的(特
别是在这些情况下)测试,实际上也没有代码会真正这样处理。更为重要的是,如
果条件已经为真,wait_event 会跳过将进程插入等待队列的代码。
注意 wait_event 的主体是用一个比较特殊的结构封闭起来的:
do {
/* … */
} while (0)
使我惊奇的是,这个小技巧并没有得到应有的重视。这里的主要思路是使被封闭的代码
能够像一个单句一样使用。考虑下面这个宏,该宏的目的是如果 p 是一个非空指针,则调用
free:#define FREE1(p) if (p) free (p)
除非你在如下所述的情况下使用 FREE1,否则所有调用都是正确有效的:
if (expression)
FREE1(p)
else
printf(“expression was false./n”) ;
FREE1 经扩展以后,else就和错误的 if――FREE1的 if――联系在一起。
我曾经发现有些程序员通过如下途径解决这种问题:
#define FREE2(p) if (p) { free(p); }
#define FREE3(p) { if (p) { free(p); } }
这两种方法都不尽人意,程序员在调用宏以后自然而然使用的分号会把扩展信息弄乱。以
FREE2 为例,在宏展开之后,为了使编译器能更准确的识别,我们还需要进行一定的缩进
调节,最终代码如下所示:
if (expression)
if (p) { free(p);}
else
printf(“expression was false./n”);
这样就会引起语法错误――else 和任何一个 if 都不匹配。FREE3 从本质上讲也存在同样的
问题。而且在研究问题产生原因的同时,你也能够明白为什么宏体里是否包含 if 是无关紧
要的。不管宏体内部内容如何,只要你使用一组括号来指定宏体,你就会碰到相同的问题。
这里是我们能够引入 do/while(0)技巧的地方。现在我们可以编写 FREE4,它能够克服
前面所出现的所有问题。
#define FREE4(P) /
do { /
if (p) /
free(p); /
while (0)
将 FREE4 和其它宏一样插入相同代码之后,宏展开后其代码如下所示(为清晰起见,
我们再次调整了缩进格式):
if (expression)
do {
if (p)
free(p);
} while (0); /* “;” following macro.*/
这段代码当然可以正确执行。编译器能够优化这个伪循环,舍弃循环控制,因此执行代
码并没有速度的损失,我们也从而得到了能够实现理想功能的宏。
虽然这是一个可以接受的解决方案,但是我们不能不提到的是编写函数要比编写宏好得
多。不过如果你不能提供函数调用所需的开销,那么就需要使用内联函数。这种情况虽然在
内核中经常出现,但是在其它地方就要少得多。(无可否认,当使用 C++,gcc 或者任何实
现了将要出现的修正版 ISO 标准 C 的编译器时,这种方案只是一种选择,就是最后为 C 增
加内联函数。)
__wait_event
16842:__wait_event 使当前进程在等待队列 wq 中等待直至 condition为真。
16829:通过调用 add_wait_queue(16791 行),局部变量__wait 可以被链接到队列上。注
意__wait 是在堆栈中而不是在内核堆中分配空间,这是内核中常用的一种技巧。在
宏运行结束之前,__wait 就已经被从等待队列中移走了,因此等待队列中指向它的
指针总是有效的。
16830:重复分配 CPU给另一个进程直至条件满足,这一点将在下面几节中讨论。
16831:进程被置为 TASK_UNINTERRUPTIBLE 状态(16190 行)。这意味着进程处于休
眠状态,不应被唤醒,即使是信号量也不能打断该进程的休眠。信号量在第 6 章中
介绍,而进程状态则在第 7 章中介绍。
16832:如果条件已经满足,则可以退出循环
请注意如果在第一次循环时条件就已经满足,那么前面一行的赋值就浪费了(因为
在循环结束之后进程状态会立刻被再次赋值)。__wait_event假定宏开始执行时条件
还没有得到满足。而且,这种对进程状态变量 state 的延迟赋值也并没有什么害处。
在某些特殊情况下,这种方法还十分有益。例如当__wait_event 开始执行时条件为
假,但是在执行到 16832 行时就为真了。这种变化只有在为有关进程状态的代码计
算 condition变量值时才会出现问题。但是在代码中这种情况我一处也没有发现。
:调用schedule(26686 行,在第 7 章中讨论)将 CPU转移给另一个进程。直到进程
再次获得 CPU时,对 schedule 的调用才会返回。这种情况只有当等待队列中的进程
被唤醒时才会发生。
:进程已经退出了,因此条件必定已经得到了满足。进程重置 TASK_RUNNING 的状
态(16188 行) ,使其适合CPU运行。
:通过调用 remove_wait_queue(16814 行)将进程从等待队列中移去。
wait_event_interruptible和__wait_event_interruptible (分别参见16868行和16847)
基本上与 wait_event 和__wait_event 相同,但不同的是它们允许休眠的进程可以被
信号量中断。如前所述,信号量将在第 6 章中介绍。
请注意 wait_event 是被如下结构所包含的。
({
/* … */
})
和 do/while(0)技巧一样,这样可以使被封闭起来的代码能够像一个单元一样运行。
这样的封闭代码就是一个独立的表达式,而不是一个独立的语句。也就是说,它可
以求值以供其它更复杂的表达式使用。发生这种情况的原因主要在于一些不可移植
的 gcc 特有代码的存在。通过使用这类技巧,一个程序块中的最后一个表达式的值
将定义为整个程序块的最终值。当在表达式中使用 wait_event_interruptible 时,执
wait_event_interruptible
行宏体后赋__ret的值为宏体的值(参看 16873行)。对于有Lisp 背景知识的程序员
来说,这是个很常见的概念。但是如果你仅仅了解一点 C和其它一些相关的过程性
程序设计语言,那么你可能就会觉得比较奇怪。
_wake_up
6829:该函数用来唤醒等待队列中正在休眠的进程。它由wake_up和wake_up_interruptible
调用(请分别参看 16612 行和 16614 行)。这些宏提供 mode 参数,只有状态满足
mode 所包含的状态之一的进程才可能被唤醒。
6833:正如将在第 10 章中详细讨论的那样,锁(lock)是用来限制对资源的访问,这在 SMP
逻辑单元中尤其重要,因为在这种情况下当一个 CPU在修改某数据结构时,另一个
CPU可能正在从该数据结构中读取数据,或者也有可能两个 CPU同时对同一个数据
结构进行修改,等等。在这种情况下,受保护的资源显然是等待队列。非常有趣的
是所有的等待队列都使用同一个锁来保护。虽然这种方法要比为每一个等待队列定
义一个新锁简单得多,但是这就意味着 SMP逻辑单元可能经常会发现自己正在等待
一个实际上并不必须的锁。
6838:本段代码遍历非空队列,为队列中正确状态的每一个进程调用 wake_up_process
(26356 行)。如前所述,进程(队列节点)在此可能并没有从队列中移走。这在很
大程度上是由于即使队列中的进程正在被唤醒,它仍然可能希望继续存在于等待队
列中,这一点正如我们在__wait_event中发现的问题一样。
参考linux 完全注释
<基于www.hacktao.com上的作品创作,转载请注明!>