tasklet是I/O驅動程序中實現可延遲函數的首選方法。從下面的內核代碼的分析中我們會看到,tasklet建立在兩個叫做HI_SOFTIRQ和TASKLET_SOFTIRQ的軟中斷之上。幾個tasklet可以與同一個軟中斷相關聯,每個tasklet執行自己的函數。tasklet和高優先級的tasklet分別存放在tasklet_vec和tasklet_hi_vec數組中。下面我們結合具體的代碼來了解他的實現和運用。
tasklet的內核實現
在start_kernel函數做內核初始化工作的時候會調用函數softirq_init
- void __init softirq_init(void)
- {
- int cpu;
-
- for_each_possible_cpu(cpu) {
- int i;
- /*對tasklet相關pcp變量的初始化*/
- per_cpu(tasklet_vec, cpu).tail =
- &per_cpu(tasklet_vec, cpu).head;
- per_cpu(tasklet_hi_vec, cpu).tail =
- &per_cpu(tasklet_hi_vec, cpu).head;
- for (i = 0; i < NR_SOFTIRQS; i++)
- INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
- }
-
- register_hotcpu_notifier(&remote_softirq_cpu_notifier);
- /*將tasklet 執行函數加入軟中斷向量中,
- 這個執行函數會執行tasklet對應一個鏈表
- 中的所有函數*/
- open_softirq(TASKLET_SOFTIRQ, tasklet_action);
- open_softirq(HI_SOFTIRQ, tasklet_hi_action);
- }
open_softirq函數在前面我們已經分析過了,在這裡可以看出,兩類tasklet以一個軟中斷的方式加入軟中斷向量中,而這兩種tasklet實際上位兩個鏈表,就是上面的tasklet_hi_vec和tasklet_vec,我們看一個就行了,實現大同小異。
- static void tasklet_action(struct softirq_action *a)
- {
- struct tasklet_struct *list;
-
- local_irq_disable();/*禁用本地中斷*/
- list = __get_cpu_var(tasklet_vec).head;/*將鏈表的地址保存*/
- __get_cpu_var(tasklet_vec).head = NULL;/*已調度的tasklet描述符的鏈表被清空*/
- __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
- local_irq_enable();/*打開本地中斷*/
-
- while (list) {/*對於list鏈表的每個tasklet描述符*/
- struct tasklet_struct *t = list;
-
- list = list->next;
-
- if (tasklet_trylock(t)) {
- /*通過查看tasklet描述符的count字段,
- 檢查tasklet是否被禁止*/
- if (!atomic_read(&t->count)) {/*如果沒有禁止*/
- /*清楚調度標志*/
- if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
- BUG();
- t->func(t->data);/*執行對應函數*/
- tasklet_unlock(t);
- continue;/*繼續下一個*/
- }
- tasklet_unlock(t);
- }
- /*運行到這裡,表示tasklet被禁止*/
- local_irq_disable();
- t->next = NULL;
- /*重新插入到鏈表中,然後再次激活軟中斷*/
- *__get_cpu_var(tasklet_vec).tail = t;
- __get_cpu_var(tasklet_vec).tail = &(t->next);
- /*這裡的激活實際上設置位掩碼pending
- 的對應位,使其在軟中斷時能夠被執行*/
- __raise_softirq_irqoff(TASKLET_SOFTIRQ);
- local_irq_enable();
- }
- }
可以看到,tasklet其實是軟中斷中兩項,每一項對應的不是一個軟中斷函數,而是一個鏈表上的所有函數,在對應的軟中斷到來時,對應鏈表中的所有函數都將得到執行。而對於tasklet的喚醒其實就是設置pending位掩碼的相應位,使軟中斷到來時會執行他。
tasklet的內核編程與應用
了解了tasklet的內核實現,對於他的應用就很簡單了,首先,你應該分配一個新的tasklet_struct數據結構,並調用tasklet_init函��初始化它。
- void tasklet_init(struct tasklet_struct *t,
- void (*func)(unsigned long), unsigned long data)
- {
- t->next = NULL;
- t->state = 0;
- atomic_set(&t->count, 0);
- t->func = func;
- t->data = data;
- }
為了重新激活你的tasklet,調用tasklet_enable函數;
為了激活tasklet,根據自己tasklet需要的優先級,調用tasklet_schedule函數或tasklet_hi_schedule函數。我們也只看一個
- static inline void tasklet_hi_schedule(struct tasklet_struct *t)
- {
- /*標志位的檢查*/
- if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
- __tasklet_hi_schedule(t);
- }
- void __tasklet_hi_schedule(struct tasklet_struct *t)
- {
- unsigned long flags;
-
- local_irq_save(flags);
- t->next = NULL;
- *__get_cpu_var(tasklet_hi_vec).tail = t;
- __get_cpu_var(tasklet_hi_vec).tail = &(t->next);
- /*激活對應softirq_vec[]數組中HI_SOFTIRQ下標
- 的軟中斷,就是tasklet_hi_vec鏈表*/
- raise_softirq_irqoff(HI_SOFTIRQ);
- local_irq_restore(flags);
- }
實現了上面的幾個操作流程,當軟中斷函數一旦被喚醒就由do_softirq函數來執行。