linux中断的上半部和下半部
原文地址:linux中斷的上半部和下半部?作者:td1442911376
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=24690947&id=3491821
?
一、什么是下半部
?
中斷是一個很霸道的東西,處理器一旦接收到中斷,就會打斷正在執(zhí)行的代碼,調(diào)用中斷處理函數(shù)。如果在中斷處理函數(shù)中沒有禁止中斷,該中斷處理函數(shù)執(zhí)行過程中仍有可能被其他中斷打斷。出于這樣的原因,大家都希望中斷處理函數(shù)執(zhí)行得越快越好。
另外,中斷上下文中不能阻塞,這也限制了中斷上下文中能干的事。
基于上面的原因,內(nèi)核將整個的中斷處理流程分為了上半部和下半部。上半部就是之前所說的中斷處理函數(shù),它能最快的響應(yīng)中斷,并且做一些必須在中斷響應(yīng)之后馬上要做的事情。而一些需要在中斷處理函數(shù)后繼續(xù)執(zhí)行的操作,內(nèi)核建議把它放在下半部執(zhí)行。
拿網(wǎng)卡來舉例,在linux內(nèi)核中,當(dāng)網(wǎng)卡一旦接受到數(shù)據(jù),網(wǎng)卡會通過中斷告訴內(nèi)核處理數(shù)據(jù),內(nèi)核會在網(wǎng)卡中斷處理函數(shù)(上半部)執(zhí)行一些網(wǎng)卡硬件的必要設(shè)置,因為這是在中斷響應(yīng)后急切要干的事情。接著,內(nèi)核調(diào)用對應(yīng)的下半部函數(shù)來處理網(wǎng)卡接收到的數(shù)據(jù),因為數(shù)據(jù)處理沒必要在中斷處理函數(shù)里面馬上執(zhí)行,可以將中斷讓出來做更緊迫的事情。
?
可以有三種方法來實現(xiàn)下半部:軟中斷、tasklet和等待隊列。
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
二、軟中斷
?
軟中斷一般很少用于實現(xiàn)下半部,但tasklet是通過軟中斷實現(xiàn)的,所以先介紹軟中斷。字面理解,軟中斷就是軟件實現(xiàn)的異步中斷,它的優(yōu)先級比硬中斷低,但比普通進(jìn)程優(yōu)先級高,同時,它和硬中斷一樣不能休眠。
?
軟中斷是在編譯時候靜態(tài)分配的,要用軟中斷必須修改內(nèi)核代碼。
?
在kernel/softirq.c中有這樣的一個數(shù)組:
51?static struct softirq_action softirq_vec[NR_SOFTIRQS]?__cacheline_aligned_in_smp;
內(nèi)核通過一個softirq_action數(shù)組來維護(hù)的軟中斷,NR_SOFTIRQS是當(dāng)前軟中斷的個數(shù),待會再看他在哪里定義。
?
先看一下softirq_action結(jié)構(gòu)體:
/*include/linux/interrupt.h*/
265 struct softirq_action
266 {
267 void (*action)(struct softirq_action *); //軟中斷處理函數(shù)
268 };
一看發(fā)現(xiàn),結(jié)構(gòu)體里面就一個軟中斷函數(shù),他的參數(shù)就是本身結(jié)構(gòu)體的指針。之所以這樣設(shè)計,是為了以后的拓展,如果在結(jié)構(gòu)體中添加了新成員,也不需要修改函數(shù)接口。在以前的內(nèi)核,該結(jié)構(gòu)體里面還有一個data的成員,用于傳參,不過現(xiàn)在沒有了。
?
接下來看一下如何使用軟中斷實現(xiàn)下半部
一、要使用軟中斷,首先就要靜態(tài)聲明軟中斷:
/*include/linux/interrupt.h*/
246 enum
247 {
248 HI_SOFTIRQ=0, //用于tasklet的軟中斷,優(yōu)先級最高,為0
249 TIMER_SOFTIRQ, //定時器的下半部
250 NET_TX_SOFTIRQ, //發(fā)送網(wǎng)絡(luò)數(shù)據(jù)的軟中斷
251 NET_RX_SOFTIRQ, //接受網(wǎng)絡(luò)數(shù)據(jù)的軟中斷
252 BLOCK_SOFTIRQ,
253 TASKLET_SOFTIRQ, //也是用于實現(xiàn)tasklet
254 SCHED_SOFTIRQ,
255 HRTIMER_SOFTIRQ,
256 RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
257 //add by xiaobai 2011.1.18
258 XIAOBAI_SOFTIRQ, //這是我添加的,優(yōu)先級最低
259
260 NR_SOFTIRQS, //這個就是上面所說的軟中斷結(jié)構(gòu)體數(shù)組成員個數(shù)
261 };
上面通過枚舉定義了NR_SOFTIRQS(10)個軟中斷的索引號,優(yōu)先級最高是0(HI_SOFTIRQ),最低是我剛添加上去的XIAOBAI_SOFTIRQ,優(yōu)先級為9。
?
二、定義了索引號后,還要注冊處理程序。
通過函數(shù)open_sofuirq來注冊軟中斷處理函數(shù),使軟中斷索引號與中斷處理函數(shù)對應(yīng)。該函數(shù)在kernel/softirq.c中定義:
/*kernel/softirq.c */
321 void open_softirq(int nr, void (*action)(struct softirq_action *))
322 {
323 softirq_vec[nr].action = action;
324 }
其實該函數(shù)就是把軟中斷處理函數(shù)的函數(shù)指針存放到對應(yīng)的結(jié)構(gòu)體中,一般的,我們自己寫的模塊是不能調(diào)用這個函數(shù)的,為了使用這個函數(shù),我修改了內(nèi)核:
322 void open_softirq(int nr, void (*action)(struct softirq_action *))
323 {
324 softirq_vec[nr].action = action;
325 }
326 EXPORT_SYMBOL(open_softirq); //這是我添加的,導(dǎo)出符號,這樣我編寫的程序就能調(diào)用
在我的程序中如下調(diào)用:
/*6th_irq_3/1st/test.c*/
13 void xiaobai_action(struct softirq_action *t) //軟中斷處理函數(shù)
14 {
15 printk("hello xiaobai!\n");
16 }
。。。。。。。。
48 open_softirq(XIAOBAI_SOFTIRQ, xiaobai_action);
?
三、在中斷處理函數(shù)返回前,觸發(fā)對應(yīng)的軟中斷。
在中斷處理函數(shù)完成了必要的操作后,就應(yīng)該調(diào)用函數(shù)raise_sotfirq觸發(fā)軟中斷,讓軟中斷執(zhí)行中斷下半部的操作。
/*kernel/softirq.c*/
312 void raise_softirq(unsigned int nr)
313 {
314 unsigned long flags;
315
316 local_irq_save(flags);
317 raise_softirq_irqoff(nr);
318 local_irq_restore(flags);
319 }
所謂的觸發(fā)軟中斷,并不是指馬上執(zhí)行該軟中斷,不然和在中斷上執(zhí)行沒什么區(qū)別。它的作用只是告訴內(nèi)核:下次執(zhí)行軟中斷的時候,記得執(zhí)行我這個軟中斷處理函數(shù)。
當(dāng)然,這個函數(shù)也得導(dǎo)出符號后才能調(diào)用:
/*kernel/softirq.c*/
312 void raise_softirq(unsigned int nr)
313 {
314 unsigned long flags;
315
316 local_irq_save(flags);
317 raise_softirq_irqoff(nr);
318 local_irq_restore(flags);
319 }
320 EXPORT_SYMBOL(raise_softirq);
在我的程序中如下調(diào)用:
/*6th_irq_3/1st/test.c*/
18 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數(shù)
19 {
20 printk("key down\n");
21 raise_softirq(XIAOBAI_SOFTIRQ);
22 return IRQ_HANDLED;
23 }
?
經(jīng)過三步,使用軟中斷實現(xiàn)下半部就成功了,看一下完整的函數(shù):
/*6th_irq_3/1st/test.c*/
1 #include
2 #include
3
4 #include
5
6 #define DEBUG_SWITCH 1
7 #if DEBUG_SWITCH
8 #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCTI ON__, ##args)
9 #else
10 #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCTI ON__, ##args)
11 #endif
12
13 void xiaobai_action(struct softirq_action *t) //軟中斷處理函數(shù)
14 {
15 printk("hello xiaobai!\n");
16 }
17
18 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數(shù)
19 {
20 printk("key down\n");
21 raise_softirq(XIAOBAI_SOFTIRQ); //觸發(fā)軟中斷
22 return IRQ_HANDLED;
23 }
24
25 static int __init test_init(void) //模塊初始化函數(shù)
26 {
27 int ret;
28
29 /*注冊中斷處理函數(shù):
30 * IRQ_EINT1:中斷號,定義在"include/mach/irqs.h"中
31 * irq_handler:中斷處理函數(shù)
32 * IRQ_TIRGGER_FALLING:中斷類型標(biāo)記,下降沿觸發(fā)中斷
33 * ker_INT_EINT1:中斷的名字,顯示在/proc/interrupts等文件中
34 * NULL;現(xiàn)在我不使用dev_id,所以這里不傳參數(shù)
35 */
36 ret = request_irq(IRQ_EINT1, irq_handler,
37 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);
38 if(ret){
39 P_DEBUG("request irq failed!\n");
40 return ret;
41 }
42
43 /*fostirq*/
44 open_softirq(XIAOBAI_SOFTIRQ, xiaobai_action); //注冊軟中斷處理程序
45
46 printk("hello irq\n");
47 return 0;
48 }
49
50 static void __exit test_exit(void) //模塊卸載函數(shù)
51 {
52 free_irq(IRQ_EINT1, NULL);
53 printk("good bye irq\n");
54 }
55
56 module_init(test_init);
57 module_exit(test_exit);
58
59 MODULE_LICENSE("GPL");
60 MODULE_AUTHOR("xoao bai");
61 MODULE_VERSION("v0.1");
注意。在上面的程序,只是為了說明如何實現(xiàn)上下半步,而我的中斷上下半步里面的操作是毫無意義的(只是打印)。上下半步的作用我在一開始就有介紹。
接下來驗證一下:
[root: 1st]# insmod test.ko
hello irq
[root: 1st]# key down //上半部操作
hello xiaobai! //下半部操作
key down
hello xiaobai!
key down
hello xiaobai!
[root: 1st]# rmmod test
good bye irq
?
上面介紹,觸發(fā)軟中斷函數(shù)raise_softirq并不會讓軟中斷處理函數(shù)馬上執(zhí)行,它只是打了個標(biāo)記,等到適合的時候再被實行。如在中斷處理函數(shù)返回后,內(nèi)核就會檢查軟中斷是否被觸發(fā)并執(zhí)行觸發(fā)的軟中斷。
軟中斷會在do_softirq中被執(zhí)行,其中核心部分在do_softirq中調(diào)用的__do_softirq中:
/*kernel/softirq.c*/
172 asmlinkage void __do_softirq(void)
173 {
。。。。。。
194 do {
195 if (pending & 1) { //如果被觸發(fā),調(diào)用軟中斷處理函數(shù)
196 int prev_count = preempt_count();
197
198 h->action(h); //調(diào)用軟中斷處理函數(shù)
199
200 if (unlikely(prev_count != preempt_count())) {
201 printk(KERN_ERR "huh, entered softirq %td %p"
202 "with preempt_count %08x,"
203 " exited with %08x?\n", h - softirq_vec,
204 h->action, prev_count, preempt_count());
205 preempt_count() = prev_count;
206 }
207
208 rcu_bh_qsctr_inc(cpu);
209 }
210 h++; //下移,獲取另一個軟中斷
211 pending >>= 1;
212 } while (pending); //大循環(huán)內(nèi)執(zhí)行,知道所有被觸發(fā)的軟中斷都執(zhí)行完
。。。。。。
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
三、tasklet
?
上面的介紹看到,軟中斷實現(xiàn)下半部的方法很麻煩,一般是不會使用的。一般,我們使用tasklet——利用軟中斷實現(xiàn)的下半部機制。
?
在介紹軟中斷索引號的時候,有兩個用于實現(xiàn)tasklet的軟中斷索引號:HI_SOFTIRQ和TASKLET_SOFTIRQ。兩個tasklet唯一的區(qū)別就是優(yōu)先級的大小,一般使用TAKSLET_SOFTIRQ。
?
先看一下如何使用tasklet,用完之后再看內(nèi)核中是如何實現(xiàn)的:
步驟一、編寫tasklet處理函數(shù),定義并初始化結(jié)構(gòu)體tasklet_struct:
內(nèi)核中是通過tasklet_struct來維護(hù)一個tasklet,介紹一下tasklet_struct里面的兩個成員:
/*linux/interrupt.h*/
319 struct tasklet_struct
320 {
321 struct tasklet_struct *next;
322 unsigned long state;
323 atomic_t count;
324 void (*func)(unsigned long); //tasklet處理函數(shù)
325 unsigned long data; //給處理函數(shù)的傳參
326 };
?
所以,在初始化tasklet_struct之前,需要先寫好tasklet處理函數(shù),如果需要傳參,也需要指定傳參,你可以直接傳數(shù)據(jù),也可以傳地址。我定義的處理函數(shù)如下:
/*6th_irq_3/2nd/test.c*/
15 void xiaobai_func(unsigned long data)
16 {
17 printk("hello xiaobai!, data[%d]\n", (int)data); //也沒干什么事情,僅僅打印。
18 }
?
同樣,可以通過兩種辦法定義和初始化tasklet_struct。
1、靜態(tài)定義并初始化
/*linux/interrupt.h*/
328 #define?DECLARE_TASKLET(name, func, data)?\
329 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
330
331 #define?DECLARE_TASKLET_DISABLED(name, func, data)?\
332 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT
上面兩個函數(shù)都是定義一個叫name的tasklet_struct,并指定他的處理函數(shù)和傳參分別是func和data。唯一的區(qū)別是,DCLARE_TASKLET_DISABLED初始化后的處于禁止?fàn)顟B(tài),暫時不能被使用。
2、動態(tài)定義并初始化
跟以往的一樣,需要先定義結(jié)構(gòu)體,然后把結(jié)構(gòu)體指針傳給tasklet_init來動態(tài)初始化:
/*kernel/softirq.c*/
435 void tasklet_init(struct tasklet_struct *t,
436 void (*func)(unsigned long), unsigned long data)
?
在我的程序中,使用動態(tài)定義并初始化:
/*6th_irq_3/2nd/test.c*/
13 struct tasklet_struct xiaobai_tasklet; //定義tasklet結(jié)構(gòu)體
32 tasklet_init(&xiaobai_tasklet, xiaobai_func, (unsigned long)123);
我這里的傳參直接傳一個數(shù)值123。這操作也相當(dāng)于:
DECLEAR_TASKLET(xiaobai_tasklet, xiaobai_func, (unsigned long)123);
?
步驟二、在中斷返回前調(diào)度tasklet:
?
跟軟中斷一樣(其實tasklet就是基于軟中斷實現(xiàn)),這里說的調(diào)度并不是馬上執(zhí)行,只是打個標(biāo)記,至于什么時候執(zhí)行就要看內(nèi)核的調(diào)度。
調(diào)度使用函數(shù)tasklet_schedule或者tasklet_hi_schedule,兩個的區(qū)別是一個使用TASKLET_SOFTIRQ,另一個使用HI_SOFTIRQ。這兩個函數(shù)都是一tasklet_struct指針為參數(shù):
/*linux/interrupt.h*/
365 static inline void tasklet_schedule(struct tasklet_struct *t)
373 static inline void tasklet_hi_schedule(struct tasklet_struct *t)
?
在我的函數(shù)中,使用tasklet_schedule:
/*6th_irq_3/2nd/test.c*/
23 tasklet_schedule(&xiaobai_tasklet);
?
步驟三、當(dāng)模塊卸載時,將tasklet_struct結(jié)構(gòu)體移除:
/*kernel/softirq.c*/
447 void tasklet_kill(struct tasklet_struct *t)
確保了?tasklet?不會被再次調(diào)度來運行,通常當(dāng)一個設(shè)備正被關(guān)閉或者模塊卸載時被調(diào)用。如果?tasklet?正在運行,?程序會休眠,等待直到它執(zhí)行完畢
?
另外,還有禁止與激活tasklet的函數(shù)。被禁止的tasklet不能被調(diào)用,直到被激活:
/*linux/interrupt.h*/
386 static inline void tasklet_disable(struct tasklet_struct *t) //禁止
393 static inline void tasklet_enable(struct tasklet_struct *t) //激活
?
最后附上程序:
/*6th_irq_3/2nd/test.c*/
1 #include
2 #include
3
4 #include
5
。。。。省略。。。。
13 struct tasklet_struct xiaobai_tasklet; //定義tasklet結(jié)構(gòu)體
14
15 void xiaobai_func(unsigned long data)
16 {
17 printk("hello xiaobai!, data[%d]\n", (int)data);
18 }
19
20 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數(shù)
21 {
22 printk("key down\n");
23 tasklet_schedule(&xiaobai_tasklet);
24 return IRQ_HANDLED;
25 }
26
27 static int __init test_init(void) //模塊初始化函數(shù)
28 {
29 int ret;
30
31 /*tasklet*/
32 tasklet_init(&xiaobai_tasklet, xiaobai_func, (unsigned long)123);
33
41 ret = request_irq(IRQ_EINT1, irq_handler,
42 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);
43 if(ret){
44 P_DEBUG("request irq failed!\n");
45 return ret;
46 }
47
48 printk("hello irq\n");
49 return 0;
50 }
51
52 static void __exit test_exit(void) //模塊卸載函數(shù)
53 {
54 tasklet_kill(&xiaobai_tasklet);
55 free_irq(IRQ_EINT1, NULL);
56 printk("good bye irq\n");
57 }
58
59 module_init(test_init);
60 module_exit(test_exit);
?
最后驗證一下,還是老樣子,上下半步只是打印一句話,沒有實質(zhì)操作:
[root: 2nd]# insmod test.ko
hello irq
[root: 2nd]# key down //上半部操作
hello xiaobai!, data[123] //下半部操作
key down
hello xiaobai!, data[123]
[root: 2nd]# rmmod test
good bye irq
?
既然知道怎么使用tasklet,接下來就要看看它是怎么基于軟中斷實現(xiàn)的
?
上面說明的是單處理器的情況下,如果是多處理器,每個處理器都會有一個tasklet_vec和tasklet_hi_vec鏈表,這個情況我就不介紹了。
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
四、總結(jié)
?
這節(jié)介紹了如何通過軟中斷(tasklet也是軟中斷的一種實現(xiàn)形式)機制來實現(xiàn)中斷下半部。使用軟中斷實現(xiàn)的優(yōu)缺點很明顯:
優(yōu)點:運行在軟中斷上下文,優(yōu)先級比普通進(jìn)程高,調(diào)度速度快。
缺點:由于處于中斷上下文,所以不能睡眠。
?
也許有人會問,那軟中斷和tasklet有什么區(qū)別?
個人理解,tasklet是基于軟中斷實現(xiàn)的,基本上和軟中斷相同。但有一點不一樣,如果在多處理器的情況下,內(nèi)核不能保證軟中斷在哪個處理器上運行(聽起來像廢話),所以,軟中斷之間需要考慮共享資源的保護(hù)。而在tasklet,內(nèi)核可以保證,兩個同類型(TASKLET_SOFTIRQ和HI_SOFTIRQ)的tasklet不能同時執(zhí)行,那就說明,同類型tasklet之間,可以不考慮同類型tasklet之間的并發(fā)情況。
?
一般的,優(yōu)先考慮使用tasklet。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代碼:?6th_irq_3(1).rar?
?
?
1、工作隊列的使用
?
按慣例,在介紹工作隊列如何實現(xiàn)之前,先說說如何使用工作隊列實現(xiàn)下半部。
?
步驟一、定義并初始化工作隊列:
?
創(chuàng)建工作隊列函數(shù):
struct workqueue_struct *create_workqueue(const char *name)
函數(shù)傳參是內(nèi)核中工作隊列的名稱,返回值是workqueue_struct結(jié)構(gòu)體的指針,該結(jié)構(gòu)體用來維護(hù)一個等待隊列。
我的代碼如下:
/*6th_irq_3/4th/test.c*/
14 struct workqueue_struct *xiaobai_wq; //定義工作隊列
33 xiaobai_wq = create_workqueue("xiaobai");
?
步驟二、定義并初始化work結(jié)構(gòu)體:
?
內(nèi)核使用結(jié)構(gòu)體來維護(hù)一個加入工作隊列的任務(wù):
/*linux/workqueue.h*/
25 struct work_struct {
26 atomic_long_t data;
27 #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
28 #define WORK_STRUCT_FLAG_MASK (3UL)
29 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
30 struct list_head entry;
31 work_func_t func; //這個是重點,下半部實現(xiàn)的處理函數(shù)指針就放在這
32 #ifdef CONFIG_LOCKDEP
33 struct lockdep_map lockdep_map;
34 #endif
35 };
?
同樣有靜態(tài)和動態(tài)兩種方法:
靜態(tài)定義并初始化work結(jié)構(gòu)體:
/*linux/workqueue.h*/
72 #define?DECLARE_WORK(n, f)?\
73 struct work_struct n = __WORK_INITIALIZER(n, f)
定義并初始化一個叫n的work_struct數(shù)據(jù)結(jié)構(gòu),它對應(yīng)的的處理函數(shù)是f。
對應(yīng)的動態(tài)初始化方法,該函數(shù)返回work_struct指針,所以需要先定義一個work_struct結(jié)構(gòu):
/*linux/workqueue.h*/
107 #define?INIT_WORK(_work, _func)?\
108 do { \
109 (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
110 INIT_LIST_HEAD(&(_work)->entry); \
111 PREPARE_WORK((_work), (_func)); \
112 } while (0)
113 #endif
?
跟tasklet一樣,在初始化的同時,需要將處理函數(shù)實現(xiàn),代碼如下:
/*6th_irq_3/4th/test.c*/
15 struct work_struct xiaobai_work; //定義work結(jié)構(gòu)體
16
17 void xiaobai_func(struct work_struct *work) //處理函數(shù)
18 {
19 printk("hello xiaobai!\n"); //同樣什么都沒干,只是打印
20 }
34 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化work結(jié)構(gòu)體
?
步驟三、在中斷處理函數(shù)中調(diào)度任務(wù):
?
工作隊列和worl結(jié)構(gòu)體都已經(jīng)實現(xiàn)了,接下來就可以調(diào)度了,使用一下函數(shù):
/*kernel/workqueue.c*/
161 int queue_work(struct workqueue_struct *wq, struct work_struct *work)
將指定的任務(wù)(work_struct),添加到指定的工作隊列中。同樣的,調(diào)度并不代表處理函數(shù)能夠馬上執(zhí)行,這由內(nèi)核進(jìn)程調(diào)度決定。
?
步驟四、在卸載模塊時,刷新并注銷等待隊列:
?
刷新等待隊列函數(shù):
/*kernel/workqueue.c*/
411 void flush_workqueue(struct workqueue_struct *wq)
該函數(shù)會一直等待,知道指定的等待隊列中所有的任務(wù)都執(zhí)行完畢并從等待隊列中移除。
注銷等待隊列:
/*kernel/workqueue.c*/
904 void destroy_workqueue(struct workqueue_struct *wq)
該函數(shù)是是創(chuàng)建等待隊列的反操作,注銷掉指定的等待隊列。
?
四個步驟講完,貼個代碼:
/*6th_irq_3/4th/test.c*/
1 #include
2 #include
3
4 #include
5 #include
6
7 #define DEBUG_SWITCH 1
8 #if DEBUG_SWITCH
9 #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCTI ON__, ##args)
10 #else
11 #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCTI ON__, ##args)
12 #endif
13
14 struct workqueue_struct *xiaobai_wq; //1.定義工作隊列
15 struct work_struct xiaobai_work; //2定義work結(jié)構(gòu)體
16
17 void xiaobai_func(struct work_struct *work) //2實現(xiàn)處理函數(shù)
18 {
19 printk("hello xiaobai!\n");
20 }
21
22 irqreturn_t irq_handler(int irqno, void *dev_id)
23 {
24 printk("key down\n");
25 queue_work(xiaobai_wq ,&xiaobai_work); //3調(diào)度任務(wù)
26 return IRQ_HANDLED;
27 }
28 static int __init test_init(void) //模塊初始化函數(shù)
29 {
30 int ret;
31
32 /*work*/
33 xiaobai_wq = create_workqueue("xiaobai"); //1初始化工作對列
34 INIT_WORK(&xiaobai_work, xiaobai_func); //2初始化work結(jié)構(gòu)體
35
36 ret = request_irq(IRQ_EINT1, irq_handler,
37 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);
38 if(ret){
39 P_DEBUG("request irq failed!\n");
40 return ret;
41 }
42
43 printk("hello irq\n");
44 return 0;
45 }
46
47 static void __exit test_exit(void) //模塊卸載函數(shù)
48 {
49 flush_workqueue(xiaobai_wq); //4刷新工作隊列
50 destroy_workqueue(xiaobai_wq); //4注銷工作隊列
51 free_irq(IRQ_EINT1, NULL);
52 printk("good bye irq\n");
53 }
54
55 module_init(test_init);
56 module_exit(test_exit);
57
58 MODULE_LICENSE("GPL");
59 MODULE_AUTHOR("xoao bai");
60 MODULE_VERSION("v0.1");
和以往的一樣,下半部僅僅是打印,沒實質(zhì)操作,看效果:
[root: 4th]# insmod test.ko
hello irq
[root: 4th]# key down
hello xiaobai!
key down
hello xiaobai!
[root: 4th]# rmmod test
good bye irq
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
二、使用共享的工作隊列
?
在內(nèi)核中有一個默認(rèn)的工作隊列events,使用共享的工作隊列可以省去創(chuàng)建和注銷工作隊列的步驟。當(dāng)然,隊列是共享的,用起來當(dāng)然會不爽,如果有多個不同的任務(wù)都加入到這個工作對列中,每個任務(wù)調(diào)度的速度就會比較慢,肯定不如自己創(chuàng)建一個爽。不過,一般默認(rèn)工作隊列都能滿足要求,不需要創(chuàng)建一個新的。
?
少了上面創(chuàng)建和注銷等待隊列兩步,使用共享工作隊列步驟相對少一點
?
步驟一、實現(xiàn)處理函數(shù),定義并初始化work結(jié)構(gòu)體:
?
這一步驟上一節(jié)介紹的步驟二完全一樣,所以就不說了。
?
步驟二、調(diào)度任務(wù):
?
默認(rèn)工作隊列的中任務(wù)的調(diào)度不需要指定工作對列,所以函數(shù)有所不同:
/*kernel/workqueue.c*/
620 int schedule_work(struct work_struct *work)
該函數(shù)會把work_struct結(jié)構(gòu)體加入到默認(rèn)工作對列events中。
?
上函數(shù):
/*6th_irq_3/3rd/test.c*/
1 #include
2 #include
3
4 #include
5 #include
6
7 #define DEBUG_SWITCH 1
8 #if DEBUG_SWITCH
9 #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCTI ON__, ##args)
10 #else
11 #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCTI ON__, ##args)
12 #endif
13
14 struct work_struct xiaobai_work; //定義work結(jié)構(gòu)體
15
16 void xiaobai_func(struct work_struct *work)
17 {
18 printk("hello xiaobai!\n");
19 }
20
21 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函數(shù)
22 {
23 printk("key down\n");
24 schedule_work(&xiaobai_work); //調(diào)度任務(wù)
25 return IRQ_HANDLED;
26 }
27 static int __init test_init(void) //模塊初始化函數(shù)
28 {
29 int ret;
30
31 /*work*/
32 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化worl結(jié)構(gòu)體
33
34 ret = request_irq(IRQ_EINT1, irq_handler,
35 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);
36 if(ret){
37 P_DEBUG("request irq failed!\n");
38 return ret;
39 }
40
41 printk("hello irq\n");
42 return 0;
43 }
44
45 static void __exit test_exit(void) //模塊卸載函數(shù)
46 {
47 free_irq(IRQ_EINT1, NULL);
48 printk("good bye irq\n");
49 }
50
51 module_init(test_init);
52 module_exit(test_exit);
53
54 MODULE_LICENSE("GPL");
55 MODULE_AUTHOR("xoao bai");
56 MODULE_VERSION("v0.1");
再看一下實現(xiàn)效果,效果和之前的一樣:
[root: 3rd]# insmod test.ko
hello irq
[root: 3rd]# key down
hello xiaobai!
key down
hello xiaobai!
[root: 3rd]# rmmod test
good bye irq
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
三、工作隊列的實現(xiàn)
?
在介紹工作隊列的實現(xiàn)之前,必須要介紹什么是工作者線程和三個數(shù)據(jù)結(jié)構(gòu)。
?
工作者線程,是指負(fù)責(zé)執(zhí)行在內(nèi)核隊列中任務(wù)的內(nèi)核線程。在工作隊列中,有專門的工作者線程來處理加入到工作對列中的任務(wù)。工作對列對應(yīng)的工作者線程可能不止一個,每個處理器有且僅有一個工作隊列對應(yīng)的工作者線程。當(dāng)然,如果內(nèi)核中兩個工作對列,那每個處理器就分別有兩個工作者線程。
在內(nèi)核中有一個默認(rèn)的工作隊列events,對于單處理器的ARM9,有一個對應(yīng)的工作者線程。
?
工作隊列結(jié)構(gòu)體workqueue_struct:
59 struct workqueue_struct {
60 struct cpu_workqueue_struct *cpu_wq; //一個工作者線程對應(yīng)一個該結(jié)構(gòu)體
61 struct list_head list;
62 const char *name;
63 int singlethread;
64 int freezeable; /* Freeze threads during suspend */
65 int rt;
66 #ifdef CONFIG_LOCKDEP
67 struct lockdep_map lockdep_map;
68 #endif
69 };
工作對列workqueue_struct有一個成員cpu_workqueue_struct,每個工作者線程對應(yīng)一個cpu_workqueue。所以,對于單處理器的ARM9,一個工作對列只有一個cpu_workqueue_struct。
?
結(jié)構(gòu)體cpu_workqueue_struct:
41 struct cpu_workqueue_struct {
42
43 spinlock_t lock;
44 /*這是內(nèi)核鏈表,所有分配在這個處理器的work_struct將通過鏈表連在一起,等待執(zhí)行*/
45 struct list_head worklist;
46 wait_queue_head_t more_work;
47 struct work_struct *current_work; //指向當(dāng)前執(zhí)行的work_struct
48
49 struct workqueue_struct *wq; //指向關(guān)聯(lián)自己的工作隊列
50 struct task_struct *thread; //指向?qū)?yīng)的內(nèi)核線程,即工作者線程
51
52 int run_depth; /* Detect run_workqueue() recursion depth */
53 } ____cacheline_aligned;
由上面知道,當(dāng)我們調(diào)用queue_work來調(diào)度任務(wù)時,并不是把work_struct添加到等待隊列中,而是會被分配到工作對列的成員cpu_workqueue_struct中。
?
work結(jié)構(gòu)體work_struct:
25 struct work_struct {
26 atomic_long_t data;
27 #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
28 #define WORK_STRUCT_FLAG_MASK (3UL)
29 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
30 struct list_head entry; //cpu_workqueue_struct通過這個成員,將wrok_struct連在一起
31 work_func_t func; //每個任務(wù)的處理函數(shù)
32 #ifdef CONFIG_LOCKDEP
33 struct lockdep_map lockdep_map;
34 #endif
35 };
?
可能上面講得很亂,下面將來個圖來講解一下,雙處理器的情況下:
?
?
?
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
四、選擇哪個來實現(xiàn)下半部
?
在2.6內(nèi)核,提供三種實現(xiàn)中斷下半部的方法,軟中斷、tasklet和工作隊列,其中tasklet是基于軟中斷實現(xiàn)的,兩者很相近。而工作隊列完全不同,它是靠內(nèi)核線程實現(xiàn)的。
?
有這樣的一張表:
簡單來說,軟中斷和tasklet優(yōu)先級較高,性能較好,調(diào)度快,但不能睡眠。而工作隊列是內(nèi)核的進(jìn)程調(diào)度,相對來說較慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能選擇動作隊列。否則最好用tasklet。
?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
?
五、總結(jié)
?
這節(jié)簡單介紹了工作隊列的使用和實現(xiàn)。其中,還有函數(shù)能夠使指定工作隊列的任務(wù)延時執(zhí)行,相關(guān)的結(jié)構(gòu)體和函數(shù)有:
struct delayed_work
DECLARE_DELAYED_WORK(n,f)
INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);
schedule_delayed_work(struct delayed_work *work, unsigned long delay)
有興趣自己看看,很簡單。
總結(jié)
以上是生活随笔為你收集整理的linux中断的上半部和下半部的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。