一. 为什么要使用中断下半部?
中断是一个很霸道的东西,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好。
另外,中断上下文中不能阻塞,这也限制了中断上下文中能干的事。基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。(1)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。(2)如果一个任务和硬件相关,将其放在中断处理程序中执行。(3)如果一个任务要保证不被其它中断打断,将其放在中断处理程序中执行。(4)其它所有任务,考虑放在下半部执行
拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内核会在网卡中断处理函数(上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处理函数里面马上执行,可以将中断让出来做更紧迫的事情。
可以有三种方法来实现下半部:软中断、tasklet和等待队列。
二. 软中断
2.2.1软中断的实现
软中断是在编译期间静态分配的。不像tasklet那样能被动态的注册或去除。软中断由softirq_action结构表示,它定义在<linux/interrupt.h>中:
struct softirq_action {
void( *action)(struct softirq_action *); /*待执行的函数*/
} ;
在kernel/softirq.c中定义了一个包含有32个该结构体的数组。
static strcut softirq_action softirq_vec[32]; 每个注册的软中断都占据该数组中的一项,现在用到的只是9个
(1) 软中断处理程序:
软中断处理程序action的函数原型如下:
void softirq_handler(struct softirq_action *)
当内核运行一个软中断处理程序的时候,它就会执行这个action函数,其唯一的参数为指向相应的softirq_action结构体的指针。这个函数把整个结构体都传进去,这个技巧可以保证将来在结构体中加入新的域时,无需对所有的软中断处理程序都进行变动。
一个软中断不会抢占另外一个软中断,实际上,唯一可以抢占软中断的是中断处理程序,不过,其它的软中断——甚至是相同类型的软中断——可以在其它处理器上同时执行。
(2) 执行软中断:
一个注册的软中断必须在被标记后才会执行。这被称作触发软中断(raising the softirq)。通常,中断处理程序会在返回前标记它的软中断,使其在稍后被执行。
在合适的时刻,该软中断就会运行,在下列地方,待处理的软中断会被检查和执行:
在处理完一个硬中断以后
在ksoftirqd内核线程中
在那些显式检查和执行待处理的软中断的代码中,如网络子系统中
不管是用什么办法唤起,软中断都要在do_softirq()中执行,该函数很简单,如果有待处理的软中断,do_softirq()会遍历每一个,调用它们的处理程序。
软中断在do_softirq()中执行。do_softirq()经过简化后的核心部分:
u32 pending = local_softirq_pending();
if(pending) {
struct softirq_action *h = softirq_vec;
set_softirq_pending(0) ; //将软中断位图置0
do {
if(pending&1) h->action(h); //调用action函数
h++;
pending>>=1;
}while(pending);
}
local_softirq_pending()返回值是待处理的软中断的32位位图,如果第n位被置为1,那么第n位对应类型的软中断等待处理
2.2.2使用软中断
软中断保留给系统中对时间要求最严格以及最重要的下半部使用。内核定时器和tasklets都是建立在软中断上的,如果你想加入一个新的软中断,首先要想想为什么用tasklet实现不了,tasklet可以动态生成,由于它们对加锁的要求不高,所以使用起来也很方便,当然,对于时间要求养并能自己高效的完成加锁工作的应用,软中断会是正确的选择。
1、 分配索引:在编译期间,可以通过<linux/interrupt.h>中定义的一个枚举类型来静态的声明软中断。
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
新插入项一般在BLOCK_SOFTIRQ和TASKLET_SOFTIRQ之间
2、 注册处理程序:接着,在运行时通过调用open_softirq()注册软件中断处理程序,该函数有三个参数:索引号、处理函数和data域存放的数值。例如网络子系统,通过以下方式注册自己的软中断:
open_softirq(NET_TX_SOFTIRQ, net_tx_action,NULL);
open_softirq(NET_TX_SOFTIRQ, net_rx_action,NULL);
软中断处理程序的执行的时候,允许响应中断,但自己不能睡眠。
3、 触发你的软中断:
通过在枚举类型的列表中添加新项以及调用open_softirq()进行注册以后,新的软中断处理程序就能够运行。raise_softirq()函数可以将一个软中断设置为挂起状态,让他在下次调用do_softirq()函数时投入运行。一个例子:
raise_softirq(NET_TX_SOFTIRQ);
这会触发NET_TX_SOFTIRQ软中断。它的处理程序net_tx_action()就会在内核下一次执行软中断时投入运行。该函数在触发一个软中断前要禁止中断,触发后再恢复回原来的状态。如果中断本来就已经禁止了,可以调用另外一个函数
extern void raise_softirq_irqoff(unsigned int nr);
在中断处理程序中触发软中断是最常见的形式。这样,内核在执行完中断处理程序后,马上就会调用do_softirq。于是软中断开始执行中断处理程序留给它去完成的剩余任务。
三 . 总结
中断处理程序----》raise_softirq()触发软中断----》ksoftirqd线程调用softirq_pending(),发现有待处理的软中断时(返回1)----》ksoftirqd线程调用do_softirq()----》local_softirq_pending()返回软中断的32位位图-----》依次访问等待处理的软中断(位图中相应位值为1)的软中断处理函数