在由内核执行的几个任务之间有些不是紧急的,在必要情况下他们可以延迟一段时间。一个中断处理程序的几个中断服务例程之间是串行执行的,并且通常在一个中断的处理程序结束前,不应该再次出现这个中断。相反,可延迟中断可以在开中断的情况下执行。
linux中所谓的可延迟函数,包括软中断和tasklet以及通过中作队列执行的函数(这个以后说),软中断的分配是静态的(即值编译时定义),而tasklet的分配和初始化可以在运行时进行。
软中断
软中断所使用的数据结构定义为
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
其中softirq_action类型为一个函数指针,从这里也可以看出,软中断的个数是有限的有NR_SOFTIRQS个,具体的定义如下:
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
};
可以看出,一共10个软中断。
struct softirq_action
{
void (*action)(struct softirq_action *);
};
软中断的初始化
/*初始化软中断*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
上面函数中,参数nr为softirq_vec[]数组的下标,初始化就是初始化softirq_vec[]数组内容。
初始化了软中断后,要执行,接下来要做的是激活软中断,运用下面函数
/*激活软中断*/
void raise_softirq(unsigned int nr)
{
unsigned long flags;
/*保存eflags寄存器IF标志的状态值
并禁用本地CPU上得中断*/
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
具体的激活工作由raise_softirq_irqoff函数实现
/*
* This function must run with irqs disabled!
*/
inline void raise_softirq_irqoff(unsigned int nr)
{
/*把软中断标记为挂起*/
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!in_interrupt())
wakeup_softirqd();/*唤醒本地的内核线程*/
}
守护线程softirqd就是对软中断的处理
static int ksoftirqd(void * __bind_cpu)
{
/*设置进程状态为可中断*/
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {/*不应该马上返回*/
preempt_disable();
/*实现软中断中一个关键数据结构是每个
CPU都有的32位掩码(描述挂起的软中断),
他存放在irq_cpustat_t数据结构的__softirq_pending
字段中。为了获取或设置位掩码的值,
内核使用宏local_softirq_pending,他选择cpu的
软中断为掩码*/
if (!local_softirq_pending()) {/*位掩码为0,标示没有软中断*/
preempt_enable_no_resched();
schedule();
preempt_disable();
}
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq();/*调用软中断处理函数*/
preempt_enable_no_resched();
cond_resched();
preempt_disable();
rcu_sched_qs((long)__bind_cpu);
}
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}
下面是软中断的执行
/*如果在这样的一个检查点(local_softirq_pending()不为0) 检测到挂起的软中断,内核调用下面函数处理*/ 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函数实现
/*读取本地CPU的软中断掩码并执行与每个设置位 相关的可延迟函数,__do_softirq只做固定次数的循环 然后就返回。如果还有其余挂起的软中断,那么 内核线程ksofirqd将会在预期的时间内处理他们*/ asmlinkage void __do_softirq(void) { struct softirq_action *h; __u32 pending; /*把循环计数器的值初始化为10*/ int max_restart = MAX_SOFTIRQ_RESTART; int cpu; /*把本地CPU(被local_softirq_pending选中的)软件中断的 位掩码复制到局部变量pending中*/ 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);/*清除本地CPU的软中断位图, 以便可以激活新的软中断*/ /*激活本地中断*/ local_irq_enable(); h = softirq_vec; do {/*根据pending每一位的的设置,执行对应的软中断 处理函数*/ 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)/*如果还有挂起的软中断,唤醒内核线程 来处理本地CPU的软中断*/ wakeup_softirqd(); lockdep_softirq_exit(); account_system_vtime(current); _local_bh_enable();/*软中断计数器-1,因而重新激活可延迟函数*/ }
到此,linux内核软中断的大致执行和实现基本上分析完了,中间有很多地方没有注释的,主要是考虑到需要别的实现机制以及有的比较易懂。能够自己看懂。