中斷處理是分為兩個部分:中斷處理常式是上半部,它接收到一個中斷,就立即執行,但只做有嚴格時限的工作;而另外被叫做下半部的另外一個部分主要做被允許能稍後完成的工作。這個下半部正是今天的重點。
下半部的任務就是執行與中斷處理密切相關但中斷處理常式本生身不執行的任務。最好情況當然是中斷處理常式把所有的工作都交給下半部執行,而自己啥都不做。因為我們總是希望中斷處理常式儘可能快的返回。但是,中斷處理常式註定要完成一部分工作。遺憾的是,並沒有誰嚴格規定說什麼任務應該在哪個部分中完成,換句話說,這個決定完全由想我們這樣的驅動工程師來做。記住,中斷處理常式會非同步執行,並且在最好的情況下它也會鎖定當前的中斷線,因此將中斷處理常式縮短的越小就越好。當然啦,沒有規則並不是沒有經驗和教訓:
1.如果一個任務對時間非常敏感,感覺告訴我還是將其放在中斷處理常式中執行是個好的選擇。 2.如果一個任務和硬體相關,還是將其放在中斷處理常式中執行吧。 3.如果一個任務要保證不被其他中斷(特別是相同的中斷)打斷,那就將其放在中斷處理常式中吧。 4.其他所有任務,除非你有更好的理由,否則全部丟到下半部執行。 |
總之,一句話:中斷處理常式要執行的越快越好。
我們前邊老是說下半部會在以後執行,那麼這個以後是個什麼概念呢。遺憾的說,這個只是相對於馬上而言的。下半部並需要指定一個明確的時間,只要把這個任務延遲一點,讓他們在系統不太繁忙併且中斷恢複後執行就可以了。通常下半部在中斷處理常式已返回就會馬上執行,關鍵在於當它們運行時,允許相應所有的中斷。
上半部只能通過中斷處理常式來完成,下半部的實現卻是有很多種方式。這些用來實現下半部的機制分別由不同的介面和子系統組成。最早的是“bottom half”,這種機制也被稱為“BH”,它提供的介面很簡單,提供了一個靜態建立,由32個bottom half組成的鏈表,上半部通過一個32位整數中的一位來標識出哪個bottom half可執行。每個BH都在全域範圍內進行同步,即使分屬於不同的處理器,也不允許任何兩個bottom half同時執行。這種方式使用方便但不夠靈活,簡單卻有效能瓶頸。以需要更好的方法了。第二種方法,任務隊列(task queues).核心定義了一組隊列。其中每個隊列都包含一個由等待調用的函數組成鏈表。根據其所處隊列的位置,這些函數會在某個時刻被執行,驅動程式可根據需要把它們自己的下半部註冊到合適的隊列上去。這種方法已經不錯,但仍然不夠靈活,它沒辦法代替整個BH介面。對於一些效能要求較高的子系統,像網路部分,它也不能勝任。在2.3開發版中,又引入了非強制中斷(softirqs)和tasklet,這裡的非強制中斷和實現系統調用所提到的非強制中斷(軟體中斷)不是同一個概念。如果無須考慮和過去開發的驅動程式相相容的話, 非強制中斷和tasklet可以完全代替BH介面。非強制中斷是一組靜態定義的下半部介面,有32個,可以在所有的處理器上同時執行----即使兩個類型完全相同。task是一種基於非強制中斷實現的靈活性強,動態建立的下半部實現機制。兩個不同類型的tasklet可以在不同的處理器上同時執行,但類型相同的tasklet不能同時執行。tasklet其實是一種在效能和易用性之間尋求平衡的產物。非強制中斷必須在編譯期間就進行靜態註冊,而tasklet可以通過代碼進行動態註冊。現在都是2.6核心了,我們說點新鮮的吧,linux2.6核心提供了三種不同形式的下半部實現機制:非強制中斷,tasklets和工作對列,這些會依次介紹到。這時,可能有人會想到定時器的概念,定時器也確實是這樣,但定時器提供了精確的延遲時間,我們這裡還不至於這樣,所以先放下,我們後面再說定時器。好,下面我開始說說詳細的各種機制:
1.非強制中斷 實際上非強制中斷使用的並不多,反而是後面的tasklet比較多,但tasklet是通過非強制中斷實現的,非強制中斷的代碼位於/kernel/softirq.c中。非強制中斷是在編譯期間靜態分配的,由softirq_action結構表示,它定義在linux/interrupt.h中:
| 2 |
void (*action)(struct softirq_action *); //待執行的函數 |
kernel/softirq.c中定義了一個包含有32個結構體的數組:
| 1 |
static struct softirq_action softirq_vec[32] |
每個被註冊的非強制中斷都佔據該數組的一項,因此最多可能有32個非強制中斷,這是沒法動態改變的。由於大部分驅動程式都使用tasklet來實現它們的下半部,所以現在的核心中,只用到了6個。上面的非強制中斷結構中,第一項是非強制中斷處理常式,原型如下: