|
------------------------------------------ 轉(zhuǎn)載請注明出處:http://lullaby2005./ ------------------------------------------ 一、為什么要進入tasklet 我們在softirq的文章中分析過,在SMP系統(tǒng)中,任何一個處理器在響應外設(shè)中斷請求,完成中斷上半部處理后,都可以調(diào)用函數(shù)do_softirq()來處理構(gòu)建在softirq機制上的下半部。也就是說,softirq處理函數(shù)在SMP系統(tǒng)中是可以并行執(zhí)行的,這要求使用softirq機制的下半部必須是多處理器可重入的。這對于一般的驅(qū)動程序開發(fā)者而言, 事情會變得復雜化、難度增大。為了降低驅(qū)動開發(fā)難度必須提供一套有效的機制,tasklet就是為了解決這一問題而出現(xiàn)的。 二、tasklet實現(xiàn)分析 1. 一個實例 #include #include #include #include #include #include #include static struct tasklet_struct my_tasklet; /*定義自己的tasklet_struct變量*/ static void tasklet_handler (unsigned long data) { printk(KERN_ALERT “tasklet_handler is running.\n”); } static int __init test_init(void) { tasklet_init(&my_tasklet, tasklet_handler, 0); /*掛入鉤子函數(shù)tasklet_handler*/ tasklet_schedule(&my_tasklet); /* 觸發(fā)softirq的TASKLET_SOFTIRQ,在下一次運行softirq時運行這個tasklet*/ return 0; } static void __exit test_exit(void) { tasklet_kill(&my_tasklet); /*禁止該tasklet的運行*/ printk(KERN_ALERT “test_exit running.\n”); } MODULE_LICENSE(“GPL”); module_init(test_init); module_exit(test_exit); 運行結(jié)果如圖: ![]() 2. 實現(xiàn)分析 我們就從上面這個實例入手來分析tasklet的實現(xiàn), 在init中,通過函數(shù)tasklet_init()來初始化自己需要注冊到系統(tǒng)中的tasklet結(jié)構(gòu): 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_struct的各個字段,掛上鉤子函數(shù)。 然后,通過函數(shù)tasklet_schedule()來觸發(fā)該tasklet static inline void tasklet_schedule(struct tasklet_struct *t) { /*如果需要調(diào)度的tasklet的state不為TASKLET_STATE_SCHED,則觸發(fā)之。這樣,就保證了多個cpu不可能同時運行同一個tasklet,因為如果一個tasklet被調(diào)度過一次,那么它的state字段就會被設(shè)置TASKLET_STATE_SCHED標記,然后插入per-cpu變量的鏈表中。如果這時另外一個cpu也去調(diào)度該tasklet,那么就會在下面的if語句中被擋掉,不會運行到__tasklet_schedule(),從而不會插入到另外這個cpu的per-cpu變量的鏈表中,就不會被運行到。所以這里是保證了tasklet編寫的函數(shù)不用是可重入的,這樣就方便了編程人員。(注意,softirq機制需要編寫可重入的函數(shù))*/ if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); } 我們來看__tasklet_schedule()的實現(xiàn): void fastcall __tasklet_schedule(struct tasklet_struct *t) { unsigned long flags; local_irq_save(flags); /*把需要添加進系統(tǒng)的自己編寫的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); /*觸發(fā)softirq的TASKLET_SOFTIRQ*/ local_irq_restore(flags); } 這段代碼也非常簡單,只是把自己要注冊到系統(tǒng)中的tasklet_struct掛入到per-cpu變量tasklet_vec的list中而已,這里是掛到鏈表首部。因為需要修改per-cpu變量tasklet_vec的list的值,為了防止中斷處理程序也去修改這個值,所以要加自旋鎖,為了保持數(shù)據(jù)的一致性。 然后通過raise_softirq_irqoff()設(shè)置低優(yōu)先級的tasklet對應的softirq標記,以便cpu在運行softirq的時候運行到tasklet,因為tasklet是凌駕在softirq機制之上的。 OK,這里就完成了我們自己的my_tasklet的注冊和觸發(fā)對應的softirq,那我們現(xiàn)在就應該分析tasklet的運行了。 我們前面提到,tasklet是凌駕在softirq機制之上的。還記得前面說到了Linux中有六種softirq,優(yōu)先級最高的是HI_SOFTIRQ,優(yōu)先級最低的是TASKLET_SOFTIRQ,一般情況下我們是利用TASKLET_SOFTIRQ來實現(xiàn)tasklet的功能。 open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);中定義了處理tasklet的處理函數(shù)tasklet_action.所以我們要分析這個函數(shù)的實現(xiàn): static void tasklet_action(struct softirq_action *a) { struct tasklet_struct *list; /*把per-cpu變量tasklet_vec的本地副本上的list設(shè)置為NULL, 由于這里要修改per-cpu變量,為了防止中斷處理程序 或者內(nèi)核搶占造成該數(shù)據(jù)的不一致性,所以這里禁止中斷再修改數(shù)據(jù) ,然后再開啟中斷.(注意,關(guān)閉本地中斷的副作用就是禁止內(nèi)核搶占, 因為內(nèi)核搶占只有兩個時間點: 1.中斷返回到內(nèi)核態(tài);2.手動使能內(nèi)核搶占。 明顯程序員不會在臨界區(qū)內(nèi)手動使能內(nèi)核搶占,所以關(guān)閉本地中斷的 副作用就是禁止內(nèi)核搶占)*/ local_irq_disable(); list = __get_cpu_var(tasklet_vec).list; __get_cpu_var(tasklet_vec).list = NULL; local_irq_enable(); /*遍歷tasklet鏈表,讓鏈表上掛入的函數(shù)全部執(zhí)行完成*/ 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函數(shù)的地方*/ tasklet_unlock(t); continue; } tasklet_unlock(t); } /*這里相當于把tasklet的list指針從鏈表中后移了(可以自行畫圖分析), 所以剛才運行過的tasklet回調(diào)函數(shù)以后不會再次運行,除非用于再次 通過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); /*再一次觸發(fā)tasklet對應的softirq,使下次系統(tǒng)運行softirq時能運行到tasklet*/ local_irq_enable(); } } 運行流程是不是很簡單呢?呵呵。只要注意到加鎖的時機就OK了! 三、總結(jié) Tasklet與一般的softirq的比較重要的一個區(qū)別在于: softirq處理函數(shù)需要被編寫成可重入的,因為多個cpu可能同時執(zhí)行同一個softirq處理函數(shù),為了防止數(shù)據(jù)出現(xiàn)不一致性,所以softirq的處理函數(shù)必須被編寫成可重入。最典型的就是要在softirq處理函數(shù)中用spinlock保護一些共享資源。而tasklet機制本身就保證了tasklet處理函數(shù)不會同時被多個cpu調(diào)度到。因為在tasklet_schedule()中,就保證了多個cpu不可能同時調(diào)度到同一個tasklet處理函數(shù),這樣tasklet就不用編寫成可重入的處理函數(shù),這樣就大大減輕了kernel編程人員的負擔。 本文來自ChinaUnix博客,如果查看原文請點:http://blog./u3/96958/showart_1959111.html |
|
|