📄 5.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>CTerm非常精华下载</title>
</head>
<body bgcolor="#FFFFFF">
<table border="0" width="100%" cellspacing="0" cellpadding="0" height="577">
<tr><td width="32%" rowspan="3" height="123"><img src="DDl_back.jpg" width="300" height="129" alt="DDl_back.jpg"></td><td width="30%" background="DDl_back2.jpg" height="35"><p align="center"><a href="http://bbs.tsinghua.edu.cn"><font face="黑体"><big><big>水木清华★</big></big></font></a></td></tr>
<tr>
<td width="68%" background="DDl_back2.jpg" height="44"><big><big><font face="黑体"><p align="center"> 内核源代码分析 (BM: suzhe) </font></big></big></td></tr>
<tr>
<td width="68%" height="44" bgcolor="#000000"><font face="黑体"><big><big><p align="center"></big></big><a href="http://cterm.163.net"><img src="banner.gif" width="400" height="60" alt="banner.gif"border="0"></a></font></td>
</tr>
<tr><td width="100%" colspan="2" height="100" align="center" valign="top"><br><p align="center">[<a href="index.htm">回到开始</a>][<a href="index.htm">上一层</a>][<a href="6.htm">下一篇</a>]
<hr><p align="left"><small>发信人: axp33a (无聊中...), 信区: Linux <br>
标 题: Linux内核源代码分析2-2-2 <br>
发信站: BBS 水木清华站 (Thu Aug 3 11:21:53 2000) WWW-POST <br>
<br>
2.2.2 等待队列 <br>
前一节我们曾简要的提到进程(也就是正在运行的程序)可以转入休眠状态以等待某个特 <br>
定事件,当该事件发生时这些进程能够被再次唤醒。内核实现这一功能的技术要点是把等 <br>
待队列(wait queue)和每一个事件联系起来。需要等待事件的进程在转入休眠状态后插 <br>
入到队列中。当事件发生之后,内核遍历相应队列,唤醒休眠的任务让它投入运行状态。 <br>
任务负责将自己从等待队列中清除。 <br>
等待队列的功能强大得令人吃惊,它们被广泛应用于整个内核中。更重要的是,实现等待 <br>
队列的代码量并不大。 <br>
1. wait_queue结构 <br>
18662:简单的数据结构就是等待队列节点,它包含两个元素: <br>
* task—指向struct task_struct结构的指针,它代表一个进程。从16325行开始的 <br>
struct task_struct结构将在第7章中进行介绍。 <br>
* next—指向队列中下一节点的指针。因而,等待队列实际上是一个单链表。 <br>
通常,我们用指向等待队列队首的指针来表示等待队列。例如,printk使用的等待队列 <br>
log_wait(25647行)。 <br>
2. wait_event <br>
16840:通过使用这个宏,内核代码能够使当前执行的进程在等待队列wq中等待直至给定 <br>
condition(可能是任何的表达式)得到满足。 <br>
16842:如果条件已经为真,当前进程显然也就无需等待了。 <br>
16844:否则,进程必须等待给定条件转变为真。这可以通过调用__wait_event来实现( <br>
16824行),我们将在下一节介绍它。由于__wait_event已经同wait_event分离,已知条 <br>
件为假的部分内核代码可以直接调用__wait_queue,而不用通过宏来进行冗余的(特别是 <br>
在这些情况下)测试,实际上也没有代码会真正这样处理。更为重要的是,如果条件已经 <br>
为真,wait_event会跳过将进程插入等待队列的代码。 <br>
注意wait_event的主体是用一个比较特殊的结构封闭起来的: <br>
奇怪的是,这个小技巧并没有得到应有的重视。这里的主要思路是使被封闭的代码能够像 <br>
一个单句一样使用。考虑下面这个宏,该宏的目的是如果p是一个非空指针,则调用free <br>
: <br>
除非你在如下所述的情况下使用FREE1,否则所有调用都是正确有效的: <br>
FREE1经扩展以后,else就和错误的if(FREE1的if)联系在一起。 <br>
有些程序员通过如下途径解决这种问题: <br>
这两种方法都不尽人意,程序员在调用宏以后自然而然使用的分号会把扩展信息弄乱。以 <br>
FREE2为例,在宏展开之后,为了使编译器能更准确地识别,我们还需要进行一定的缩进 <br>
调节,最终代码如下所示: <br>
这样就会引起语法错误—else和任何一个if都不匹配。FREE3从本质上讲也存在同样的问 <br>
题。而且在研究问题产生原因的同时,就能够明白为什么宏体里是否包含if是无关紧要的 <br>
。不管宏体内部内容如何,只要使用一组括号来指定宏体,就会碰到相同的问题。 <br>
引入do/while(0)技巧能够克服前面所出现的所有问题,现在我们可以编写FREE4。 <br>
将FREE4和其他宏一样插入相同代码之后,宏展开后其代码如下所示(为清晰起见,我们 <br>
再次调整了缩进格式): <br>
这段代码当然可以正确执行。编译器能够优化这个伪循环,舍弃循环控制,因此执行代码 <br>
并没有速度的损失,我们也从而得到了能够实现理想功能的宏。 <br>
虽然这是一个可以接受的解决方案,但是我们不能不提到的是编写函数要比编写宏好得多 <br>
。不过如果你不能提供函数调用所需的开销,那么就需要使用内联函数。这种情况虽然在 <br>
内核中经常出现,但是在其他地方就要少得多。(不可否认,当使用C++、gcc或者任何实 <br>
现了将要出现的修正版ISO标准C的编译器时,这种方案只是一种选择,就是最后为C增加 <br>
内联函数。) <br>
3. __wait_event <br>
16824:__wait_event使当前进程在等待队列wq中等待,直至condition为真。 <br>
16829:通过调用add_wait_queue(16791行),局部变量__wait可以被链接到队列上。注 <br>
意__wait是在堆栈中而不是在内核堆中分配空间,这是内核中常用的一种技巧。在宏运行 <br>
结束之前,__wait就已经被从等待队列中移走了,因此等待队列中指向它的指针总是有效 <br>
的。 <br>
16830:重复分配CPU给另一个进程直至条件满足,这一点将在下面几节中讨论。 <br>
16831:进程被置为TASK_UNINTERRUPTIBLE状态(16190行)。这意味着进程处于休眠状态 <br>
,不应被唤醒,即使是信号也不能打断该进程的休眠。信号在第6章中介绍,而进程状态 <br>
则在第7章中介绍。 <br>
16832:如果条件已经满足,则可以退出循环。 <br>
请注意如果在第一次循环时条件就已经满足,那么前面一行的赋值就浪费了(因为在循环 <br>
结束之后进程状态会立刻被再次赋值)。__wait_event假定宏开始执行时条件还没有得到 <br>
满足。而且,这种对进程状态变量state的延迟赋值也并没有什么害处。在某些特殊情况 <br>
下,这种方法还十分有益。例如当__wait_event开始执行时条件为假,但是在执行到 <br>
16832行时就为真了。这种变化只有在为有关进程状态的代码计算condition变量值时才会 <br>
出现问题。但是在代码中这种情况我没有发现。 <br>
16834:调用schedule(26686行,在第7章中讨论)将CPU转移给另一个进程。直到进程再 <br>
次获得CPU时,对schedule的调用才会返回。这种情况只有当等待队列中的进程被唤醒时 <br>
才会发生。 <br>
16836:进程已经退出了,因此条件必定已经得到了满足。进程重置TASK_RUNNING的状态 <br>
(16188行),使其适合CPU运行。 <br>
16837:通过调用remove_wait_queue(16814行)将进程从等待队列中移去。 <br>
wait_event_interruptible和__wait_event_interruptible(分别参见16868行和16847) <br>
基本上与wait_event和__wait_event相同,但不同的是它们允许休眠的进程可以被信号中 <br>
断。信号将在第6章中介绍。 <br>
请注意wait_event是被如下结构所包含的。 <br>
和do/while(0)技巧一样,这样可以使被封闭起来的代码能够像一个单元一样运行。这样 <br>
的封闭代码就是一个独立的表达式,而不是一个独立的语句。也就是说,它可以求值以供 <br>
其他更复杂的表达式使用。发生这种情况的原因主要在于一些不可移植的gcc特有代码的 <br>
存在。通过使用这类技巧,一个程序块中的最后一个表达式的值将定义为整个程序块的最 <br>
终值。当在表达式中使用wait_event_interruptible时,执行宏体后赋__ret的值为宏体 <br>
的值(参见16873行)。对于有Lisp背景知识的程序员来说,这是个很常见的概念。但是 <br>
如果你仅仅了解一点C和其他一些相关的过程性程序设计语言,你可能就会觉得比较奇怪 <br>
。 <br>
__wake_up <br>
26829:该函数用来唤醒等待队列中正在休眠的进程。它由wake_up和wake_up_ <br>
interruptible调用(请分别参见16612行和16614行)。这些宏提供mode参数,只有状态 <br>
满足mode所包含的状态之一的进程才可能被唤醒。 <br>
26833:正如将在第10章中详细讨论的那样,锁(lock)是用来限制对资源的访问,这在 <br>
SMP逻辑单元中尤其重要,因为在这种情况下当一个CPU在修改某数据结构时,另一个CPU <br>
可能正在从该数据结构中读取数据,或者也有可能两个CPU同时对同一个数据结构进行修 <br>
改,等等。在这种情况下,受保护的资源显然是等待队列。非常有趣的是所有的等待队列 <br>
都使用同一个锁来保护。虽然这种方法要比为每一个等待队列定义一个新锁简单得多,但 <br>
是这就意味着SMP逻辑单元可能经常会发现自己正在等待一个实际上并不必须的锁。 <br>
26838:本段代码遍历非空队列,为队列中正确状态的每一个进程调用wake_up_process( <br>
26356行)。如前所述,进程(队列节点)在此可能并没有从队列中移走。这在很大程度 <br>
上是由于即使队列中的进程正在被唤醒,它仍然可能希望继续存在于等待队列中,这一点 <br>
正如我们在__wait_event中发现的问题一样。 <br>
<br>
<br>
-- <br>
※ 来源:·BBS 水木清华站 smth.org·[FROM: 166.111.196.22] <br>
</small><hr>
<p align="center">[<a href="index.htm">回到开始</a>][<a href="index.htm">上一层</a>][<a href="6.htm">下一篇</a>]
<p align="center"><a href="http://cterm.163.net">欢迎访问Cterm主页</a></p>
</table>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -