To be a better coder
分类: 嵌入式
2016-12-09 17:46:59
原文地址:linux中断的上半部和下半部 作者:td1442911376
一、什么是下半部
中断是一个很霸道的东西,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好。
另外,中断上下文中不能阻塞,这也限制了中断上下文中能干的事。
基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。
拿网卡来举例,在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>" "
9 #else
10 #define P_DEBUG(fmt, args...) printk("<7>" "
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>" "
10 #else
11 #define P_DEBUG(fmt, args...) printk("<7>" "
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>" "
10 #else
11 #define P_DEBUG(fmt, args...) printk("<7>" "
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)
有兴趣自己看看,很简单。