linux中斷的上半部和下半部 【轉】


轉自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=24690947&id=3491821

一、什么是下半部

 

中斷是一個很霸道的東西,處理器一旦接收到中斷,就會打斷正在執行的代碼,調用中斷處理函數。如果在中斷處理函數中沒有禁止中斷,該中斷處理函數執行過程中仍有可能被其他中斷打斷。出於這樣的原因,大家都希望中斷處理函數執行得越快越好。

另外,中斷上下文中不能阻塞,這也限制了中斷上下文中能干的事。

基於上面的原因,內核將整個的中斷處理流程分為了上半部和下半部。上半部就是之前所說的中斷處理函數,它能最快的響應中斷,並且做一些必須在中斷響應之后馬上要做的事情。而一些需要在中斷處理函數后繼續執行的操作,內核建議把它放在下半部執行。

拿網卡來舉例,在linux內核中,當網卡一旦接受到數據,網卡會通過中斷告訴內核處理數據,內核會在網卡中斷處理函數(上半部)執行一些網卡硬件的必要設置,因為這是在中斷響應后急切要干的事情。接着,內核調用對應的下半部函數來處理網卡接收到的數據,因為數據處理沒必要在中斷處理函數里面馬上執行,可以將中斷讓出來做更緊迫的事情。

 

可以有三種方法來實現下半部:軟中斷、tasklet和等待隊列。

 

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

二、軟中斷

 

軟中斷一般很少用於實現下半部,但tasklet是通過軟中斷實現的,所以先介紹軟中斷。字面理解,軟中斷就是軟件實現的異步中斷,它的優先級比硬中斷低,但比普通進程優先級高,同時,它和硬中斷一樣不能休眠。

 

軟中斷是在編譯時候靜態分配的,要用軟中斷必須修改內核代碼。

 

在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 };

一看發現,結構體里面就一個軟中斷函數,他的參數就是本身結構體的指針。之所以這樣設計,是為了以后的拓展,如果在結構體中添加了新成員,也不需要修改函數接口。在以前的內核,該結構體里面還有一個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、動態定義並初始化

跟以往的一樣,需要先定義結構體,然后把結構體指針傳給tasklet_init來動態初始化:

/*kernel/softirq.c*/

435 void tasklet_init(struct tasklet_struct *t,

436 void (*func)(unsigned long), unsigned long data)

 

在我的程序中,使用動態定義並初始化:

/*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就是基於軟中斷實現),這里說的調度並不是馬上執行,只是打個標記,至於什么時候執行就要看內核的調度。

調度使用函數tasklet_schedule或者tasklet_hi_schedule,兩個的區別是一個使用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 正在運行, 程序會休眠,等待直到它執行完畢

 

另外,還有禁止與激活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

2 #include

3

4 #include

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

 

四、總結

 

這節介紹了如何通過軟中斷(tasklet也是軟中斷的一種實現形式)機制來實現中斷下半部。使用軟中斷實現的優缺點很明顯:

優點:運行在軟中斷上下文,優先級比普通進程高,調度速度快。

缺點:由於處於中斷上下文,所以不能睡眠。

 

也許有人會問,那軟中斷和tasklet有什么區別?

個人理解,tasklet是基於軟中斷實現的,基本上和軟中斷相同。但有一點不一樣,如果在多處理器的情況下,內核不能保證軟中斷在哪個處理器上運行(聽起來像廢話),所以,軟中斷之間需要考慮共享資源的保護。而在tasklet,內核可以保證,兩個同類型(TASKLET_SOFTIRQ和HI_SOFTIRQ)的tasklet不能同時執行,那就說明,同類型tasklet之間,可以不考慮同類型tasklet之間的並發情況。

 

一般的,優先考慮使用tasklet。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代碼: 6th_irq_3(1).rar 

 

 

1、工作隊列的使用

 

按慣例,在介紹工作隊列如何實現之前,先說說如何使用工作隊列實現下半部。

 

步驟一、定義並初始化工作隊列:

 

創建工作隊列函數:

struct workqueue_struct *create_workqueue(const char *name)

函數傳參是內核中工作隊列的名稱,返回值是workqueue_struct結構體的指針,該結構體用來維護一個等待隊列。

我的代碼如下:

/*6th_irq_3/4th/test.c*/

14 struct workqueue_struct *xiaobai_wq; //定義工作隊列

33 xiaobai_wq = create_workqueue("xiaobai");

 

步驟二、定義並初始化work結構體:

 

內核使用結構體來維護一個加入工作隊列的任務:

/*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; //這個是重點,下半部實現的處理函數指針就放在這

32 #ifdef CONFIG_LOCKDEP

33 struct lockdep_map lockdep_map;

34 #endif

35 };

 

同樣有靜態和動態兩種方法:

靜態定義並初始化work結構體:

/*linux/workqueue.h*/

72 #define DECLARE_WORK(n, f) \

73 struct work_struct n = __WORK_INITIALIZER(n, f)

定義並初始化一個叫n的work_struct數據結構,它對應的的處理函數是f。

對應的動態初始化方法,該函數返回work_struct指針,所以需要先定義一個work_struct結構:

/*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一樣,在初始化的同時,需要將處理函數實現,代碼如下:

/*6th_irq_3/4th/test.c*/

15 struct work_struct xiaobai_work; //定義work結構體

16

17 void xiaobai_func(struct work_struct *work) //處理函數

18 {

19 printk("hello xiaobai!\n"); //同樣什么都沒干,只是打印

20 }

34 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化work結構體

 

步驟三、在中斷處理函數中調度任務:

 

