您的位置:首页 > 运维架构 > Linux

个人学习笔记--linux中断下半部之软中断

2015-09-10 11:46 405 查看
一.  为什么要使用中断下半部?

中断是一个很霸道的东西,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好。

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

基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。(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)的软中断处理函数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息