tasklet / 工作佇列 / 軟中斷
軟中斷、tasklet和工作佇列並不是Linux核心中一直存在的機制,而是由更早版本的核心中的“下半部”(bottom half)演變而來。下半部的機制實際上包括五種,但2.6版本的核心中,下半部和任務佇列的函式都消失了,只剩下了前三者。本文重點在於介紹這三者之間的關係。
(1)上半部和下半部的區別
上半部指的是中斷處理程式,下半部則指的是一些雖然與中斷有相關性但是可以延後執行的任務。舉個例子:在網路傳輸中,網絡卡接收到資料包這個事件不一定需要馬上被處理,適合用下半部去實現;但是使用者敲擊鍵盤這樣的事件就必須馬上被響應,應該用中斷實現。
兩者的主要區別在於:中斷不能被相同型別的中斷打斷,而下半部依然可以被中斷打斷;中斷對於時間非常敏感,而下半部基本上都是一些可以延遲的工作。由於二者的這種區別,所以對於一個工作是放在上半部還是放在下半部去執行,可以參考下面四條:
a)如果一個任務對時間非常敏感,將其放在中斷處理程式中執行。
b)如果一個任務和硬體相關,將其放在中斷處理程式中執行。
c)如果一個任務要保證不被其他中斷(特別是相同的中斷)打斷,將其放在中斷處理程式中執行。
d)其他所有任務,考慮放在下半部去執行。
(2)為什麼要使用軟中斷?
軟中斷作為下半部機制的代表,是隨著SMP(share memory processor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上添加了一定的機制)。軟中斷一般是“可延遲函式”的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是因為要滿足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,而且可以在多個CPU上並行執行,使得總的系統效率可以更高。它的特性包括:
a)產生後並不是馬上可以執行,必須要等待核心的排程才能執行。軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部)。
b)可以併發執行在多個CPU上(即使同一型別的也可以)。所以軟中斷必須設計為可重入的函式(允許多個CPU同時操作),因此也需要使用自旋鎖來保護其資料結構。
(3)為什麼要使用tasklet?(tasklet和軟中斷的區別)
由於軟中斷必須使用可重入函式,這就導致設計上的複雜度變高,作為裝置驅動程式的開發者來說,增加了負擔。而如果某種應用並不需要在多個CPU上並行執行,那麼軟中斷其實是沒有必要的。因此誕生了彌補以上兩個要求的tasklet。它具有以下特性:
a)一種特定型別的tasklet只能執行在一個CPU上,不能並行,只能序列執行。
b)多個不同型別的tasklet可以並行在多個CPU上。
c)軟中斷是靜態分配的,在核心編譯好之後,就不能改變。但tasklet就靈活許多,可以在執行時改變(比如新增模組時)。
tasklet是在兩種軟中斷型別的基礎上實現的,因此如果不需要軟中斷的並行特性,tasklet就是最好的選擇。
(4)為什麼要使用工作佇列work queue?(work queue和軟中斷的區別)
上面我們介紹的可延遲函式執行在中斷上下文中(軟中斷的一個檢查點就是do_IRQ退出的時候),於是導致了一些問題:軟中斷不能睡眠、不能阻塞。由於中斷上下文出於核心態,沒有程序切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致核心會整個僵死。但可阻塞函式不能用在中斷上下文中實現,必須要執行在程序上下文中,例如訪問磁碟資料塊的函式。因此,可阻塞函式不能用軟中斷來實現。但是它們往往又具有可延遲的特性。
因此在2.6版的核心中出現了在核心態執行的工作佇列(替代了2.4核心中的任務佇列)。它也具有一些可延遲函式的特點(需要被啟用和延後執行),但是能夠能夠在不同的程序間切換,以完成不同的工作。
工作佇列類似 tasklets,允許核心程式碼請求在將來某個時間呼叫一個函式,不同在於:
(1)tasklet 在軟體中斷上下文中執行,所以 tasklet 程式碼必須是原子的; 而工作佇列函式在一個特殊核心程序上下文執行,有更多的靈活性,且能夠休眠。
(2)tasklet 只能在最初被提交的處理器上執行,而這只是工作佇列的預設工作方式。
(3)核心程式碼可以請求工作佇列函式被延後一個給定的時間間隔。
(4)tasklet 執行的很快, 短時期, 並且在原子態, 而工作佇列函式可能是長週期且不需要是原子的,兩個機制有它適合的情形。