软中断(softirq)是用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果。softirq是基本的下半部机制,需要互斥使用。一般很少直接使用。通常只用在少数性能比较关键的子系统中。它是可重入的,允许一个softirq的不同实例可同时运行在不同的处理器上。软中断的代码位于kernel/softirq.c。
软中断在编译时静态分配,不能动态注册和销毁。软中断一般用sofirq_action结构来表示,定义在<linux/interrupt.h>中:
struct softirq_action { void (*action)(struct softirq_action *); };
一个具有32个元素的结构数组声明在kernel/softirq.c中:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
每个注册的软中断占据数组的一项,因此,总共有NR_SOFTIRQS个注册的软中断。软中断的数目是在编译时静态决定的,不能动态更改。内核中软中断个数的限制是32个,但在当前内核中,只有9个。
软中断处理函数
软中断处理函数原型如下:
void softirq_handler(struct softirq_action *)
软中断不会抢占另一个软中断,只有中断处理函数才能抢占一个软中断。
软中断一般用于处理系统中对时间最苛刻和重要的后半部代码。当前,只有两个子系统直接使用了软中断:网络子系统和块设备子系统。另外内核计时器和微线程都基于软中断之上。
执行软中断
一个注册的软中断必须被标记后,才能运行。这称之为触发,实质上就是将其标记为未决状态。通常,中断处理函数会触发一个软中断,然后返回。在合适的时间,软中断会执行。
检测未决状态下的软中断通常发生在如下几个地方:
从硬件中断代码路径中返回
在ksoftirqd内核线程中
在任何显式地检测并执行未决软中断的代码中,如网络子系统。
执行软中断的代码主要发生在函数__do_softirq()函数中,由do_softirq()调用。主要代码如下:
u32 pending; pending = local_softirq_pending(); if (pending) { struct softirq_action *h; /* reset the pending bitmask */ set_softirq_pending(0); h = softirq_vec; do { if (pending & 1) h->action(h); h++; pending >>= 1; } while (pending); }
其基本步骤如下:
1.设置本地变量pending的值为宏local_softirq_pending()返回的值。它是一个32位掩码,如果第n位置1,表示第n个软中断处于未决状态。
2.清空掩码。
3.指针h被置为softirq_vec的第一项。
4.如果pending的第一位置位,调用h->action(h)。
5.递增指针h,使其指向softirq_vec数组的第二项。
6.掩码pending右移一位。
7.pointer现在指向数组的第二项,pending掩码的第一个比特位就是原来的第二个比特位,重复前述步骤。
8.重复执行,直到pending为0。
使用软中断
在声明一个软中断时,用到了软中断的索引号,它是一个枚举类型,定义在<linux/interrupt.h>中。内核使用该索引来作为软中断的相对优先级。值越小,优先级越大。创建一个新的软中断时,就包括向该枚举类型添加一个新的项。
Tasklet |
Priority |
Softirq Description |
HI_SOFTIRQ |
0 |
High-priority tasklets |
TIMER_SOFTIRQ |
1 |
Timers |
NET_TX_SOFTIRQ |
2 |
Send network packets |
NET_RX_SOFTIRQ |
3 |
Receive network packets |
BLOCK_SOFTIRQ |
4 |
Block devices |
TASKLET_SOFTIRQ |
5 |
Normal priority tasklets |
SCHED_SOFTIRQ |
6 |
Scheduler |
HRTIMER_SOFTIRQ |
7 |
High-resolution timers |
RCU_SOFTIRQ |
8 |
RCU locking |
注册软中断处理函数
使用open_softirq()函数可以注册软中断对应的处理函数,如下例子所示:
open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action);
软中断处理函数处于中断上下文中,且所有其他的中断是使能的,不能休眠。当一个软中断处理函数运行时,当前处理器的软中断被禁用。但是,另外一个处理器可以执行其他的软中断。如果在执行的过程中,触发了相同的软中断,另一个处理器可以同时运行它。这意味着,只在软中断处理函数中使用的任何其享的数据或全局数据需要进行适当的锁定。这是很重要的一点,也就是为什么尽量使用微线程的原因了。仅仅防止软中断不同步运行并不理想。如果一个软中断获得了阻止其本身的另一个实例同步运行的锁,就没有任何理由使用软中断了。结果,大部分软中断处理函数使用每-处理器数据或其他的技巧以避免显示地使用互斥锁。
触发软中断
当一个软中断处理函数通过open_softirq()加入到枚举列表后,它就可以运行了。调用函数raise_softirq()就行了,如下所示:
raise_softirq(NET_TX_SOFTIRQ);
该函数首先会在触发软中断之前禁用所有中断,之后将它们恢复成之前的状态。如果所有的中断已经关闭,可以使用另外一个函数:raise_softirq_irqoff(),如下所示:
/* * interrupts must already be off! */ raise_softirq_irqoff(NET_TX_SOFTIRQ);
softirq使用模板:
UsingSoftirq toOffload workfrom InterruptHandlers |
void__init roller_init() { /*… */ open_softirq(ROLLER_SOFT_IRQ,roller_analyze, NULL); }
/*The bottomhalf */ void roller_analyze() { /*… */ }
/*The interrupthandler */ staticirqreturn_t roller_interrupt(intirq, void*dev_id) { /*… */ /*Mark softirqas pending*/ raise_softirq(ROLLER_SOFT_IRQ); returnIRQ_HANDLED; } |