?? 00000005.htm
字號:
<HTML><HEAD> <TITLE>BBS水木清華站∶精華區(qū)</TITLE></HEAD><BODY><CENTER><H1>BBS水木清華站∶精華區(qū)</H1></CENTER>發(fā)信人: axp33a (無聊中...), 信區(qū): Linux <BR>標(biāo) 題: Linux內(nèi)核源代碼分析2-2-2 <BR>發(fā)信站: BBS 水木清華站 (Thu Aug 3 11:21:53 2000) WWW-POST <BR> <BR>2.2.2 等待隊(duì)列
<BR>前一節(jié)我們曾簡要的提到進(jìn)程(也就是正在運(yùn)行的程序)可以轉(zhuǎn)入休眠狀態(tài)以等待某個特 <BR>定事件,當(dāng)該事件發(fā)生時(shí)這些進(jìn)程能夠被再次喚醒。內(nèi)核實(shí)現(xiàn)這一功能的技術(shù)要點(diǎn)是把等 <BR>待隊(duì)列(wait queue)和每一個事件聯(lián)系起來。需要等待事件的進(jìn)程在轉(zhuǎn)入休眠狀態(tài)后插 <BR>入到隊(duì)列中。當(dāng)事件發(fā)生之后,內(nèi)核遍歷相應(yīng)隊(duì)列,喚醒休眠的任務(wù)讓它投入運(yùn)行狀態(tài)。 <BR>任務(wù)負(fù)責(zé)將自己從等待隊(duì)列中清除。
<BR>等待隊(duì)列的功能強(qiáng)大得令人吃驚,它們被廣泛應(yīng)用于整個內(nèi)核中。更重要的是,實(shí)現(xiàn)等待 <BR>隊(duì)列的代碼量并不大。
<BR>1. wait_queue結(jié)構(gòu)
<BR>18662:簡單的數(shù)據(jù)結(jié)構(gòu)就是等待隊(duì)列節(jié)點(diǎn),它包含兩個元素:
<BR>* task—指向struct task_struct結(jié)構(gòu)的指針,它代表一個進(jìn)程。從16325行開始的 <BR>struct task_struct結(jié)構(gòu)將在第7章中進(jìn)行介紹。
<BR>* next—指向隊(duì)列中下一節(jié)點(diǎn)的指針。因而,等待隊(duì)列實(shí)際上是一個單鏈表。
<BR>通常,我們用指向等待隊(duì)列隊(duì)首的指針來表示等待隊(duì)列。例如,printk使用的等待隊(duì)列 <BR>log_wait(25647行)。
<BR>2. wait_event
<BR>16840:通過使用這個宏,內(nèi)核代碼能夠使當(dāng)前執(zhí)行的進(jìn)程在等待隊(duì)列wq中等待直至給定 <BR>condition(可能是任何的表達(dá)式)得到滿足。
<BR>16842:如果條件已經(jīng)為真,當(dāng)前進(jìn)程顯然也就無需等待了。
<BR>16844:否則,進(jìn)程必須等待給定條件轉(zhuǎn)變?yōu)檎妗_@可以通過調(diào)用__wait_event來實(shí)現(xiàn)( <BR>16824行),我們將在下一節(jié)介紹它。由于__wait_event已經(jīng)同wait_event分離,已知條 <BR>件為假的部分內(nèi)核代碼可以直接調(diào)用__wait_queue,而不用通過宏來進(jìn)行冗余的(特別是 <BR>在這些情況下)測試,實(shí)際上也沒有代碼會真正這樣處理。更為重要的是,如果條件已經(jīng) <BR>為真,wait_event會跳過將進(jìn)程插入等待隊(duì)列的代碼。
<BR>注意wait_event的主體是用一個比較特殊的結(jié)構(gòu)封閉起來的:
<BR>奇怪的是,這個小技巧并沒有得到應(yīng)有的重視。這里的主要思路是使被封閉的代碼能夠像 <BR>一個單句一樣使用。考慮下面這個宏,該宏的目的是如果p是一個非空指針,則調(diào)用free <BR>:
<BR>除非你在如下所述的情況下使用FREE1,否則所有調(diào)用都是正確有效的:
<BR>FREE1經(jīng)擴(kuò)展以后,else就和錯誤的if(FREE1的if)聯(lián)系在一起。
<BR>有些程序員通過如下途徑解決這種問題:
<BR>這兩種方法都不盡人意,程序員在調(diào)用宏以后自然而然使用的分號會把擴(kuò)展信息弄亂。以 <BR>FREE2為例,在宏展開之后,為了使編譯器能更準(zhǔn)確地識別,我們還需要進(jìn)行一定的縮進(jìn) <BR>調(diào)節(jié),最終代碼如下所示:
<BR>這樣就會引起語法錯誤—else和任何一個if都不匹配。FREE3從本質(zhì)上講也存在同樣的問 <BR>題。而且在研究問題產(chǎn)生原因的同時(shí),就能夠明白為什么宏體里是否包含if是無關(guān)緊要的 <BR>。不管宏體內(nèi)部內(nèi)容如何,只要使用一組括號來指定宏體,就會碰到相同的問題。
<BR>引入do/while(0)技巧能夠克服前面所出現(xiàn)的所有問題,現(xiàn)在我們可以編寫FREE4。
<BR>將FREE4和其他宏一樣插入相同代碼之后,宏展開后其代碼如下所示(為清晰起見,我們 <BR>再次調(diào)整了縮進(jìn)格式):
<BR>這段代碼當(dāng)然可以正確執(zhí)行。編譯器能夠優(yōu)化這個偽循環(huán),舍棄循環(huán)控制,因此執(zhí)行代碼 <BR>并沒有速度的損失,我們也從而得到了能夠?qū)崿F(xiàn)理想功能的宏。
<BR>雖然這是一個可以接受的解決方案,但是我們不能不提到的是編寫函數(shù)要比編寫宏好得多 <BR>。不過如果你不能提供函數(shù)調(diào)用所需的開銷,那么就需要使用內(nèi)聯(lián)函數(shù)。這種情況雖然在 <BR>內(nèi)核中經(jīng)常出現(xiàn),但是在其他地方就要少得多。(不可否認(rèn),當(dāng)使用C++、gcc或者任何實(shí) <BR>現(xiàn)了將要出現(xiàn)的修正版ISO標(biāo)準(zhǔn)C的編譯器時(shí),這種方案只是一種選擇,就是最后為C增加 <BR>內(nèi)聯(lián)函數(shù)。)
<BR>3. __wait_event
<BR>16824:__wait_event使當(dāng)前進(jìn)程在等待隊(duì)列wq中等待,直至condition為真。
<BR>16829:通過調(diào)用add_wait_queue(16791行),局部變量__wait可以被鏈接到隊(duì)列上。注 <BR>意__wait是在堆棧中而不是在內(nèi)核堆中分配空間,這是內(nèi)核中常用的一種技巧。在宏運(yùn)行 <BR>結(jié)束之前,__wait就已經(jīng)被從等待隊(duì)列中移走了,因此等待隊(duì)列中指向它的指針總是有效 <BR>的。
<BR>16830:重復(fù)分配CPU給另一個進(jìn)程直至條件滿足,這一點(diǎn)將在下面幾節(jié)中討論。
<BR>16831:進(jìn)程被置為TASK_UNINTERRUPTIBLE狀態(tài)(16190行)。這意味著進(jìn)程處于休眠狀態(tài) <BR>,不應(yīng)被喚醒,即使是信號也不能打斷該進(jìn)程的休眠。信號在第6章中介紹,而進(jìn)程狀態(tài) <BR>則在第7章中介紹。
<BR>16832:如果條件已經(jīng)滿足,則可以退出循環(huán)。
<BR>請注意如果在第一次循環(huán)時(shí)條件就已經(jīng)滿足,那么前面一行的賦值就浪費(fèi)了(因?yàn)樵谘h(huán) <BR>結(jié)束之后進(jìn)程狀態(tài)會立刻被再次賦值)。__wait_event假定宏開始執(zhí)行時(shí)條件還沒有得到 <BR>滿足。而且,這種對進(jìn)程狀態(tài)變量state的延遲賦值也并沒有什么害處。在某些特殊情況 <BR>下,這種方法還十分有益。例如當(dāng)__wait_event開始執(zhí)行時(shí)條件為假,但是在執(zhí)行到 <BR>16832行時(shí)就為真了。這種變化只有在為有關(guān)進(jìn)程狀態(tài)的代碼計(jì)算condition變量值時(shí)才會 <BR>出現(xiàn)問題。但是在代碼中這種情況我沒有發(fā)現(xiàn)。
<BR>16834:調(diào)用schedule(26686行,在第7章中討論)將CPU轉(zhuǎn)移給另一個進(jìn)程。直到進(jìn)程再 <BR>次獲得CPU時(shí),對schedule的調(diào)用才會返回。這種情況只有當(dāng)?shù)却?duì)列中的進(jìn)程被喚醒時(shí) <BR>才會發(fā)生。
<BR>16836:進(jìn)程已經(jīng)退出了,因此條件必定已經(jīng)得到了滿足。進(jìn)程重置TASK_RUNNING的狀態(tài) <BR>(16188行),使其適合CPU運(yùn)行。
<BR>16837:通過調(diào)用remove_wait_queue(16814行)將進(jìn)程從等待隊(duì)列中移去。 <BR>wait_event_interruptible和__wait_event_interruptible(分別參見16868行和16847) <BR>基本上與wait_event和__wait_event相同,但不同的是它們允許休眠的進(jìn)程可以被信號中 <BR>斷。信號將在第6章中介紹。
<BR>請注意wait_event是被如下結(jié)構(gòu)所包含的。
<BR>和do/while(0)技巧一樣,這樣可以使被封閉起來的代碼能夠像一個單元一樣運(yùn)行。這樣 <BR>的封閉代碼就是一個獨(dú)立的表達(dá)式,而不是一個獨(dú)立的語句。也就是說,它可以求值以供 <BR>其他更復(fù)雜的表達(dá)式使用。發(fā)生這種情況的原因主要在于一些不可移植的gcc特有代碼的 <BR>存在。通過使用這類技巧,一個程序塊中的最后一個表達(dá)式的值將定義為整個程序塊的最 <BR>終值。當(dāng)在表達(dá)式中使用wait_event_interruptible時(shí),執(zhí)行宏體后賦__ret的值為宏體 <BR>的值(參見16873行)。對于有Lisp背景知識的程序員來說,這是個很常見的概念。但是 <BR>如果你僅僅了解一點(diǎn)C和其他一些相關(guān)的過程性程序設(shè)計(jì)語言,你可能就會覺得比較奇怪 <BR>。
<BR>__wake_up
<BR>26829:該函數(shù)用來喚醒等待隊(duì)列中正在休眠的進(jìn)程。它由wake_up和wake_up_ <BR>interruptible調(diào)用(請分別參見16612行和16614行)。這些宏提供mode參數(shù),只有狀態(tài) <BR>滿足mode所包含的狀態(tài)之一的進(jìn)程才可能被喚醒。
<BR>26833:正如將在第10章中詳細(xì)討論的那樣,鎖(lock)是用來限制對資源的訪問,這在 <BR>SMP邏輯單元中尤其重要,因?yàn)樵谶@種情況下當(dāng)一個CPU在修改某數(shù)據(jù)結(jié)構(gòu)時(shí),另一個CPU <BR>可能正在從該數(shù)據(jù)結(jié)構(gòu)中讀取數(shù)據(jù),或者也有可能兩個CPU同時(shí)對同一個數(shù)據(jù)結(jié)構(gòu)進(jìn)行修 <BR>改,等等。在這種情況下,受保護(hù)的資源顯然是等待隊(duì)列。非常有趣的是所有的等待隊(duì)列 <BR>都使用同一個鎖來保護(hù)。雖然這種方法要比為每一個等待隊(duì)列定義一個新鎖簡單得多,但 <BR>是這就意味著SMP邏輯單元可能經(jīng)常會發(fā)現(xiàn)自己正在等待一個實(shí)際上并不必須的鎖。
<BR>26838:本段代碼遍歷非空隊(duì)列,為隊(duì)列中正確狀態(tài)的每一個進(jìn)程調(diào)用wake_up_process( <BR>26356行)。如前所述,進(jìn)程(隊(duì)列節(jié)點(diǎn))在此可能并沒有從隊(duì)列中移走。這在很大程度 <BR>上是由于即使隊(duì)列中的進(jìn)程正在被喚醒,它仍然可能希望繼續(xù)存在于等待隊(duì)列中,這一點(diǎn) <BR>正如我們在__wait_event中發(fā)現(xiàn)的問題一樣。
<BR> <BR> <BR>-- <BR>※ 來源:·BBS 水木清華站 smth.org·[FROM: 166.111.196.22] <BR><CENTER><H1>BBS水木清華站∶精華區(qū)</H1></CENTER></BODY></HTML>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -