深入分析Tasklet機制一、tasklet使用
Tasklet的使用比較簡單,每個Tasklet結構體有一個函數指標,指向你自己定義的函數。當我們要使用 tasklet ,首先新定義一個tasklet_struct結構,並初始化好要執行函數指標,然後將它掛接到 task_vec 鏈表中,並觸發一個非強制中斷就可以等著被執行了。
1.1.定義tasklet_struct結構
Void my_tasklet_func(unsigned long)
DECLARE_TASKLET(my_tasklet.my_tasklet_func,data)
代碼DECLARE_TASKLET實現了定義名稱為my_tasklet的tasklet並將其與my_tasklet_func這個函數綁定,而傳入這個函數的參數為data。需要調度tasklet的時候引用一個tasklet_schedule()函數就能使系統在適當的時候進行調度,如下所示:
1.2.調度tasklet
Tasklet_schedule(&my_tasklet)
此函數將定義後的tasklet掛接到cpu的tasklet_vec鏈表,具體是哪個cpu的tasklet_vec鏈表,是根據當前線程是運行在哪個cpu來決定的。此函數不僅會掛接tasklet ,而且會引起一個軟tasklet的非強制中斷 , 即把tasklet對應的中斷向量掛起 (pend) 。
1.3.驅動模板
void xxx_do_tasklet(unsigned long);DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);void xxx_do_tasklet(unsigned long){……}irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs){ …… tasklet_schedule(&xxx_tasklet); ……}int _init xxx_init(void){ …… result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL) ……}void _exit xxx_exit(void){ …… free_irq(xxx_irq,xxx_irq_interrupt); ……}二、tasklet函數詳解2.1.tasklet_struct
tasklet對於中斷處理特別有用:硬體中斷必須儘快處理, 但大部分的資料管理可以延後到以後安全的時間執行。tasklet 以一個資料結構形式存在,使用前必須被初始化。初始化能夠通過調用一個特定函數或者通過使用某些宏定義聲明結構:
#include <linux/interrupt.h> struct tasklet_struct{ struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data;};
在這個結構體中,第一個成員代錶鏈表中的下一個tasklet。第二個變數代表此刻tasklet的狀態,一般為TASKLET_STATE_SCHED,表示此tasklet已被調度且正準備運行;此變數還可取TASKLET_STATE_RUN,表示正在運行,但只用在多處理器的情況下。count成員是一個引用計數器,只有當其值為0時候,tasklet才會被啟用;否則被禁止,不能被執行。而接下來的func變數很明顯是一個函數指標,它指向tasklet處理函數,這個處理函數的唯一參數為data。
2.2.tasklet操作函數
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);#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 }void tasklet_disable(struct tasklet_struct *t); /*函數暫時禁止給定的tasklet被tasklet_schedule調度,直到這個tasklet被再次被enable;若這個tasklet當前在運行, 這個函數忙等待直到這個tasklet退出*/void tasklet_disable_nosync(struct tasklet_struct *t); /*和tasklet_disable類似,但是tasklet可能仍然運行在另一個 CPU */void tasklet_enable(struct tasklet_struct *t); /*使能一個之前被disable的tasklet;若這個tasklet已經被調度, 它會很快運行。tasklet_enable和tasklet_disable必須匹配調用, 因為核心跟蹤每個tasklet的"禁止次數"*/ void tasklet_schedule(struct tasklet_struct *t); /*調度 tasklet 執行,如果tasklet在運行中被調度, 它在完成後會再次運行; 這保證了在其他事件被處理當中發生的事件受到應有的注意. 這個做法也允許一個 tasklet 重新調度它自己*/void tasklet_hi_schedule(struct tasklet_struct *t); /*和tasklet_schedule類似,只是在更高優先順序執行。當非強制中斷處理運行時, 它處理高優先順序 tasklet 在其他非強制中斷之前,只有具有低響應周期要求的驅動才應使用這個函數, 可避免其他軟體中斷處理引入的附加周期*/void tasklet_kill(struct tasklet_struct *t); /*確保了 tasklet 不會被再次調度來運行,通常當一個裝置正被關閉或者模組卸載時被調用。如果 tasklet 正在運行, 這個函數等待直到它執行完畢。若 tasklet 重新調度它自己,則必須阻止在調用 tasklet_kill 前它重新調度它自己,如同使用 del_timer_sync*/三、實現分析
我們就從上面這個執行個體入手來分析tasklet的實現,主要分析tasklet_schedule()函數的實現。
static inline void tasklet_schedule(struct tasklet_struct *t){ /*如果需要調度的tasklet的state不為TASKLET_STATE_SCHED,則觸發之。這樣,就保證了多個cpu不可能同時運行同一個tasklet,因為如果一個tasklet被調度過一次,那麼它的state欄位就會被設定TASKLET_STATE_SCHED標記,然後插入per-cpu變數的鏈表中。如果這時另外一個cpu也去調度該tasklet,那麼就會在下面的if語句中被擋掉,不會運行到__tasklet_schedule(),從而不會插入到另外這個cpu的per-cpu變數的鏈表中,就不會被運行到。所以這裡是保證了tasklet編寫的函數不用是可重新進入的,這樣就方便了編程人員。(注意,softirq機制需要編寫可重新進入的函數)*/ if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t);}
我們來看__tasklet_schedule()的實現:
void fastcall __tasklet_schedule(struct tasklet_struct *t){ unsigned long flags; local_irq_save(flags); /*把需要添加進系統的自己編寫的struct tasklet_struc加入到per-cpu變數tasklet_vec的本機複本的鏈表的表頭中*/ t->next = __get_cpu_var(tasklet_vec).list; __get_cpu_var(tasklet_vec).list = t; raise_softirq_irqoff(TASKLET_SOFTIRQ); /*觸發softirq的TASKLET_SOFTIRQ*/ local_irq_restore(flags);}
這段代碼也非常簡單,只是把自己要註冊到系統中的tasklet_struct掛入到per-cpu變數tasklet_vec的list中而已,這裡是掛到鏈表首部。因為需要修改per-cpu變數tasklet_vec的list的值,為了防止中斷處理常式也去修改這個值,為了保持資料的一致性,所以關閉中斷。然後通過raise_softirq_irqoff()設定低優先順序的tasklet對應的softirq標記,以便cpu在運行softirq的時候運行到tasklet,因為tasklet是淩駕在softirq機制之上的。這裡就完成了我們自己的my_tasklet的註冊和觸發對應的softirq,那我們現在就應該分析tasklet的運行了。我們前面提到,tasklet是淩駕在softirq機制之上的。Linux有六種softirq,優先順序最高的是HI_SOFTIRQ,優先順序最低的是TASKLET_SOFTIRQ,一般情況下我們是利用TASKLET_SOFTIRQ來實現tasklet的功能。在open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL)中定義了處理tasklet的處理函數tasklet_action。所以我們要分析這個函數的實現:
static void tasklet_action(struct softirq_action *a){ struct tasklet_struct *list; /*把per-cpu變數tasklet_vec的本機複本上的list設定為NULL,由於這裡要修改per-cpu變數,為了防止中斷處理常式或者核心搶佔造成該資料的不一致性,所以這裡禁止中斷再修改資料,然後再開啟中斷.(注意,關閉本地中斷的副作用就是禁止核心搶佔,因為核心搶佔只有兩個時間點: 1.中斷返回到核心態;2.手動使能核心搶佔。明顯不會在臨界區內手動使能核心搶佔,所以關閉本地中斷的副作用就是禁止核心搶佔)*/ local_irq_disable(); list = __get_cpu_var(tasklet_vec).list; __get_cpu_var(tasklet_vec).list = NULL; local_irq_enable(); /*遍曆tasklet鏈表,讓鏈表上掛入的函數全部執行完成*/ while (list) { struct tasklet_struct *t = list; list = list->next; if (tasklet_trylock(t)) { if (!atomic_read(&t->count)) { if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) BUG(); t->func(t->data); /*真正運行user註冊的tasklet函數的地方*/ tasklet_unlock(t); continue; } tasklet_unlock(t); } /*這裡相當於把tasklet的list指標從鏈表中後移了(可以自行畫圖分析),所以剛才運行過的tasklet回呼函數以後不會再次運行,除非用於再次通過tasklet_schedule()註冊之*/ local_irq_disable(); t->next = __get_cpu_var(tasklet_vec).list; __get_cpu_var(tasklet_vec).list = t; __raise_softirq_irqoff(TASKLET_SOFTIRQ); /*再一次觸發tasklet對應的softirq,使下次系統運行softirq時能運行到tasklet*/ local_irq_enable(); }}