linux裝置驅動歸納總結(六):3.中斷下半部之tasklet
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、什麼是下半部
中斷是一個很霸道的東西,處理器一旦接收到中斷,就會打斷正在執行的程式碼,呼叫中斷處理函式。如果在中斷處理函式中沒有禁止中斷,該中斷處理函式執行過程中仍有可能被其他中斷打斷。出於這樣的原因,大家都希望中斷處理函式執行得越快越好。
另外,中斷上下文中不能阻塞,這也限制了中斷上下文中能幹的事。
基於上面的原因,核心將整個的中斷處理流程分為了上半部和下半部。上半部就是之前所說的中斷處理函式,它能最快的響應中斷,並且做一些必須在中斷響應之後馬上要做的事情。而一些需要在中斷處理函式後繼續執行的操作,核心建議把它放在下半部執行。
拿網絡卡來舉例,在linux核心中,當網絡卡一旦接受到資料,網絡卡會通過中斷告訴核心處理資料,核心會在網絡卡中斷處理函式(上半部)執行一些網絡卡硬體的必要設定,因為這是在中斷響應後急切要乾的事情。接著,核心呼叫對應的下半部函式來處理網絡卡接收到的資料,因為資料處理沒必要在中斷處理函式裡面馬上執行,可以將中斷讓出來做更緊迫的事情。
可以有三種方法來實現下半部:軟中斷、tasklet和等待佇列。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、軟中斷
軟中斷一般很少用於實現下半部,但
軟中斷是在編譯時候靜態分配的,要用軟中斷必須修改核心程式碼。
在kernel/softirq.c中有這樣的一個數組:
51 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
核心通過一個softirq_action陣列來維護的軟中斷,NR_SOFTIRQS是當前軟中斷的個數,待會再看他在哪裡定義。
先看一下
/*include/linux/interrupt.h*/
265 struct softirq_action
266 {
267 void (*action)(struct softirq_action *); //軟中斷處理函式
268 };
一看發現,結構體裡面就一個軟中斷函式,他的引數就是本身結構體的指標。之所以這樣設計,是為了以後的拓展,如果在結構體中添加了新成員,也不需要修改函式介面。在以前的核心,該結構體裡面還有一個data的成員,用於傳參,不過現在沒有了。
接下來看一下如何使用軟中斷實現下半部
一、要使用軟中斷,首先就要靜態宣告軟中斷:
/*include/linux/interrupt.h*/
246 enum
247 {
248 HI_SOFTIRQ=0, //用於tasklet的軟中斷,優先順序最高,為0
249 TIMER_SOFTIRQ, //定時器的下半部
250 NET_TX_SOFTIRQ, //傳送網路資料的軟中斷
251 NET_RX_SOFTIRQ, //接受網路資料的軟中斷
252 BLOCK_SOFTIRQ,
253 TASKLET_SOFTIRQ, //也是用於實現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, //這是我新增的,優先順序最低
259
260 NR_SOFTIRQS, //這個就是上面所說的軟中斷結構體陣列成員個數
261 };
上面通過列舉定義了NR_SOFTIRQS(10)個軟中斷的索引號,優先順序最高是0(HI_SOFTIRQ),最低是我剛新增上去的XIAOBAI_SOFTIRQ,優先順序為9。
二、定義了索引號後,還要註冊處理程式。
通過函式open_sofuirq來註冊軟中斷處理函式,使軟中斷索引號與中斷處理函式對應。該函式在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 }
其實該函式就是把軟中斷處理函式的函式指標存放到對應的結構體中,一般的,我們自己寫的模組是不能呼叫這個函式的,為了使用這個函式,我修改了核心:
322 void open_softirq(int nr, void (*action)(struct softirq_action *))
323 {
324 softirq_vec[nr].action = action;
325 }
326 EXPORT_SYMBOL(open_softirq); //這是我新增的,匯出符號,這樣我編寫的程式就能呼叫
在我的程式中如下呼叫:
/*6th_irq_3/1st/test.c*/
13 void xiaobai_action(struct softirq_action *t) //軟中斷處理函式
14 {
15 printk("hello xiaobai!\n");
16 }
。。。。。。。。
48 open_softirq(XIAOBAI_SOFTIRQ, xiaobai_action);
三、在中斷處理函式返回前,觸發對應的軟中斷。
在中斷處理函式完成了必要的操作後,就應該呼叫函式raise_sotfirq觸發軟中斷,讓軟中斷執行中斷下半部的操作。
/*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 }
所謂的觸發軟中斷,並不是指馬上執行該軟中斷,不然和在中斷上執行沒什麼區別。它的作用只是告訴核心:下次執行軟中斷的時候,記得執行我這個軟中斷處理函式。
當然,這個函式也得匯出符號後才能呼叫:
/*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);
在我的程式中如下呼叫:
/*6th_irq_3/1st/test.c*/
18 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函式
19 {
20 printk("key down\n");
21 raise_softirq(XIAOBAI_SOFTIRQ);
22 return IRQ_HANDLED;
23 }
經過三步,使用軟中斷實現下半部就成功了,看一下完整的函式:
/*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) //軟中斷處理函式
14 {
15 printk("hello xiaobai!\n");
16 }
17
18 irqreturn_t irq_handler(int irqno, void *dev_id) //中斷處理函式
19 {
20 printk("key down\n");
21 raise_softirq(XIAOBAI_SOFTIRQ); //觸發軟中斷
22 return IRQ_HANDLED;
23 }
24
25 static int __init test_init(void) //模組初始化函式
26 {
27 int ret;
28
29 /*註冊中斷處理函式:
30 * IRQ_EINT1:中斷號,定義在"include/mach/irqs.h"中
31 * irq_handler:中斷處理函式
32 * IRQ_TIRGGER_FALLING:中斷型別標記,下降沿觸發中斷
33 * ker_INT_EINT1:中斷的名字,顯示在/proc/interrupts等檔案中
34 * NULL;現在我不使用dev_id,所以這裡不傳引數
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) //模組解除安裝函式
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");
注意。在上面的程式,只是為了說明如何實現上下半步,而我的中斷上下半步裡面的操作是毫無意義的(只是列印)。上下半步的作用我在一開始就有介紹。
接下來驗證一下:
[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
上面介紹,觸發軟中斷函式raise_softirq並不會讓軟中斷處理函式馬上執行,它只是打了個標記,等到適合的時候再被實行。如在中斷處理函式返回後,核心就會檢查軟中斷是否被觸發並執行觸發的軟中斷。
軟中斷會在do_softirq中被執行,其中核心部分在do_softirq中呼叫的__do_softirq中:
/*kernel/softirq.c*/
172 asmlinkage void __do_softirq(void)
173 {
。。。。。。
194 do {
195 if (pending & 1) { //如果被觸發,呼叫軟中斷處理函式
196 int prev_count = preempt_count();
197
198 h->action(h); //呼叫軟中斷處理函式
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); //大迴圈內執行,知道所有被觸發的軟中斷都執行完
。。。。。。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、tasklet
上面的介紹看到,軟中斷實現下半部的方法很麻煩,一般是不會使用的。一般,我們使用tasklet——利用軟中斷實現的下半部機制。
在介紹軟中斷索引號的時候,有兩個用於實現tasklet的軟中斷索引號:HI_SOFTIRQ和TASKLET_SOFTIRQ。兩個tasklet唯一的區別就是優先順序的大小,一般使用TAKSLET_SOFTIRQ。
先看一下如何使用tasklet,用完之後再看核心中是如何實現的:
步驟一、編寫tasklet處理函式,定義並初始化結構體tasklet_struct:
核心中是通過tasklet_struct來維護一個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處理函式
325 unsigned long data; //給處理函式的傳參
326 };
所以,在初始化tasklet_struct之前,需要先寫好tasklet處理函式,如果需要傳參,也需要指定傳參,你可以直接傳資料,也可以傳地址。我定義的處理函式如下:
/*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、靜態定義並初始化
/*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
上面兩個函式都是定義一個叫name的tasklet_struct,並指定他的處理函式和傳參分別是func和data。唯一的區別是,DCLARE_TASKLET_DISABLED初始化後的處於禁止狀態,暫時不能被使用。
2、動態定義並初始化
跟以往的一樣,需要先定義結構體,然