二、软中断
软中断一般很少用于实现下半部,但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