📄 00000005.htm
字号:
<HTML><HEAD> <TITLE>BBS水木清华站∶精华区</TITLE></HEAD><BODY><CENTER><H1>BBS水木清华站∶精华区</H1></CENTER>发信人: 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><CENTER><H1>BBS水木清华站∶精华区</H1></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -