什么是下半部
中断是一个很霸道的东西,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好。
另外,中断上下文中不能阻塞,这也限制了中断上下文中能干的事。
基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。
拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内核会在网卡中断处理函数(上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处理函数里面马上执行,可以将中断让出来做更紧迫的事情。
可以有三种方法来实现下半部:软中断、tasklet和等待队列。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、软中断
软中断一般很少用于实现下半部,但tasklet是通过软中断实现的,所以先介绍软中断。字面理解,软中断就是软件实现的异步中断,它的优先级比硬中断低,但比普通进程优先级高,同时,它和硬中断一样不能休眠。
软中断是在编译时候静态分配的,要用软中断必须修改内核代码。
在kernel/softirq.c中有这样的一个数组:
51static struct softirq_action
softirq_vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;
内核通过一个softirq_action数组来维护的软中断,NR_SOFTIRQS是当前软中断的个数,待会再看他在哪里定义。
先看一下softirq_action结构体:
265 struct
softirq_action
266 {
267 void (*action)(struct softirq_action
*); //软中断处理函数
268 };
一看发现,结构体里面就一个软中断函数,他的参数就是本身结构体的指针。之所以这样设计,是为了以后的拓展,如果在结构体中添加了新成员,也不需要修改函数接口。在以前的内核,该结构体里面还有一个data的成员,用于传参,不过现在没有了。
接下来看一下如何使用软中断实现下半部
一、要使用软中断,首先就要静态声明软中断:
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,
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中定义:
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);
//这是我添加的,导出符号,这样我编写的程序就能调用
在我的程序中如下调用:
13 void xiaobai_action(struct
softirq_action *t) //软中断处理函数
14 {
15 printk("hello
xiaobai!\n");
16 }
。。。。。。。。
48 open_softirq(XIAOBAI_SOFTIRQ,
xiaobai_action);
三、在中断处理函数返回前,触发对应的软中断。
在中断处理函数完成了必要的操作后,就应该调用函数raise_sotfirq触发软中断,让软中断执行中断下半部的操作。
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 }
所谓的触发软中断,并不是指马上执行该软中断,不然和在中断上执行没什么区别。它的作用只是告诉内核:下次执行软中断的时候,记得执行我这个软中断处理函数。
当然,这个函数也得导出符号后才能调用:
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);
在我的程序中如下调用:
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 }
经过三步,使用软中断实现下半部就成功了,看一下完整的函数:
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
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
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中:
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
x,"
203 " exited with x?\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);
//大循环内执行,知道所有被触发的软中断都执行完