原理介紹:
Linux把中斷處理例程分兩部分:上半部分:實際響應中斷的例程。
下半部分:被頂部分調用,通過開中斷的方式進行。
兩種機制實現:
上半部的功能是"登記中斷",當一箇中斷髮生時,它進行相應地硬件讀寫後就把中斷例程的下半部掛到該設備的下半部執行隊列中去。因此,上半部執行的速度就會很快,可以服務更多的中斷請求。但是,僅有"登記中斷"是遠遠不夠的,因爲中斷的事件可能很複雜。因此,Linux引入了一個下半部,來完成中斷事件的絕大多數使命。
下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷的,下半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷!下半部則相對來說並不是非常緊急的,通常還是比較耗時的,因此由系統自行安排運行時機,不在中斷服務上下文中執行。
Tasklet(小任務機制)
-- 內核在BH機制的基礎上進行了擴展, 實現“軟中斷請求”(softirq)機制。利用軟中斷代替 bottom half handler 的處理。
-- tasklet 機制正是利用軟中斷來完成對驅動 bottom half 的處理。
-- tasklet會讓內核選擇某個合適的時間來執行給定的小任務。
小任務tasklet的實現
其數據結構爲struct tasklet_struct,每一個結構體代表一個獨立的小任務,在<linux/interrupt.h>定義如下
struct tasklet_struct
{
struct tasklet_struct *next;/*指向下一個鏈表結構*/
unsigned long state; /*小任務狀態*/
atomic_t count; /*引用計數器*/
void (*func)(unsigned long);/*小任務的處理函數*/
unsigned long data; /*傳遞小任務函數的參數*/
};
state的取值參照下邊的枚舉型:
enum
{
TASKLET_STATE_SCHED, /* 小任務已被調用執行*/
TASKLET_STATE_RUN /*僅在多處理器上使用*/
};
count域是小任務的引用計數器。只有當它的值爲0的時候才能被激活,並其被設置爲掛起狀態時,才能夠被執行,否則爲禁止狀態。
Tasklet
-- ksoftirqd()是一個後臺運行的內核線程,它會週期的遍歷軟中斷的向量列表,如果發現哪個軟中斷向量被掛起了( pend ),就執行對應的處理函數。
-- tasklet 所對應的處理函數就是tasklet_action,這個處理函數在系統啓動時初始化軟中斷時,就在軟中斷向量表中註冊。
-- tasklet_action() 遍歷一個全局的 tasklet_vec 鏈表。鏈表中的元素爲 tasklet_struct結構體。
一、聲明和使用小任務tasklet
靜態的創建一個小任務的宏有一下兩個:
DECLARE_TASKLET(name, func, data)DECLARE_TASKLET_DISABLED(name, func, data)
name 是 tasklet 的名字, Func 是執行 tasklet 的函數; data 是 unsigned long 類型的 function 參數。
這兩個宏的區別在於計數器設置的初始值不同,前者爲0,後者爲1。爲0的表示激活狀態,爲1的表示禁止狀態。
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }其中ATOMIC_INIT宏爲:#define ATOMIC_INIT(i) { (i) }此宏在include/asm- generic/atomic.h中定義。這樣就創建了一個名爲name的小任務,其處理函數爲func。當該函數被調用的時候,data參數就被傳遞給它。
二、小任務處理函數程序 處理函數的的形式爲:
void my_tasklet_func(unsigned long data)這樣DECLARE_TASKLET(my_tasklet, my_tasklet_func, data)實現了小任務名和處理函數的綁定,而data就是函數參數。
三、調度tasklet
調度小任務時引用tasklet_schedule(&my_tasklet)函數就能使系統在合適的時候進行調度。
函數原型爲:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
四、調度執行指定的tasklet。將定義後的 tasklet 掛接到 cpu 的 tasklet_vec 鏈表。而且會引起一個軟 tasklet 的軟中斷 , 既把 tasklet 對應的中斷向量掛起 (pend) 。
這個調度函數放在中斷處理的上半部處理函數中,這樣中斷申請的時候調用處理函數(即irq_handler_t handler)後,轉去執行下半部的小任務。
tasklet 的接口
1 void tasklet_disable(struct tasklet_struct *t);
這個函數禁止給定的 tasklet. tasklet ,但仍然可以被 tasklet_schedule 調度, 但是它的執行被延後直到這個 tasklet 被再次激活。
2 void tasklet_enable(struct tasklet_struct *t);
激活一個之前被禁止的 tasklet. 如果這個 tasklet 已經被調度, 它會很快運行.
一個對tasklet_enable 的調用必須匹配每個對 tasklet_disable 的調用, 因爲內核跟蹤每個 tasklet 的"禁止次數".
3 void tasklet_hi_schedule(struct tasklet_struct *t);
調度 tasklet 在更高優先級執行. 當軟中斷處理運行時, 它在其他軟中斷之前處理高優先級 tasklet。
4 void tasklet_kill(struct tasklet_struct *t);
這個函數確保了這個 tasklet 沒被再次調度來運行; 它常常被調用當一個設備正被關閉或者模塊卸載時. 如果這個 tasklet 被調度來運行, 這個函數等待直到它已執行.
五、示例模板:
使用tasklet作爲下半部的處理中斷的設備驅動程序模板如下:
/*定義tasklet和下半部函數並關聯*/
void my_do_tasklet(unsigned long);
DECLARE_TASKLET(my_tasklet, my_do_tasklet, 0);
/*中斷處理下半部*/
void my_do_tasklet(unsigned long)
{
……/*編寫自己的處理事件內容*/
}
/*中斷處理上半部*/
irpreturn_t my_interrupt(unsigned int irq,void *dev_id)
{
……
/*調度my_tasklet函數,根據聲明將去執行my_tasklet_func函數*/
tasklet_schedule(&my_tasklet)
……
}
/*設備驅動的加載函數*/
int __init xxx_init(void)
{
……
/*申請中斷, 轉去執行my_interrupt函數並傳入參數*/
result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);
……
}
/*設備驅動模塊的卸載函數*/
void __exit xxx_exit(void)
{
……
/*釋放中斷*/
free_irq(my_irq,my_interrupt);
……
}