在編寫裝置驅動時, tasklet 機制是一種比較常見的機制,通常用於減少中斷處理的時間,將本應該是在中斷服務程式中完成的任務轉化成非強制中斷完成。
為了最大程度的避免中斷處理時間過長而導致中斷丟失,有時候我們需要把一些在中斷處理中不是非常緊急的任務放在後面執行,而讓中斷處理常式儘快返回。在老版本的 linux 中通常將中斷處理分為 top half handler 、 bottom half handler 。利用 top half handler 處理中斷必須處理的任務,而 bottom half handler 處理不是太緊急的任務。
但是 linux2.6 以後的 linux 採取了另外一種機制,就是非強制中斷來代替 bottom half handler 的處理。而 tasklet 機制正是利用非強制中斷來完成對驅動 bottom half 的處理。 Linux2.6 中非強制中斷通常只有固定的幾種: HI_SOFTIRQ( 高優先順序的 tasklet ,一種特殊的 tasklet) 、 TIMER_SOFTIRQ (定時器)、 NET_TX_SOFTIRQ (網口發送)、 NET_RX_SOFTIRQ (網口接收) 、 BLOCK_SOFTIRQ (塊裝置)、 TASKLET_SOFTIRQ (普通 tasklet )。當然也可以通過直接修改核心自己加入自己的非強制中斷,但是一般來說這是不合理的,非強制中斷的優先順序比較高,如果不是在核心處理頻繁的任務不建議使用。通常驅動使用者使用 tasklet 足夠了。
非強制中斷和 tasklet 的關係如:
可以看出, ksoftirqd 是一個後台啟動並執行核心線程,它會周期的遍曆非強制中斷的向量列表,如果發現哪個非強制中斷向量被掛起了( pend ),就執行對應的處理函數,對於 tasklet 來說,此處理函數就是 tasklet_action ,這個處理函數在系統啟動時初始化非強制中斷的就掛接了。
Tasklet_action 函數,遍曆一個全域的 tasklet_vec 鏈表(此鏈表對於 SMP 系統是每個 CPU 都有一個),此鏈表中的元素為 tasklet_struct 。此結構如下 :
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
每個結構一個函數指標,指向你自己定義的函數。當我們要使用 tasklet ,首先新定義一個 tasklet_struct 結構,並初始化好要執行函數指標,然後將它掛接到 task_vec 鏈表中,並發一個非強制中斷就可以等著被執行了。
原理大概如此,對於 linux 驅動的作者其實不需要關心這些,關鍵是我們如何去使用 tasklet 這種機制。
Linux 中提供了如下介面:
DECLARE_TASKLET(name,function,data) :此介面初始化一個 tasklet ;其中 name 是 tasklet 的名字, function 是執行 tasklet 的函數; data 是 unsigned long 類型的 function 參數。
static inline void tasklet_schedule(struct tasklet_struct *t) :此介面將定義後的 tasklet 掛接到 cpu 的 tasklet_vec 鏈表,具體是哪個 cpu 的 tasklet_vec 鏈表,是根據當前線程是運行在哪個 cpu 來決定的。此函數不僅會掛接 tasklet ,而且會起一個軟 tasklet 的非強制中斷 , 既把 tasklet 對應的中斷向量掛起 (pend) 。
兩個工作完成後,基本上可以了, tasklet 機制並不複雜,很容易的使程式儘快的響應中斷,避免造成中斷丟失。