Linux softirq
一、 软中断产生的原因
软中断:用于有效的实现内核的延期操作,也是底半部机制tasklet的基础
二、 数据结构
1)softirq_action
该结构是软中断的核心数据结构,代表软中断处理函数。
struct softirq_action
{
void (*action)(struct softirq_action *);
};
2)下面是软中断的类型,目前支持10个软中断类型。
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
};
3)softirq_vec
该数组存储了已注册的软中断类型对应的处理函数,该数组非常重要
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
三、 软中断的注册
要在内核中使用一个软中断,首先要进行注册,所谓注册,其实就是在数组softirq_vec中增加相应软中断的处理函数,调用函数定义如下:
/*
注册一个软中断,即在软中断向量数据中,增加软中断number为nr的处理函数
*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
四、 启动软中断
如果要启动某个软中断,则需要置位irq_stat[cpu].__softirq_pending中的相应位,而后续的处理工作则由do_softirq处理。
而设置__softirq_pending的操作则由函数__raise_softirq_irqoff来实现。
可以通过以下两种方法启动软中断:
1)在中断上下文中,通过调用函数raise_softirq,置位irq_stat[cpu].__softirq_pending中的相应软中断位,则会在中断结束后在函数irq_exit中调用invoke_softirq,实现软中断处理
2)在非中断上下文中,通过调用raise_softirq_irqoff,置位irq_stat[cpu].__softirq_pending中的相应软中断位,并唤醒软中断守护进程,通过软中断守护进程实现软中断的处理
3)在__do_softirq中,当该函数执行完时还有未决的软中断,则唤醒软中断守护进程,由软中断守护进程继续处理未决的软中断
以上3种方法中,不管是通过调用函数invoke_softirq,还是通过软中断守护进程来处理软中断,最终都会调用函数do_softirq、__do_softirq。
do_softirq主要完成以下工作
1) 首先判断当前是否在中断上下文中,若是则直接返回
2) 关闭irq,读取当前cpu的irq_stat[cpu].__softirq_pending
3) 判断__softirq_pending的值是否为0,若不为0则说明有软中断待处理,则调用__do_softirq
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt())
return;
local_irq_save(flags);
pending = local_softirq_pending();
if (pending)
__do_softirq();
local_irq_restore(flags);
}
__do_softirq的定义如下:
其主要完成的工作有:
1) 获取irq_stat[cpu].__softirq_pending的值
2) 重置irq_stat[cpu].__softirq_pending的值为0,并开启软中断
3) 获取软中断向量数据softirq_vec
4) 在一个while循环中,对于每一个未处理的软中断,执行softirq_vec中相对应的action处理函数
5) 关闭中断,重新读取irq_stat[cpu].__softirq_pending的值,若该值不为0则
6) 在重复执行的次数没有超过MAX_SOFTIRQ_RESTART,且irq_stat[cpu].__softirq_pending的值不为0时,重新执行上述2、3、4、5的操作
7) 若已超过MAX_SOFTIRQ_RESTART,则调用wakeup_softirqd,唤醒软中断守护进程,由软中断守护进程继续处理
#define MAX_SOFTIRQ_RESTART 10
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
account_system_vtime(current);
__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
do {
if (pending & 1) {
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(h - softirq_vec);
trace_softirq_entry(h, softirq_vec);
h->action(h);
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
_local_bh_enable();
}
疑问:在SMP下,多个CPU可以同时执行同一个软中断吗?
解答:因为每一个CPU都有一个__softirq_pending变量,那么在同一时刻多个CPU的__softirq_pending的某个相同位可能都为1,这样的话,就可能出现在同一时刻多个CPU同时执行同一个软中断。
软中断的应用:
在内核代码中,使用软中断的有:在接收和发送网络数据包时,会借助软中断实现;另一个中断底半部处理机制tasklet也使用了软中断,tasklet即使用软中断作为基础,然后再实现tasklet。在我们编写我们自己的驱动代码时,个人建议首选使用tasklet,而不要使用softirq。