工作隊列和worl結構體都已經實現了,接下來就可以調度了,使用一下函數:

/*kernel/workqueue.c*/

161 int queue_work(struct workqueue_struct *wq, struct work_struct *work)

將指定的任務(work_struct),添加到指定的工作隊列中。同樣的,調度並不代表處理函數能夠馬上執行,這由內核進程調度決定。

 

步驟四、在卸載模塊時,刷新並注銷等待隊列:

 

刷新等待隊列函數:

/*kernel/workqueue.c*/

411 void flush_workqueue(struct workqueue_struct *wq)

該函數會一直等待,知道指定的等待隊列中所有的任務都執行完畢並從等待隊列中移除。

注銷等待隊列:

/*kernel/workqueue.c*/

904 void destroy_workqueue(struct workqueue_struct *wq)

該函數是是創建等待隊列的反操作,注銷掉指定的等待隊列。

 

四個步驟講完,貼個代碼:

/*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結構體

16

17 void xiaobai_func(struct work_struct *work) //2實現處理函數

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調度任務

26 return IRQ_HANDLED;

27 }

28 static int __init test_init(void) //模塊初始化函數

29 {

30 int ret;

31

32 /*work*/

33 xiaobai_wq = create_workqueue("xiaobai"); //1初始化工作對列

34 INIT_WORK(&xiaobai_work, xiaobai_func); //2初始化work結構體

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) //模塊卸載函數

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");

和以往的一樣,下半部僅僅是打印,沒實質操作,看效果:

[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

 

二、使用共享的工作隊列

 

在內核中有一個默認的工作隊列events,使用共享的工作隊列可以省去創建和注銷工作隊列的步驟。當然,隊列是共享的,用起來當然會不爽,如果有多個不同的任務都加入到這個工作對列中,每個任務調度的速度就會比較慢,肯定不如自己創建一個爽。不過,一般默認工作隊列都能滿足要求,不需要創建一個新的。

 

少了上面創建和注銷等待隊列兩步,使用共享工作隊列步驟相對少一點

 

步驟一、實現處理函數,定義並初始化work結構體:

 

這一步驟上一節介紹的步驟二完全一樣,所以就不說了。

 

步驟二、調度任務:

 

默認工作隊列的中任務的調度不需要指定工作對列,所以函數有所不同:

/*kernel/workqueue.c*/

620 int schedule_work(struct work_struct *work)

該函數會把work_struct結構體加入到默認工作對列events中。

 

上函數:

/*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結構體

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) //中斷處理函數

22 {

23 printk("key down\n");

24 schedule_work(&xiaobai_work); //調度任務

25 return IRQ_HANDLED;

26 }

27 static int __init test_init(void) //模塊初始化函數

28 {

29 int ret;

30

31 /*work*/

32 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化worl結構體

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) //模塊卸載函數

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");

再看一下實現效果,效果和之前的一樣:

[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

 

三、工作隊列的實現

 

在介紹工作隊列的實現之前,必須要介紹什么是工作者線程和三個數據結構。

 

工作者線程,是指負責執行在內核隊列中任務的內核線程。在工作隊列中,有專門的工作者線程來處理加入到工作對列中的任務。工作對列對應的工作者線程可能不止一個,每個處理器有且僅有一個工作隊列對應的工作者線程。當然,如果內核中兩個工作對列,那每個處理器就分別有兩個工作者線程。

在內核中有一個默認的工作隊列events,對於單處理器的ARM9,有一個對應的工作者線程。

 

工作隊列結構體workqueue_struct:

59 struct workqueue_struct {

60 struct cpu_workqueue_struct *cpu_wq; //一個工作者線程對應一個該結構體

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,每個工作者線程對應一個cpu_workqueue。所以,對於單處理器的ARM9,一個工作對列只有一個cpu_workqueue_struct。

 

結構體cpu_workqueue_struct:

41 struct cpu_workqueue_struct {

42

43 spinlock_t lock;

44 /*這是內核鏈表,所有分配在這個處理器的work_struct將通過鏈表連在一起,等待執行*/

45 struct list_head worklist;

46 wait_queue_head_t more_work;

47 struct work_struct *current_work; //指向當前執行的work_struct

48

49 struct workqueue_struct *wq; //指向關聯自己的工作隊列

50 struct task_struct *thread; //指向對應的內核線程,即工作者線程

51

52 int run_depth; /* Detect run_workqueue() recursion depth */

53 } ____cacheline_aligned;

由上面知道,當我們調用queue_work來調度任務時,並不是把work_struct添加到等待隊列中,而是會被分配到工作對列的成員cpu_workqueue_struct中。

 

work結構體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; //每個任務的處理函數

32 #ifdef CONFIG_LOCKDEP

33 struct lockdep_map lockdep_map;

34 #endif

35 };

 

可能上面講得很亂,下面將來個圖來講解一下,雙處理器的情況下:

 

 

 

 

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

四、選擇哪個來實現下半部

 

在2.6內核,提供三種實現中斷下半部的方法,軟中斷、tasklet和工作隊列,其中tasklet是基於軟中斷實現的,兩者很相近。而工作隊列完全不同,它是靠內核線程實現的。

 

有這樣的一張表:

簡單來說,軟中斷和tasklet優先級較高,性能較好,調度快,但不能睡眠。而工作隊列是內核的進程調度,相對來說較慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能選擇動作隊列。否則最好用tasklet。

 

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

五、總結

 

這節簡單介紹了工作隊列的使用和實現。其中,還有函數能夠使指定工作隊列的任務延時執行,相關的結構體和函數有:

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)

有興趣自己看看,很簡單。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM