tasklet是在HI_SOFTIRQ和TASKLET_SOFTIRQ兩個軟中斷的基礎上實現的(它們是在同一個源文件中實現,由此可見它們的關系密切程度),它的數據結構和軟中斷比較相似,這篇博文將分析tasklet的初始化過程。
1.和tasklet相關的數據結構
tasklet_vec和tasklet_hi_vec數組(kernel/softirq.c)
1 static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);2 static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
第1行中為所有cpu創建了名為tasklet_vec數組,每個元素和一個cpu相關聯,數組元素類型為struct tasklet_head。第2行和第1行類似,創建出的tasklet_hi_vec數組存放的是每個cpu上優先級更高的tasklet小任務。下面看下struct tasklet_head結構體(kernel/softirq.c)。
1 struct tasklet_head {2 struct tasklet_struct *head;3 struct tasklet_struct **tail;4 };
內核會為每個cpu分配一個這樣的結構體變量,從而每個cpu都擁有一個小任務函數的鏈表。再看下結構體(kernel/softirq.c)。
1 struct tasklet_struct2 {3 struct tasklet_struct *next;4 unsigned long state;5 atomic_t count;6 void (*func)(unsigned long);7 unsigned long data;8 };
第1行next指向小任務鏈表下一個節點,第6行func變量指向了小任務函數。
2.tasklet初始化
小任務是基于HI_SOFTIRQ和TASKLET_SOFTIRQ兩個軟中斷來實現的。HI_SOFTIRQ所對應的軟中斷函數是tasklet_action,如下(kernel/softirq.c):
1 static void tasklet_action(struct softirq_action *a) 2 { 3 struct tasklet_struct *list; 4 5 local_irq_disable(); 6 list = __this_cpu_read(tasklet_vec.head); 7 __this_cpu_write(tasklet_vec.head, NULL); 8 __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head); 9 local_irq_enable();10 11 while (list) {12 struct tasklet_struct *t = list;13 14 list = list->next;15 16 if (tasklet_trylock(t)) {17 if (!atomic_read(&t->count)) {18 if (!test_and_clear_bit(TASKLET_STATE_SCHED,19 &t->state))20 BUG();21 t->func(t->data);22 tasklet_unlock(t);23 continue;24 }25 tasklet_unlock(t);26 }27 28 local_irq_disable();29 t->next = NULL;30 *__this_cpu_read(tasklet_vec.tail) = t;31 __this_cpu_write(tasklet_vec.tail, &(t->next));32 __raise_softirq_irqoff(TASKLET_SOFTIRQ);33 local_irq_enable();34 }35 }
第6行中tasklet_vec數組中和本地cpu相關的元素的head域值,保存在list變量中,實際上把當前cpu的tastlet任務鏈表卸下來,list成為鏈表頭指針。第7,8行把tasklet_vec數組和本地cpu相關的元素的head域置為NULL,tail域指向head域。第11-26行用while循環執行所有的tasklet函數。第21行是tasklet函數的執行。第30-31行把執行之后的struct tasklet_struct鏈表節點重新掛到tasklet_vec數組中,由此可見裝tasklet函數的結構體不用程序員手動創建,由系統來提供。第32行重新設置軟中斷掩碼表的相應位。TASKLET_SOFTIRQ所對應的軟中斷函數是tasklet_hi_action,和tasklet_action執行過程類似,不再分析。
來看下tasklet_action函數初始化過程(kernel/softirq.c)。
1 void __init softirq_init(void) 2 { 3 int cpu; 4 5 for_each_possible_cpu(cpu) { 6 per_cpu(tasklet_vec, cpu).tail = 7 &per_cpu(tasklet_vec, cpu).head; 8 per_cpu(tasklet_hi_vec, cpu).tail = 9 &per_cpu(tasklet_hi_vec, cpu).head;10 }11 12 open_softirq(TASKLET_SOFTIRQ, tasklet_action);13 open_softirq(HI_SOFTIRQ, tasklet_hi_action);14 }
第5-10行,初始化tasklet_vec和tasklet_hi_vec兩個數組的元素,使每個元素的tail域指向head域。cpu編號作為兩個數組的下標。第12-13行將負責處理tasklet任務的兩個軟中斷函數tasklet_action和tasklet_hi_action分別存入軟中斷數組softirq_vec[NR_SOFTIRQS]的相應元素中。來看下open_softirq函數(kernel/softirq.c)。
1 void open_softirq(int nr, void (*action)(struct softirq_action *))2 {3 softirq_vec[nr].action = action;4 }
相信你一看就明白了,不用解釋了~
再看看tasklet的初始化過程(kernel/softirq.c):
1 void tasklet_init(struct tasklet_struct *t,2 void (*func)(unsigned long), unsigned long data)3 {4 t->next = NULL;5 t->state = 0;6 atomic_set(&t->count, 0);7 t->func = func;8 t->data = data;9 }
該函數接收tasklet_struct結構體指針和小任務函數指針,然后對tasklet_struct結構體進行初始化,并將函數指針賦給t->func域。tasklet_init函數在tasklet_hrtimer_init函數中被調用。
3.tasklet調度(這兩個函數分別在include/linux/interrupt.h和kernel/softirq.c文件中)
1 static inline void tasklet_schedule(struct tasklet_struct *t)2 {3 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))4 __tasklet_schedule(t);5 }
1 void __tasklet_schedule(struct tasklet_struct *t) 2 { 3 unsigned long flags; 4 5 local_irq_save(flags); 6 t->next = NULL; 7 *__this_cpu_read(tasklet_vec.tail) = t; 8 __this_cpu_write(tasklet_vec.tail, &(t->next)); 9 raise_softirq_irqoff(TASKLET_SOFTIRQ);10 local_irq_restore(flags);11 }12 EXPORT_SYMBOL(__tasklet_schedule);
在__tasklet_schedule函數第7-8行,將struct tasklet_struct類型的tasklet掛到taskeletvec數組中,然后第9行進行軟中斷掩碼置位。之后當軟中斷被執行時,tasklet函數就可以被執行了。
至此,tasklet的初始化就分析完了。
新聞熱點
疑難解答