linux设备驱动归纳总结(六):3.中断下半部之tasklet
- 博客訪問:74424
- 博文數量:42
- 博客積分:687
- 博客等級:中校
- 關注人氣: 4
- 注冊時間:2010-12-01 18:55:26
-
tekkama
-
小雅貝貝
-
luozhiy
-
Knivo
-
embedtek
-
sillybo
-
wang2kk
-
嵌入小凱
-
send_li
-
CU官方博
-
songtao
-
lxhhust
-
fh265
1小時前 -
shenhai
10月30日 -
a275532
10月14日 -
Knivo
10月13日 -
10月13日 -
lxr215
10月10日 -
luozhiy
10月9日 -
chafe
10月8日 -
mournju
10月7日 -
zotozo
10月7日 -
high_way
10月7日 -
wangxin
10月4日
linux設備驅動歸納總結(六):3.中斷的上半部和下半部——tasklet
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、什么是下半部
中斷是一個很霸道的東西,處理器一旦接收到中斷,就會打斷正在執(zhí)行的代碼,調用中斷處理函數。如果在中斷處理函數中沒有禁止中斷,該中斷處理函數執(zhí)行過程中仍有可能被其他中斷打斷。出于這樣的原因,大家都希望中斷處理函數執(zhí)行得越快越好。
另外,中斷上下文中不能阻塞,這也限制了中斷上下文中能干的事。
基于上面的原因,內核將整個的中斷處理流程分為了上半部和下半部。上半部就是之前所說的中斷處理函數,它能最快的響應中斷,并且做一些必須在中斷響應之后馬上要做的事情。而一些需要在中斷處理函數后繼續(xù)執(zhí)行的操作,內核建議把它放在下半部執(zhí)行。
拿網卡來舉例,在linux內核中,當網卡一旦接受到數據,網卡會通過中斷告訴內核處理數據,內核會在網卡中斷處理函數(上半部)執(zhí)行一些網卡硬件的必要設置,因為這是在中斷響應后急切要干的事情。接著,內核調用對應的下半部函數來處理網卡接收到的數據,因為數據處理沒必要在中斷處理函數里面馬上執(zhí)行,可以將中斷讓出來做更緊迫的事情。
可以有三種方法來實現下半部:軟中斷、tasklet和等待隊列。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、軟中斷
軟中斷一般很少用于實現下半部,但tasklet是通過軟中斷實現的,所以先介紹軟中斷。字面理解,軟中斷就是軟件實現的異步中斷,它的優(yōu)先級比硬中斷低,但比普通進程優(yōu)先級高,同時,它和硬中斷一樣不能休眠。
軟中斷是在編譯時候靜態(tài)分配的,要用軟中斷必須修改內核代碼。
在kernel/softirq.c中有這樣的一個數組:
51?static struct softirq_action softirq_vec[NR_SOFTIRQS]?__cacheline_aligned_in_smp;
內核通過一個softirq_action數組來維護的軟中斷,NR_SOFTIRQS是當前軟中斷的個數,待會再看他在哪里定義。
先看一下softirq_action結構體:
/*include/linux/interrupt.h*/
265 struct softirq_action
266 {
267 void (*action)(struct softirq_action *); //軟中斷處理函數
268 };
一看發(fā)現,結構體里面就一個軟中斷函數,他的參數就是本身結構體的指針。之所以這樣設計,是為了以后的拓展,如果在結構體中添加了新成員,也不需要修改函數接口。在以前的內核,該結構體里面還有一個data的成員,用于傳參,不過現在沒有了。
接下來看一下如何使用軟中斷實現下半部
一、要使用軟中斷,首先就要靜態(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ā)送網絡數據的軟中斷
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, //這是我添加的,優(yōu)先級最低
259
260 NR_SOFTIRQS, //這個就是上面所說的軟中斷結構體數組成員個數
261 };
上面通過枚舉定義了NR_SOFTIRQS(10)個軟中斷的索引號,優(yōu)先級最高是0(HI_SOFTIRQ),最低是我剛添加上去的XIAOBAI_SOFTIRQ,優(yōu)先級為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);
三、在中斷處理函數返回前,觸發(fā)對應的軟中斷。
在中斷處理函數完成了必要的操作后,就應該調用函數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ū)別。它的作用只是告訴內核:下次執(zhí)行軟中斷的時候,記得執(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 }
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 <linux/module.h>
2 #include <linux/init.h>
3
4 #include <linux/interrupt.h>
5
6 #define DEBUG_SWITCH 1
7 #if DEBUG_SWITCH
8 #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)
9 #else
10 #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%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); //觸發(fā)軟中斷
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:中斷類型標記,下降沿觸發(fā)中斷
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
上面介紹,觸發(fā)軟中斷函數raise_softirq并不會讓軟中斷處理函數馬上執(zhí)行,它只是打了個標記,等到適合的時候再被實行。如在中斷處理函數返回后,內核就會檢查軟中斷是否被觸發(fā)并執(zhí)行觸發(fā)的軟中斷。
軟中斷會在do_softirq中被執(zhí)行,其中核心部分在do_softirq中調用的__do_softirq中:
/*kernel/softirq.c*/
172 asmlinkage void __do_softirq(void)
173 {
。。。。。。
194 do {
195 if (pending & 1) { //如果被觸發(fā),調用軟中斷處理函數
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); //大循環(huán)內執(zhí)行,知道所有被觸發(fā)的軟中斷都執(zhí)行完
。。。。。。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、tasklet
上面的介紹看到,軟中斷實現下半部的方法很麻煩,一般是不會使用的。一般,我們使用tasklet——利用軟中斷實現的下半部機制。
在介紹軟中斷索引號的時候,有兩個用于實現tasklet的軟中斷索引號:HI_SOFTIRQ和TASKLET_SOFTIRQ。兩個tasklet唯一的區(qū)別就是優(yōu)先級的大小,一般使用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、靜態(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
上面兩個函數都是定義一個叫name的tasklet_struct,并指定他的處理函數和傳參分別是func和data。唯一的區(qū)別是,DCLARE_TASKLET_DISABLED初始化后的處于禁止狀態(tài),暫時不能被使用。
2、動態(tài)定義并初始化
跟以往的一樣,需要先定義結構體,然后把結構體指針傳給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結構體
32 tasklet_init(&xiaobai_tasklet, xiaobai_func, (unsigned long)123);
我這里的傳參直接傳一個數值123。這操作也相當于:
DECLEAR_TASKLET(xiaobai_tasklet, xiaobai_func, (unsigned long)123);
步驟二、在中斷返回前調度tasklet:
跟軟中斷一樣(其實tasklet就是基于軟中斷實現),這里說的調度并不是馬上執(zhí)行,只是打個標記,至于什么時候執(zhí)行就要看內核的調度。
調度使用函數tasklet_schedule或者tasklet_hi_schedule,兩個的區(qū)別是一個使用TASKLET_SOFTIRQ,另一個使用HI_SOFTIRQ。這兩個函數都是一tasklet_struct指針為參數:
/*linux/interrupt.h*/
365 static inline void tasklet_schedule(struct tasklet_struct *t)
373 static inline void tasklet_hi_schedule(struct tasklet_struct *t)
在我的函數中,使用tasklet_schedule:
/*6th_irq_3/2nd/test.c*/
23 tasklet_schedule(&xiaobai_tasklet);
步驟三、當模塊卸載時,將tasklet_struct結構體移除:
/*kernel/softirq.c*/
447 void tasklet_kill(struct tasklet_struct *t)
確保了?tasklet?不會被再次調度來運行,通常當一個設備正被關閉或者模塊卸載時被調用。如果?tasklet?正在運行,?程序會休眠,等待直到它執(zhí)行完畢
另外,還有禁止與激活tasklet的函數。被禁止的tasklet不能被調用,直到被激活:
/*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 <linux/module.h>
2 #include <linux/init.h>
3
4 #include <linux/interrupt.h>
5
。。。。省略。。。。
13 struct tasklet_struct xiaobai_tasklet; //定義tasklet結構體
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) //中斷處理函數
21 {
22 printk("key down\n");
23 tasklet_schedule(&xiaobai_tasklet);
24 return IRQ_HANDLED;
25 }
26
27 static int __init test_init(void) //模塊初始化函數
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) //模塊卸載函數
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);
最后驗證一下,還是老樣子,上下半步只是打印一句話,沒有實質操作:
[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,接下來就要看看它是怎么基于軟中斷實現的
上面說明的是單處理器的情況下,如果是多處理器,每個處理器都會有一個tasklet_vec和tasklet_hi_vec鏈表,這個情況我就不介紹了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、總結
這節(jié)介紹了如何通過軟中斷(tasklet也是軟中斷的一種實現形式)機制來實現中斷下半部。使用軟中斷實現的優(yōu)缺點很明顯:
優(yōu)點:運行在軟中斷上下文,優(yōu)先級比普通進程高,調度速度快。
缺點:由于處于中斷上下文,所以不能睡眠。
也許有人會問,那軟中斷和tasklet有什么區(qū)別?
個人理解,tasklet是基于軟中斷實現的,基本上和軟中斷相同。但有一點不一樣,如果在多處理器的情況下,內核不能保證軟中斷在哪個處理器上運行(聽起來像廢話),所以,軟中斷之間需要考慮共享資源的保護。而在tasklet,內核可以保證,兩個同類型(TASKLET_SOFTIRQ和HI_SOFTIRQ)的tasklet不能同時執(zhí)行,那就說明,同類型tasklet之間,可以不考慮同類型tasklet之間的并發(fā)情況。
一般的,優(yōu)先考慮使用tasklet。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代碼:?6th_irq_3(1).rar???
分享到:新浪微博QQ空間開心網豆瓣人人網twitterfb 0?
???頂 閱讀(975)┊?評論?(1)┊收藏(0)┊舉報┊打印 前一篇:linux設備驅動歸納總結(六):2.分享中斷號 [發(fā)評論]?評論?重要提示:警惕虛假中獎信息!
- 回復?舉報 songtao0728?2011-08-09 15:10 請教一個問題:
我在按你的方面試驗軟中斷,編譯通過后,下載到開發(fā)板后,加載出現:
soft: Unknown symbol raise_softirq
soft: Unknown symbol open_softirq
insmod: cannot insert 'soft.ko':unknown symbol in module or invalid parameter
這是不是因為我的開發(fā)板的系統(tǒng)沒有這兩個函數的導出符號所導致的?
我如果不重新編譯開發(fā)板的內核的話,是不是還有別的辦法解決?
京ICP證041476號 京ICP證060528號
總結
以上是生活随笔為你收集整理的linux设备驱动归纳总结(六):3.中断下半部之tasklet的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: profit from/prove to
- 下一篇: GC0053-STM32单片机NTC热敏