软中断 使得内核可以延期执行任务,是中断下半部的一种实现方法。
软中断使用的较少;tasklet 是下半部最常用的一种方式,但其实 tasklet 也是通过软中断来实现的。
系统中对时间要求最严格并且最重要的情况下,才会使用软中断。 目前只有 网络子系统和SCSI 系统中才使用了软中断。
内核定时器和 tasklet 都是在软中断基础上实现的。
如果需要加入一个软中断,首先应考虑使用 tasklet。
1. 软中断就是由 softirq_action 结构表示。
/* softirq mask and active fields moved to irq_cpustat_t in
* asm/hardirq.h to get better cache usage. KAO
*/
struct softirq_action
{
void (*action)(struct softirq_action *); //指向处理程序的指针
void *data; //指向处理程序函数的私有数据的指针
};
软中断机制的核心是一个表,包含了32个softirq_action结构体的数组:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
每个被注册的软中断都是这个数组中的一个数据项,因此最多有32个软中断。
当内核运行一个软中断处理程序时,他就会执行软中断中的 action 函数,action函数的参数就是 软中断结构体中的 data 成员。
一个软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序(即中断上半部),不过其他的软中断(甚至相同的软中断)可以在其他CPU上同时运行。
2. 软中断类型:
在注册软中断 open_softirq() 时,会声明的参数 nr,表示使用的是下面哪种软中断
各类软中断的优先级,按enum类型的号来决定, 越前面的优先级越高。
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
frequency threaded job scheduling. For almost all the purposes
tasklets are more than enough. F.e. all serial device BHs et
al. should be converted to tasklets, not to softirqs.
*/
enum
{
HI_SOFTIRQ=0, //优先级较高的tasklet
TIMER_SOFTIRQ, //定时器软中断
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ, //这两个用于网络的发送和接收。
BLOCK_SOFTIRQ, //用于块层,实现异步请求完成。
TASKLET_SOFTIRQ, //用来实现tasklet
SCHED_SOFTIRQ, //用于调度器,实现SMP系统上周期性的负载均衡。
#ifdef CONFIG_HIGH_RES_TIMERS
HRTIMER_SOFTIRQ, //高分辨率定时器
#endif
RCU_SOFTIRQ; //RCU锁定
NR_SOFTIRQS
};
3. 注册软中断,然后内核才能执行软中断:
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
nr ---- 每个软中断的唯一的编号,也就是上面软中断类型中的某一个,一般最多只有32个。
action --- 软中断处理函数
data --- 传递给 软中断处理函数的 参数。
该函数也就是传递软中断结构体中的成员参数,给 softirq_vec[] 数组.
一个注册的软中断,必须被标记后才会执行,这个就叫做触发软中断(raising the softirq).
也就是函数: raise_softirq(int nr);
void fastcall raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
/*
* This function must run with irqs
*/
inline fastcall 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();
}
static inline void __raise_softirq_irqoff(unsigned int nr)
{
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}
中断上半部会在返回前标记它的软中断,使其在稍后被执行;于是在合适的时刻,该软中断就会运行。以下三个地方,软中断会被检查及执行:
a. 在一个中断上半部代码返回时
b. 在ksoftirqd内核线程里。
c. 在一些显式检查和执行待处理软中断的代码中,如网络子系统。
5. 唤醒软中断守护进程: wakeup_softirqd(), 这是第二个开启软中断的方法:
/*
* we cannot loop indefinitely here to avoid userspace starvation,
* but we also don't want to introduce a worst case 1/HZ latency
* to the pending events, so lets the scheduler to balance
* the softirq load for us.
*/
static inline void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __get_cpu_var(ksoftirqd); //这里唤醒软中断守护进程
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
这里使用了 ksoftirqd 软中断守护进程,唤醒该进程,然后调度CPU去处理该进程,也就是处理软中断。
6. 软中断守护进程 --- ksoftirqd().
wakeup_softirq(), 唤醒了该守护进程。
每个处理器都有一组辅助处理软中断(和tasklet)的内核线程,当内核中大量出现软中断的时候,这些内核线程就会辅助处理它们。
每个处理器都有一个这样的线程,名字都叫做ksoftirqd/n, n是它们对应的处理器编号。如双核 有ksoftirq/0, ksoftirq/1
只要有待处理的软中断, ksoftirq 就会调用 do_softirq() 去处理它们。
具体定义如下:
static int ksoftirqd(void * __bind_cpu)
{
set_current_state(TASK_INTERRUPTIBLE); //设置当前进程可被中断
while (!kthread_should_stop()) {
preempt_disable(); //禁止内核抢占
if (!local_softirq_pending()) { //如果没有软中断
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();
}
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;
}
7. 软中断处理 do_softirq():各个平台有各自不同的软中断处理内容。
不管什么方法唤醒软中断,软中断都要在 do_softirq() 函数中执行。
do_softirq() 核心思想是: 如果有待处理的软中断(local_softirq_pending()),循环遍历每一个软中断,并调用它们的处理函数。
其核心代码可以简化为如下:
u32 pendin;
pending = local_softirq_pending();
if(pending){
struct softirq_action *h;
set_softirq_pending(0); //重新设置待处理软中断标志为不需处理软中断
h = softirq_vec; //h指针指向 软中断数组 softirq_vec[] 的第一项。
do{
if (pending & 1) //如果该软中是待处理软中断
h->action(h); //则执行软中断
h++; //指向下一个软中断
pending >>1 ; //右移一位,用来判断下一个软中断是不是待处理软中断。
}while (pending)
}
上面的代码检查并执行所有待处理的软中断。
--- 用局部变量 pending 保存 local_softirq_pending() 宏的返回值,它是待处理软中断的32位位图,如果第n位被置1,那么第n位对应的软中断为待处理软中断。
--- 待处理软中断位图被保存到pending 变量之后,就可以把实际中的软中断位图清零了,set_softirq_pending(0);
--- 一直循环,知道pending = 0, 这时表明没有待处理的软中断了,任务也就完成了。
--- 这种检查足以保证 h 一直指向 softirq_vec 的有效项,因为 pending 最多设置 32 位,所以循环最多执行 32 次。