|
linux內(nèi)核分析筆記----上半部與下半部(上) 嗨,嗨,如果您記性好的話,我在上一篇博客中提到過這樣一件事:中斷處理是分為兩個(gè)部分:中斷處理程序是上半部,它接收到一個(gè)中斷,就立即執(zhí)行,但只做有 嚴(yán)格時(shí)限的工作;而另外被叫做下半部的另外一個(gè)部分主要做被允許能稍后完成的工作。這個(gè)下半部正是今天的重點(diǎn)。 下半部的任務(wù)就是執(zhí)行與中斷處理密切相關(guān)但中斷處理程序本生身不執(zhí)行的任務(wù)。最好情況當(dāng)然是中斷處理程序把所有的工作都交給下半部執(zhí)行,而自己啥都不做。 因?yàn)槲覀兛偸窍M袛嗵幚沓绦虮M可能快的返回。但是,中斷處理程序注定要完成一部分工作。遺憾的是,并沒有誰嚴(yán)格規(guī)定說什么任務(wù)應(yīng)該在哪個(gè)部分中完成,換 句話說,這個(gè)決定完全由想我們這樣的驅(qū)動(dòng)工程師來做。記住,中斷處理程序會(huì)異步執(zhí)行,并且在最好的情況下它也會(huì)鎖定當(dāng)前的中斷線,因此將中斷處理程序縮短 的越小就越好。當(dāng)然啦,沒有規(guī)則并不是沒有經(jīng)驗(yàn)和教訓(xùn):
總之,一句話:中斷處理程序要執(zhí)行的越快越好。 我 們前邊老是說下半部會(huì)在以后執(zhí)行,那么這個(gè)以后是個(gè)什么概念呢?遺憾的說,這個(gè)只是相對(duì)于馬上而言的。下半部并需要指定一個(gè)明確的時(shí)間,只要把這個(gè)任務(wù)推 遲一點(diǎn),讓他們?cè)谙到y(tǒng)不太繁忙并且中斷恢復(fù)后執(zhí)行就可以了。通常下半部在中斷處理程序已返回就會(huì)馬上執(zhí)行,關(guān)鍵在于當(dāng)它們運(yùn)行時(shí),允許相應(yīng)所有的中斷。 上半部只能通過中斷處理程序來完成,下半部的實(shí)現(xiàn)卻是有很多種方式。這些用來實(shí)現(xiàn)下半部的機(jī)制分別由不同的接口和子系統(tǒng)組成。最早的是“bottom half”,這種機(jī)制也被稱為“BH”,它提供的接口很簡(jiǎn)單,提供了一個(gè)靜態(tài)創(chuàng)建,由32個(gè)bottom half組成的鏈表,上半部通過一個(gè)32位整數(shù)中的一位來標(biāo)識(shí)出哪個(gè)bottom half可執(zhí)行。每個(gè)BH都在全局范圍內(nèi)進(jìn)行同步,即使分屬于不同的處理器,也不允許任何兩個(gè)bottom half同時(shí)執(zhí)行。這種方式使用方便但不夠靈活,簡(jiǎn)單卻有性能瓶頸。以需要更好的方法了。第二種方法,任務(wù)隊(duì)列(task queues).內(nèi)核定義了一組隊(duì)列。其中每個(gè)隊(duì)列都包含一個(gè)由等待調(diào)用的函數(shù)組成鏈表。根據(jù)其所處隊(duì)列的位置,這些函數(shù)會(huì)在某個(gè)時(shí)刻被執(zhí)行,驅(qū)動(dòng)程序可 根據(jù)需要把它們自己的下半部注冊(cè)到合適的隊(duì)列上去。這種方法已經(jīng)不錯(cuò),但仍然不夠靈活,它沒辦法代替整個(gè)BH接口。對(duì)于一些性能要求較高的子系統(tǒng),像網(wǎng)絡(luò) 部分,它也不能勝任。在2.3開發(fā)版中,又引入了軟中斷(softirqs)和tasklet,這里的軟中斷和實(shí)現(xiàn)系統(tǒng)調(diào)用所提到的軟中斷(軟件中斷)不 是同一個(gè)概念。如果無須考慮和過去開發(fā)的驅(qū)動(dòng)程序相兼容的話, 軟中斷和tasklet可以完全代替BH接口。軟中斷是一組靜態(tài)定義的下半部接口,有32個(gè),可以在所有的處理器上同時(shí)執(zhí)行----即使兩個(gè)類型完全相 同。task是一種基于軟中斷實(shí)現(xiàn)的靈活性強(qiáng),動(dòng)態(tài)創(chuàng)建的下半部實(shí)現(xiàn)機(jī)制。兩個(gè)不同類型的tasklet可以在不同的處理器上同時(shí)執(zhí)行,但類型相同的 tasklet不能同時(shí)執(zhí)行。tasklet其實(shí)是一種在性能和易用性之間尋求平衡的產(chǎn)物。軟中斷必須在編譯期間就進(jìn)行靜態(tài)注冊(cè),而tasklet可以通 過代碼進(jìn)行動(dòng)態(tài)注冊(cè)?,F(xiàn)在都是2.6內(nèi)核了,我們說點(diǎn)新鮮的吧,linux2.6內(nèi)核提供了三種不同形式的下半部實(shí)現(xiàn)機(jī)制:軟中斷,tasklets和工 作對(duì)列,這些會(huì)依次介紹到。這時(shí),可能有人會(huì)想到定時(shí)器的概念,定時(shí)器也確實(shí)是這樣,但定時(shí)器提供了精確的推遲時(shí)間,我們這里還不至于這樣,所以先放下, 我們后面再說定時(shí)器。好,下面我開始說說詳細(xì)的各種機(jī)制: 1.軟中斷 實(shí)際上軟中斷使用的并不多,反而是后面的tasklet比較多,但tasklet是通過軟中斷實(shí)現(xiàn)的,軟中斷的代碼位于/kernel /softirq.c中。軟中斷是在編譯期間靜態(tài)分配的,由softirq_action結(jié)構(gòu)表示,它定義在linux/interrupt.h中:
kernel/softirq.c中定義了一個(gè)包含有32個(gè)結(jié)構(gòu)體的數(shù)組:
每個(gè)被注冊(cè)的軟中斷都占據(jù)該數(shù)組的一項(xiàng),因此最多可能有32個(gè)軟中斷,這是沒法動(dòng)態(tài)改變的。由于大部分驅(qū)動(dòng)程序都使用tasklet來實(shí)現(xiàn)它們的下半部,所以現(xiàn)在的內(nèi)核中,只用到了6個(gè)。上面的軟中斷結(jié)構(gòu)中,第一項(xiàng)是軟中斷處理程序,原型如下:
當(dāng)內(nèi)核運(yùn)行一個(gè)軟中斷處理程序時(shí),它會(huì)執(zhí)行這個(gè)action函數(shù),其唯一的參數(shù)為指向相應(yīng)softirq_action結(jié)構(gòu)體的指針。例如,如果my_softirq指向softirq_vec數(shù)組的實(shí)現(xiàn),那么內(nèi)核會(huì)用如下的方式調(diào)用軟中斷處理程序中的函數(shù):
一 個(gè)軟中斷不會(huì)搶占另外一個(gè)軟中斷,實(shí)際上,唯一可以搶占軟中斷的是中斷處理程序。不過,其他的軟中斷----甚至是相同類型的軟中斷-----可以在其他 類型的機(jī)器上同時(shí)執(zhí)行。一個(gè)注冊(cè)的軟中斷必須在被標(biāo)記后才能執(zhí)行----觸發(fā)軟中斷(rasing the softirq).通常,中斷處理程序會(huì)在返回前標(biāo)記它的軟中斷,使其在稍后執(zhí)行。在下列地方,待處理的軟中斷會(huì)被檢查和執(zhí)行:
軟中斷會(huì)在do_softirq()中執(zhí)行,如果有待處理的軟中斷,do_softirq會(huì)循環(huán)遍歷每一個(gè),調(diào)用他們的處理程序。核心部分如下:
上述代碼會(huì)檢查并執(zhí)行所有待處理的軟中斷,softirq_pending(),用它來獲得待處理的軟中斷的32位位圖-----如果第n位被設(shè)置為1, 那么第n位對(duì)應(yīng)類型的軟中斷等待處理,一旦待處理的軟中斷位圖被保存,就可以將實(shí)際的軟中斷位圖清零了。pending &1是判斷pending的第一位是否被置為1.一旦pending為0,就表示沒有待處理的中斷了,因?yàn)閜ending最多可能設(shè)置32位,循 環(huán)最多也只能執(zhí)行32位。軟中斷保留給系統(tǒng)中對(duì)時(shí)間要求最嚴(yán)格以及最重要的下半部使用。所以使用之前還是要想清楚的。下面簡(jiǎn)要的說明如何使用軟中斷:
在中斷處理程序中觸發(fā)軟中斷是最常見的形式,中斷處理程序執(zhí)行硬件設(shè)備的相關(guān)操作,然后觸發(fā)相應(yīng)的軟中斷,最后退出。內(nèi)核在執(zhí)行完中斷處理程序后,馬上就會(huì)調(diào)用do_softirq()函數(shù)。于是,軟中斷就開始執(zhí)行中斷處理程序留給它去完成的剩下任務(wù)。 2.Tasklets:tasklet 是通過軟中斷實(shí)現(xiàn)的,所以它們本身也是軟中斷。它由兩類軟中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ.區(qū)別在于前者會(huì)先于后者執(zhí) 行。Tasklets由tasklet_struct結(jié)構(gòu)表示,每個(gè)結(jié)構(gòu)體單獨(dú)代表一個(gè)tasklet,在linux/interrupt.h中定義:
結(jié) 構(gòu)體中func成員是tasklet的處理程序,data是它唯一的參數(shù)。state的取值只能是0,TASKLET_STATE_SCHED(表明 tasklet已經(jīng)被調(diào)度,正準(zhǔn)備投入運(yùn)行)和TASKLET_STATE_RUN(表明該tasklet正在運(yùn)行,它只有在多處理器的系統(tǒng)上才會(huì)作為一 種優(yōu)化來使用,單處理器系統(tǒng)任何時(shí)候都清楚單個(gè)tasklet是不是正在運(yùn)行)之間取值。count是tasklet的引用計(jì)數(shù)器,如果不為0,則 tasklet被禁止,不允許執(zhí)行;只有當(dāng)它為0時(shí),tasklet才被激活,并且被設(shè)置為掛起狀態(tài)時(shí),該tasklet才能夠執(zhí)行。已調(diào)度的 tasklet(等同于被觸發(fā)的軟中斷)存放在兩個(gè)單處理器數(shù)據(jù)結(jié)構(gòu):tasklet_vec(普通tasklet)和task_hi_vec高優(yōu)先級(jí)的 tasklet)中,這兩個(gè)數(shù)據(jù)結(jié)構(gòu)都是由tasklet_struct結(jié)構(gòu)體構(gòu)成的鏈表,鏈表中的每個(gè)tasklet_struct代表一個(gè)不同的 tasklet。Tasklets由tasklet_schedule()和tasklet_hi_schedule()進(jìn)行調(diào)度,它們接收一個(gè)指向 tasklet_struct結(jié)構(gòu)的指針作為參數(shù)。兩個(gè)函數(shù)非常相似(區(qū)別在于一個(gè)使用TASKLET_SOFTIRQ而另外一個(gè)使用 HI_SOFTIRQ).有關(guān)tasklet_schedule()的操作細(xì)節(jié):
那么作為tasklet處理的核心tasklet_action()和tasklet_hi_action(),具體做了些什么呢:
說了這么多,我們?cè)撛鯓邮褂眠@個(gè)tasklet呢,這個(gè)我在linux設(shè)備驅(qū)動(dòng)理論帖講的太多了。但別急,下邊為了博客完整,我仍然會(huì)大致講講: 1.聲明自己的tasklet:投其所好,既可以靜態(tài)也可以動(dòng)態(tài)創(chuàng)建,這取決于選擇是想有一個(gè)對(duì)tasklet的直接引用還是間接引用。靜態(tài)創(chuàng)建方法(直接引用),可以使用下列兩個(gè)宏的一個(gè)(在linux/interrupt.h中定義):
這兩個(gè)宏之間的區(qū)別在于引用計(jì)數(shù)器的初始值不同,前面一個(gè)把創(chuàng)建的tasklet的引用計(jì)數(shù)器設(shè)置為0,使其處于激活狀態(tài),另外一個(gè)將其設(shè)置為1,處于禁止?fàn)顟B(tài)。而動(dòng)態(tài)創(chuàng)建(間接引用)的方式如下:
2.編寫tasklet處理程序:函數(shù)類型是void tasklet_handler(unsigned long data).因?yàn)槭强寇浿袛鄬?shí)現(xiàn),所以tasklet不能休眠,也就是說不能在tasklet中使用信號(hào)量或者其他什么阻塞式的函數(shù)。由于tasklet 運(yùn)行時(shí)允許響應(yīng)中斷,所以必須做好預(yù)防工作,如果新加入的tasklet和中斷處理程序之間共享了某些數(shù)據(jù)額的話。兩個(gè)相同的tasklet絕不能同時(shí)執(zhí) 行,如果新加入的tasklet和其他的tasklet或者軟中斷共享了數(shù)據(jù),就必須要進(jìn)行適當(dāng)?shù)劓i保護(hù)。 3.調(diào)度自己的tasklet:前邊的工作一做完,接下來就剩下調(diào)度了。通過一下方法實(shí) 現(xiàn):tasklet_schedule(&my_tasklet).在tasklet被調(diào)度以后,只要有合適的機(jī)會(huì)就會(huì)得到運(yùn)行。如果在還沒有得 到運(yùn)行機(jī)會(huì)之前,如果有一個(gè)相同的tasklet又被調(diào)度了,那么它仍然只會(huì)運(yùn)行一次。如果這時(shí)已經(jīng)開始運(yùn)行,那么這個(gè)新的tasklet會(huì)被重新調(diào)度并 再次運(yùn)行。一種優(yōu)化策略是一個(gè)tasklet總在調(diào)度它的處理器上執(zhí)行。調(diào)用tasklet_disable()來禁止某個(gè)指定的tasklet,如果該 tasklet當(dāng)前正在執(zhí)行,這個(gè)函數(shù)會(huì)等到它執(zhí)行完畢再返回。調(diào)用tasklet_disable_nosync()也是來禁止的,只是不用在返回前等 待tasklet執(zhí)行完畢,這么做安全性就不咋嘀了(因?yàn)闆]法估計(jì)該tasklet是否仍在執(zhí)行)。tasklet_enable()激活一個(gè) tasklet??梢允褂胻asklet_kill()函數(shù)從掛起的對(duì)列中去掉一個(gè)tasklet。這個(gè)函數(shù)會(huì)首先等待該tasklet執(zhí)行完畢,然后再 將其移去。當(dāng)然,沒有什么可以阻止其他地方的代碼重新調(diào)度該tasklet。由于該函數(shù)可能會(huì)引起休眠,所以禁止在中斷上下文中使用它。 接下來的問題,我在前邊說過,對(duì)于軟中斷,內(nèi)核會(huì)選擇幾個(gè)特殊的實(shí)際進(jìn)行處理(常見的是中斷處理程序返回時(shí))。軟中斷被觸發(fā)的頻率有時(shí)會(huì)很好,而且還可能 會(huì)自行重復(fù)觸發(fā),這帶來的結(jié)果就是用戶空間的進(jìn)程無法獲得足夠的處理器時(shí)間,因?yàn)樘幱陴囸I狀態(tài)。同時(shí),如果單純的對(duì)重復(fù)觸發(fā)的軟中斷采取不立即處理的策略 也是無法接受的。兩種極端但完美的情況是什么樣的呢:
我現(xiàn)在想的是來個(gè)折衷方案吧,那多好,內(nèi)核開發(fā)者門還真是想到了。內(nèi)核選中的方案是不會(huì)立即處理重新觸發(fā)的軟中斷,作為改進(jìn),當(dāng)大量軟中斷出現(xiàn)的時(shí)候,內(nèi) 核會(huì)喚醒一組內(nèi)核線程來處理這些負(fù)載。這些線程在最低優(yōu)先級(jí)上運(yùn)行(nice值為19)。這種這種方案能夠保證在軟中斷負(fù)擔(dān)很重的時(shí)候用戶程序不會(huì)因?yàn)榈? 不到處理時(shí)間而處理饑餓狀態(tài)。相應(yīng)的,也能保證“過量”的軟中斷終究會(huì)得到處理。最后,在空閑系統(tǒng)上,這個(gè)方案同樣表現(xiàn)良好,軟中斷處理得非常迅速(因?yàn)? 僅存的內(nèi)存線程肯定會(huì)馬上調(diào)度)。為了保證只要有空閑的處理器,它們就會(huì)處理軟中斷,所以給每個(gè)處理器都分配一個(gè)這樣的線程。所有線程的名字都叫做 ksoftirad/n,區(qū)別在于n,它對(duì)應(yīng)的是處理器的編號(hào)。一旦該線程被初始化,它就會(huì)執(zhí)行類似下面這樣的死循環(huán):
softirq_pending()負(fù)責(zé)發(fā)現(xiàn)是否有待處理的軟中斷。當(dāng)所有需要執(zhí)行的操作都完成以后,該內(nèi)核線程將自己設(shè)置為 TASK_INTERRUPTIBLE狀態(tài),喚起調(diào)度程序選擇其他可執(zhí)行進(jìn)程投入運(yùn)行。最后,只要do_softirq()函數(shù)發(fā)現(xiàn)已經(jīng)執(zhí)行過的內(nèi)核線程 重新觸發(fā)了自己,軟中斷內(nèi)核線程就會(huì)被喚醒。 linux內(nèi)核分析筆記----上半部與下半部(下) 接著上節(jié)的來,我們?cè)谏瞎?jié)說了軟中斷和tasklet,那這最后就是工作隊(duì)列了哦.. 工作隊(duì)列和前面討論的其他形式都不相同,它可以把工作推后,交由一個(gè)內(nèi)核線程去執(zhí)行----該工作總是會(huì)在進(jìn)程上下文執(zhí)行。這樣,通過工作隊(duì)列執(zhí)行代碼能 占盡進(jìn)程上下文的所有優(yōu)勢(shì),最重要的就是工作隊(duì)列允許重新調(diào)度甚至是睡眠。相比較前邊兩個(gè),這個(gè)選擇起來就很容易了。我說過,前邊兩個(gè)是不允許休眠的,這 個(gè)是允許休眠的,這就很明白了是不?這意味著在你需要獲得大量?jī)?nèi)存的時(shí)候,在你需要獲取信號(hào)量時(shí),在你需要執(zhí)行阻塞式的I/O操作時(shí),它都會(huì)非常有用(先 說話, 這個(gè)不是我說的,是書上這么說的哦)。 工作隊(duì)列子系統(tǒng)是一個(gè)用于創(chuàng)建內(nèi)核線程的接口,通過它創(chuàng)建的進(jìn)程負(fù)責(zé)執(zhí)行由內(nèi)核其他部分排到隊(duì)列里的任務(wù)。它創(chuàng)建的這些內(nèi)核線程被稱作工作者線程 (worker threads).工作隊(duì)列可以讓你的驅(qū)動(dòng)程序創(chuàng)建一個(gè)專門的工作者線程來處理需要推后的工作。不過,工作隊(duì)列子系統(tǒng)提供了一個(gè)缺省的工作者線程來處理這 些工作。因此,工作隊(duì)列最基本的表現(xiàn)形式就轉(zhuǎn)變成一個(gè)把需要推后執(zhí)行的任務(wù)交給特定的通用線程這樣一種接口。缺省的工作線程叫做event/n.每個(gè)處理 器對(duì)應(yīng)一個(gè)線程,這里的n代表了處理器編號(hào)。除非一個(gè)驅(qū)動(dòng)程序或者子系統(tǒng)必須建立一個(gè)屬于自己的內(nèi)核線程,否則最好還是使用缺省線程。 1.工作這線程結(jié)構(gòu)用下面的結(jié)構(gòu)表示:
結(jié)構(gòu)中數(shù)組的每一項(xiàng)對(duì)應(yīng)系統(tǒng)的一個(gè)CPU.接下來,在看看在kernel/workqueue.c中的核心數(shù)據(jù)結(jié)構(gòu)cpu_workqueue_struct:
2.表示工作的數(shù)據(jù)結(jié)構(gòu):所有的工作者線程都是用普通的內(nèi)核線程來實(shí)現(xiàn)的,它們都要執(zhí)行worker_thread()函數(shù)。在它初始化完以后,這個(gè)函數(shù) 執(zhí)行一個(gè)死循環(huán)執(zhí)行一個(gè)循環(huán)并開始休眠,當(dāng)有操作被插入到隊(duì)列的時(shí)候,線程就會(huì)被喚醒,以便執(zhí)行這些操作。當(dāng)沒有剩余的時(shí)候,它又會(huì)繼續(xù)休眠。工作有 work_struct(linux/workqueue)結(jié)構(gòu)表示:
當(dāng)一個(gè)工作線程被喚醒時(shí),它會(huì)執(zhí)行它的鏈表上的所有工作。工作一旦執(zhí)行完畢,它就將相應(yīng)的work_struct對(duì)象從鏈表上移去,當(dāng)鏈表不再有對(duì)象時(shí),它就繼續(xù)休眠。woker_thread函數(shù)的核心流程如下:
分析一下上面的代碼。首先線程將自己設(shè)置 為休眠狀態(tài)并把自己加入等待隊(duì)列。如果工作對(duì)列是空的,線程調(diào)用schedule()函數(shù)進(jìn)入睡眠狀態(tài)。如果鏈表有對(duì)象,線程就將自己設(shè)為運(yùn)行態(tài),脫離等 待隊(duì)列。然后,再次調(diào)用run_workqueue()執(zhí)行推后的工作。好了,接下來,問題就糾結(jié)在run_workqueue(),它完成實(shí)際推后到此 的工作:
該函數(shù)循環(huán)遍歷鏈表上每個(gè)待處理的工作,執(zhí)行鏈表上每個(gè)結(jié)點(diǎn)上的work_struct的func成員函數(shù):
老師說的好:光說不練,不是好漢。現(xiàn)在我們繼續(xù)來看看怎么用吧: 1.首先,實(shí)際創(chuàng)建一些需要推后完成的工作,可以在編譯時(shí)靜態(tài)地創(chuàng)建該數(shù)據(jù)結(jié)構(gòu):
當(dāng)然了,如果愿意,我們當(dāng)然可以在運(yùn)行時(shí)通過指針動(dòng)態(tài)創(chuàng)建一個(gè)工作:
2.工作隊(duì)列處理函數(shù),會(huì)由一個(gè)工作者線 程執(zhí)行,因此,函數(shù)會(huì)運(yùn)行在進(jìn)程上下文中,默認(rèn)情況下,允許相應(yīng)中斷,并且不持有鎖。如果需要,函數(shù)可以睡眠。需要注意的是,盡管處理函數(shù)運(yùn)行在進(jìn)程上下 文中,但它不能訪問用戶空間,因?yàn)閮?nèi)核線程在用戶空間沒有相應(yīng)的內(nèi)存映射。函數(shù)原型如下:
3.對(duì)工作進(jìn)行調(diào)度。前面的準(zhǔn)備工作做完 以后,下面就可以開始調(diào)度了,只需調(diào)用schedule_work(&work).這樣work馬上就會(huì)被調(diào)度,一旦其所在的處理器上的工作者線 程被喚醒,它就會(huì)被執(zhí)行。當(dāng)然如果不想快速執(zhí)行,而是想延遲一段時(shí)間執(zhí)行,按就用 schedule_delay_work(&work,delay);delay是要延遲的時(shí)間節(jié)拍,后面講. 4.刷新操作。插入隊(duì)列的工作會(huì)在工作者線程下一次被喚醒的時(shí)候執(zhí)行。有時(shí),在繼續(xù)下一步工作之前,你必須保證一些操作已經(jīng)執(zhí)行完畢等等。由于這些原因, 內(nèi)核提供了一個(gè)用于刷新指定工作隊(duì)列的函數(shù):void flush_scheduled_work(void); 這個(gè)函數(shù)會(huì)一直等待,直到隊(duì)列中所有的對(duì)象都被執(zhí)行后才返回。在等待所有待處理的工作執(zhí)行的時(shí)候,該函數(shù)會(huì)進(jìn)入休眠狀態(tài),所以只能在進(jìn)程上下文中使用它。 需要說明的是,該函數(shù)并不取消任何延遲執(zhí)行的工作。取消延遲執(zhí)行的工作應(yīng)該調(diào)用:int cancel_delayed_work(struct work_struct *work);這個(gè)函數(shù)可以取消任何與work_struct 相關(guān)掛起的工作。 5.創(chuàng)建新的工作隊(duì)列。前邊說過最好使用缺省線程,可如果你堅(jiān)持要使用自己創(chuàng)建的線程,咋辦?這時(shí)你就應(yīng)該創(chuàng)建一個(gè)新的工作隊(duì)列和與之相應(yīng)的工作者線程, 方法很簡(jiǎn)單,用下面的函數(shù):struct workqueue_struct *create_workqueue(const char *name);name是新內(nèi)核線程的名字。這樣就會(huì)創(chuàng)建所有的工作者線程(系統(tǒng)中的每個(gè)處理器都有一個(gè))并且做好所有開始處理工作之前的準(zhǔn)備工作。在創(chuàng) 建之后,就調(diào)用下面的函數(shù)吧:
這兩個(gè)函數(shù)和schedule_work()和schedule_delayed_work()相近,唯一的區(qū)別在于它們可以針對(duì)特定的工作隊(duì)列而不是缺省的event隊(duì)列進(jìn)行操作。 好了,工作隊(duì)列也說完了,我還是結(jié)合前邊一篇,把這三個(gè)地板不實(shí)現(xiàn)的策略比較一下,方便以后選擇. 首先,tasklet是基于軟中斷實(shí)現(xiàn)的,兩者相近,工作隊(duì)列機(jī)制與它們完全不同,靠?jī)?nèi)核線程來實(shí)現(xiàn)。軟中斷提供的序列化的保障最少,這就要求中斷處理函 數(shù)必須格外小心地采取一些步驟確保共享數(shù)據(jù)的安全,兩個(gè)甚至更多相同類別的軟中斷有可能在不同的處理器上同時(shí)執(zhí)行。如果被考察的代碼本身多線索化的工作做 得非常好,它完全使用單處理器變量,那么軟中斷就是非常好的選擇。對(duì)于時(shí)間要求嚴(yán)格和執(zhí)行效率很高的應(yīng)用來說,它執(zhí)行的也最快。否則選擇tasklets 意義更大。tasklet接口簡(jiǎn)單,而且兩種同種類型的tasklet不能同時(shí)執(zhí)行,所以實(shí)現(xiàn)起來也會(huì)簡(jiǎn)單一些。如果需要把任務(wù)推遲到進(jìn)程上下文中完成, 那你只能選擇工作隊(duì)列了。如果不需要休眠,那軟中斷和tasklet可能更合適。另外就是工作隊(duì)列造成的開銷最大,當(dāng)然這是相對(duì)的,針對(duì)大部分情況,工作 隊(duì)列都能提供足夠的支持。從方便度上考慮就是:工作隊(duì)列,tasklets,最后才是軟中斷。我們?cè)谧鲵?qū)動(dòng)的時(shí)候,關(guān)于這三個(gè)下半部實(shí)現(xiàn),需要考慮兩點(diǎn): 首先,是不是需要一個(gè)可調(diào)度的實(shí)體來執(zhí)行需要推后完成的工作(即休眠的需要),如果有,工作隊(duì)列就是唯一的選擇,否則最好用tasklet。性能如果是最 重要的,那還是軟中斷吧。 最后,就是一些禁止下半部的相關(guān)部分了,給一個(gè)表:
這些函數(shù)有可能被嵌套使用----最后被調(diào)用的local_bh_enable()最終激活下半部。函數(shù)通過preempt_count為每個(gè)進(jìn)程維護(hù)一 個(gè)計(jì)數(shù)器。當(dāng)計(jì)數(shù)器變?yōu)?時(shí),下半部才能夠被處理。因?yàn)橄掳氩康奶幚硪呀?jīng)被禁止了,所以local_bh_enable()還需要檢查所有現(xiàn)存的待處理的 下半部并執(zhí)行它們。 好了,這一次講完了,畫了兩次,我們?cè)谶@兩次中提到了一些同時(shí)發(fā)生的問題,這時(shí)可能存在數(shù)據(jù)共享互斥訪問的問題,這個(gè)就是內(nèi)核同步方面的事情了,我們后面再慢慢說這個(gè)事。 |
|
|