Posted on 2008-04-09 22:52
silentneil 閱讀(180)
評(píng)論(0) 編輯 收藏 引用
1、中斷處理程序的局限:
* 中斷處理程序以異步方式執(zhí)行并且它有可能會(huì)打斷其他重要代碼的執(zhí)行。因此,它們應(yīng)該執(zhí)行得越快越好。
* 如果當(dāng)前有一個(gè)中斷處理程序正在執(zhí)行,在最好的情況下,與該中斷同級(jí)的其他中斷會(huì)被屏蔽,在最壞的情況下,所有其他中斷都會(huì)被屏蔽。因此,仍應(yīng)該讓它們執(zhí)行得越快越好。
* 由于中斷處理程序往往需要對(duì)硬件進(jìn)行操作,所以它們通常有很高的時(shí)限要求。
* 中斷處理程序不在進(jìn)程上下文中運(yùn)行,所以它們不能阻塞。
2、下半部的任務(wù)就是執(zhí)行與中斷處理密切相關(guān)但中斷處理程序本身不執(zhí)行的工作。
3、并不存在嚴(yán)格明確的規(guī)定來說明到底什么任務(wù)應(yīng)該在哪個(gè)部分中完成,如何做決定完全取決于驅(qū)動(dòng)程序開發(fā)者自己的判斷。對(duì)于在上半部和下半部之間劃分工作,盡管不存在某種嚴(yán)格的規(guī)則,但還是有一些提示可供借鑒:
* 如果一個(gè)任務(wù)對(duì)時(shí)間非常敏感,將其放在中斷處理程序中執(zhí)行。
* 如果一個(gè)任務(wù)和硬件相關(guān),將其放在中斷處理程序中執(zhí)行。
* 如果一個(gè)任務(wù)要保證不被其它中斷(特別是相同的中斷)打斷,將其放在中斷處理程序中執(zhí)行。
* 其他所有任務(wù),考慮放置在下半部執(zhí)行。
4、和上半部分只能通過中斷處理程序?qū)崿F(xiàn)不同,下半部可以通過多種機(jī)制實(shí)現(xiàn)。最早的Linux只提供"bottom half"這種機(jī)制用于實(shí)現(xiàn)下半部,這種機(jī)制也被稱為"BH"。不久,內(nèi)核開發(fā)者們就引入了任務(wù)隊(duì)列機(jī)制來實(shí)現(xiàn)工作的推后執(zhí)行,并用來代替BH機(jī)制。目前這兩種機(jī)制已經(jīng)在2.5之后的版本中被去除。在2.6版本中,內(nèi)核提供了三種不同形式的下半部實(shí)現(xiàn)機(jī)制:軟中斷、tasklet和工作隊(duì)列。其實(shí)還有另外一個(gè)可以用于將工作推后執(zhí)行的機(jī)制是內(nèi)核定時(shí)器。它可以把操作推遲到某個(gè)確定的時(shí)間段之后執(zhí)行。也就是說,當(dāng)你必須保證在一個(gè)確定的時(shí)間段過去后再運(yùn)行時(shí),你應(yīng)該使用內(nèi)核定時(shí)器。
5、軟中斷是一組靜態(tài)定義的下半部接口,有32個(gè),可以在所有處理器上同時(shí)執(zhí)行-即使兩個(gè)類型相同也可以。其適合于像網(wǎng)絡(luò)這樣對(duì)性能要求非常高的情況。此外,軟中斷必須在編譯期間就進(jìn)行靜態(tài)注冊(cè)。
6、軟中斷的實(shí)現(xiàn):軟中斷的代碼位于kernel/softirq.c文件中。其由softirq_action結(jié)構(gòu)表示,定義在< linux/interrupt.h>中。在kernel/softirq.c中定義了一個(gè)包含有32個(gè)該結(jié)構(gòu)體的數(shù)組。目前這32個(gè)項(xiàng)中只用到6 個(gè)。一個(gè)軟中斷不會(huì)搶占另外一個(gè)軟中斷。實(shí)際上,唯一可以搶占軟中斷的是中斷處理程序。一個(gè)注冊(cè)的軟中斷必須在被標(biāo)記后才會(huì)執(zhí)行。這被稱作觸發(fā)軟中斷。通常,中斷處理程序會(huì)在返回前標(biāo)記它的軟中斷,使其在稍后被執(zhí)行。在下列地方,待處理的軟中斷會(huì)被檢查和執(zhí)行:
* 在處理完一個(gè)硬件中斷以后。
* 在ksoftirqd內(nèi)核線程中。
* 在那些顯式檢查和執(zhí)行待處理的軟中斷的代碼中,如網(wǎng)絡(luò)子系統(tǒng)中。
不管用什么辦法喚起,軟中斷都要在do_softirq()中執(zhí)行。該函數(shù)很簡(jiǎn)單,如果有待處理的軟中斷,do_softirq()就會(huì)循環(huán)遍歷每一個(gè),調(diào)用它們的處理程序。
7、使用軟中斷:軟中斷保留給系統(tǒng)中對(duì)時(shí)間要求最嚴(yán)格以及最重要的下半部使用。目前只有兩個(gè)子系統(tǒng)-網(wǎng)絡(luò)和SCSI直接使用軟中斷。此外,內(nèi)核定時(shí)器和tasklets都是建立在軟中斷上的。對(duì)于時(shí)間要求嚴(yán)格并能自己高效地完成加鎖工作的應(yīng)用,軟中斷會(huì)是正確的選擇。
1)分配索引
在編譯期間,通過<linux/interrupt.h>中定義的一個(gè)枚舉類型來靜態(tài)地聲明軟中斷。內(nèi)核用這些從0開始的索引來表示一種相對(duì)優(yōu)先級(jí)。索引號(hào)小的軟中斷在索引號(hào)大的軟中斷之前執(zhí)行。建立一個(gè)新的軟中斷必須在此枚舉類型中加入新的項(xiàng)。而加入時(shí),不能像在其他地方一樣,簡(jiǎn)單地把新項(xiàng)加到列表的末尾。相反,你必須根據(jù)你希望賦予它的優(yōu)先級(jí)來決定加入的位置。
2)注冊(cè)你的處理程序
接著,在運(yùn)行時(shí)通過調(diào)用open_softirq()注冊(cè)軟中斷處理程序。軟中斷處理程序執(zhí)行的時(shí)候,允許響應(yīng)中斷,但它自己不能休眠。在一個(gè)處理程序運(yùn)行的時(shí)候,當(dāng)前處理器上的軟中斷被禁止。但其他處理器仍可以執(zhí)行別的軟中斷。實(shí)際上,如果一個(gè)軟中斷在它被執(zhí)行的同時(shí)再次被觸發(fā)了,那么另外一個(gè)處理器可以同時(shí)運(yùn)行其處理程序。這意味著任何共享數(shù)據(jù)-甚至是僅在軟中斷處理程序內(nèi)部使用的全局變量都需要嚴(yán)格的鎖保護(hù)。如果僅僅通過互斥的加鎖方式來防止它自身的并發(fā)執(zhí)行,那么使用軟中斷就沒有任何意義。因此大部分軟中斷處理程序都通過采取單處理器數(shù)據(jù)或其他一些技巧來避免顯式地加鎖,從而提供更出色的性能。
3)觸發(fā)你的軟中斷
raise_softirq()函數(shù)可以將一個(gè)軟中斷設(shè)置為掛起狀態(tài),讓它在下次調(diào)用do_softirq()函數(shù)時(shí)投入運(yùn)行。該函數(shù)在觸發(fā)一個(gè)軟中斷之前先要禁止中斷,觸發(fā)后再恢復(fù)回原來的狀態(tài)。如果中斷已經(jīng)被禁止了,那可以調(diào)用另一函數(shù)raise_softirq_irqoff(),這會(huì)帶來一些優(yōu)化效果。在中斷處理程序中觸發(fā)軟中斷是最常見的形式。在這種情況下,中斷處理程序執(zhí)行硬件設(shè)備的相關(guān)操作,然后觸發(fā)相應(yīng)的軟中斷,最后退出。內(nèi)核在執(zhí)行完中斷處理程序以后,馬上就會(huì)調(diào)用do_softirq()函數(shù)。于是軟中斷開始執(zhí)行中斷處理程序留給它去完成的剩余任務(wù)。
8、Tasklets是利用軟中斷實(shí)現(xiàn)的一種下半部機(jī)制。它和進(jìn)程沒有任何關(guān)系。Tasklets和軟中斷在本質(zhì)上很相似,行為表現(xiàn)也相近,但是它的接口更簡(jiǎn)單,鎖保護(hù)也要求較低。選擇到底是用軟中斷還是tasklets其實(shí)很簡(jiǎn)單:通常你應(yīng)該用tasklets,軟中斷的使用者屈指可數(shù)。它只在那些執(zhí)行頻率很高和連續(xù)性要求很高的情況下才需要。
9、tasklet是通過軟中斷實(shí)現(xiàn)的,所以它們本身也是軟中斷。Tasklets由tasklet_struct結(jié)構(gòu)表示。每個(gè)結(jié)構(gòu)體單獨(dú)代表一個(gè)tasklet,其定義在<linux/interrupt.h>中。已調(diào)度的tasklet(等同于被觸發(fā)的軟中斷)存放在兩個(gè)單處理器數(shù)據(jù)結(jié)構(gòu):tasklet_vec(普通tasklet)和tasklet_hi_vec(高優(yōu)先級(jí)tasklet)中。這兩個(gè)數(shù)據(jù)結(jié)構(gòu)都是由 tasklet_struct結(jié)構(gòu)體構(gòu)成的鏈表。Tasklets由tasklet_schedule()和tasklet_hi_schedule() 函數(shù)進(jìn)行調(diào)度。
10、使用Tasklets
1)聲明自己的Tasklet
既可以使用<linux/interrupt.h>中定義的兩個(gè)宏中的一個(gè)DECLARE_TASKLET或 DECLARE_TASKLET_DISABLED來靜態(tài)創(chuàng)建tasklet,前者把創(chuàng)建的tasklet的引用計(jì)數(shù)器設(shè)置為0,該tasklet處于激活狀態(tài)。另一個(gè)把引入計(jì)數(shù)器設(shè)為1,所以該tasklet處于禁止?fàn)顟B(tài)。還可以使用tasklet_init()動(dòng)態(tài)創(chuàng)建一個(gè)tasklet。
2)編寫自己的tasklet處理程序
tasklet處理程序必須符合規(guī)定的函數(shù)類型:void tasklet_handler(unsigned long data)。因?yàn)槭强寇浿袛鄬?shí)現(xiàn),所以tasklet不能睡眠。這意味著你不能在tasklet中使用信號(hào)量或其他什么阻塞式函數(shù)。如果你的 tasklet和其他的tasklet或軟中斷共享了數(shù)據(jù),你必須進(jìn)行適當(dāng)?shù)逆i保護(hù)。
3)調(diào)度自己的tasklet
通過調(diào)用tasklet_schedule()函數(shù)來調(diào)度。在tasklet被調(diào)度以后在其還沒有得到運(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()函數(shù)來禁止某個(gè)指定的tasklet,也可以調(diào)用tasklet_enable()函數(shù)激活一個(gè)tasklet。還可以調(diào)用tasklet_kill()函數(shù)從掛起的隊(duì)列中去掉一個(gè)tasklet。
11、每個(gè)處理器都有一組輔助處理軟中斷的內(nèi)核線程。當(dāng)內(nèi)核中出現(xiàn)大量軟中斷的時(shí)候,這些內(nèi)核進(jìn)程就會(huì)輔助處理它們。這些內(nèi)核線程在最低的優(yōu)先級(jí)上運(yùn)行(nice值是19),這能避免它們跟其他重要的任務(wù)搶奪資源,但它們最終肯定會(huì)被執(zhí)行。
12、工作隊(duì)列是另外一種將工作推后執(zhí)行的形式,它和我們之前討論過的所有其他形式都不相同。工作隊(duì)列可以把工作推后,交由一個(gè)內(nèi)核線程去執(zhí)行-該工作總是會(huì)在進(jìn)程上下文執(zhí)行。如果你需要用一個(gè)可以重新調(diào)度的實(shí)體來執(zhí)行你的下半部處理,你應(yīng)該使用工作隊(duì)列。它是唯一能在進(jìn)程上下文運(yùn)行的下半部實(shí)現(xiàn)的機(jī)制,也只有它才可以睡眠。這意味著在你需要獲得大量的內(nèi)存時(shí)、在你需要獲取信號(hào)量時(shí),在你需要執(zhí)行阻塞式的I/O操作時(shí),它都會(huì)非常有用。
13、工作隊(duì)列子系統(tǒng)是一個(gè)用于創(chuàng)建內(nèi)核線程的接口,通過它創(chuàng)建的進(jìn)程負(fù)責(zé)執(zhí)行由內(nèi)核其他部分排到隊(duì)列里的任務(wù)。它創(chuàng)建的這些內(nèi)核線程被稱作工作者線程。工作隊(duì)列子系統(tǒng)提供了一個(gè)缺省的工作者線程來處理需要推后的工作。不過如果需要在工作者線程中執(zhí)行大量的處理操作,也可以創(chuàng)建屬于自己的工作者線程。這么做有助于減輕缺省線程的負(fù)擔(dān),避免工作隊(duì)列中其他需要完成的工作處于饑餓狀態(tài)。
14、下半部機(jī)制的選擇:簡(jiǎn)單地說,一般的驅(qū)動(dòng)程序編寫者需要做兩個(gè)選擇。首先,你是不是需要一個(gè)可調(diào)度的實(shí)體來執(zhí)行需要推后完成的工作-你有休眠的需要嗎?要是有,工作隊(duì)列就是你的唯一選擇。否則最好用tasklet。要是必須專注于性能的提高,那么就考慮軟中斷吧。