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