Linux
的softirq机制是与SMP紧密不可分的。为此,整个softirq机制的设计与实现中自始自终都贯彻了一个思想:“谁触发,谁执行”(Who
marks,Who
runs),也即触发软中断的那个CPU负责执行它所触发的软中断,而且每个CPU都由它自己的软中断触发与控制机制。这个设计思想也使得softirq
机制充分利用了SMP系统的性能和特点。 多个softirq可以并行执行,甚至同一个softirq可以在多个processor上同时执行。
一、softirq的实现
每个softirq在内核中通过struct softirq_action来表示,另外,通过全局属组softirq_vec标识当前内核支持的所有的softirq。
-
/* 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 *);
-
};
-
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
Linux内核最多可以支持32个softirq(
思考:为什么是32个?),但当前只实现了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
-
};
二、softirq处理函数
struct softirq_action结构体中,只有一个函数指针成员action,即指向用户定义的softirq处理函数。当执行时,可以通过如下代码:
softirq_vec[i]->action(i);
一个注册的softirq在执行之前必须被激活,术语称为"raise the
softirq"。被激活的softirq通常并不会立即执行,一般会在之后的某个时刻检查当前系统中是否有被pending的softirq,如果有就
去执行,Linux内核中检查是否有softirq挂起的检查点主要有以下三类:
(1)硬件中断代码返回的时候
-
/*
-
* Exit an interrupt context. Process softirqs if needed and possible:
-
*/
-
void irq_exit(void)
-
{
-
account_system_vtime(current);
-
trace_hardirq_exit();
-
sub_preempt_count(IRQ_EXIT_OFFSET);
-
if (!in_interrupt() && local_softirq_pending())
-
invoke_softirq();
-
rcu_irq_exit();
-
#ifdef CONFIG_NO_HZ
-
/* Make sure that timer wheel updates are propagated */
-
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
-
tick_nohz_stop_sched_tick(0);
-
#endif
-
preempt_enable_no_resched();
-
}
(2)ksoftirqd内核服务线程运行的时候
-
static int run_ksoftirqd(void * __bind_cpu)
-
{
-
... ...
-
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_note_context_switch((long)__bind_cpu);
-
}
-
preempt_enable();
-
set_current_state(TASK_INTERRUPTIBLE);
-
}
-
__set_current_state(TASK_RUNNING);
-
return 0;
-
... ...
-
}
(3)在一些内核子系统中显示的去检查挂起的softirq
-
int netif_rx_ni(struct sk_buff *skb)
-
{
-
int err;
-
preempt_disable();
-
err = netif_rx(skb);
-
if (local_softirq_pending())
-
do_softirq();
-
preemptenable();
-
return err;
-
}
下面重点分析以下do_softirq(),了解Linux内核到底是怎么来处理softirq的。
-
asmlinkage void do_softirq(void)
-
{
-
unsigned long flags;
-
struct thread_info *curctx;
-
union irq_ctx *irqctx;
-
u32 *isp;
-
if (in_interrupt()) /*首先判断是否在中断上下文中*/
-
return;
-
local_irq_save(flags);
if(local_softirq_pending()){
-
curctx = current_thread_info();
-
irqctx = __get_cpu_var(softirq_ctx);
-
irqctx->tinfo.task = curctx->task;
-
irqctx->tinfo.previous_esp = current_stack_pointer;
-
/* build the stack frame on the softirq stack */
-
isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
-
call_on_stack(__do_softirq, isp);
-
/*
-
* Shouldnt happen, we returned above if in_interrupt():
-
*/
-
WARN_ON_ONCE(softirq_count());
-
}
local_irq_restore(flags);
-
}
实际的处理函数为__do_softirq:
-
asmlinkage void __do_softirq(void)
-
{
-
struct softirq_action *h;
-
__u32 pending;
-
int max_restart = MAX_SOFTIRQ_RESTART; /*不启动ksoftirqd之前,最大的处理softirq的次数,经验值*/
-
int cpu;
-
-
/*取得当前被挂起的softirq,同时这里也解释了为什么Linux内核最多支持32个softirq,因为pending只有32bit*/
-
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);/*获取了pending的softirq之后,清空所有pending的softirq的标志*/
local_irq_enable();
-
h = softirq_vec;
do {
-
if (pending & 1) { /*从最低位开始,循环右移逐位处理pending的softirq*/
-
int prev_count = preempt_count();
-
kstat_incr_softirqs_this_cpu(h - softirq_vec);
trace_softirq_entry(h, softirq_vec);
-
h->action(h); /*执行softirq的处理函数*/
-
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) /*启动ksoftirqd的阈值*/
-
goto restart;
-
if (pending) /*启动ksoftirqd去处理softirq,此时说明pending的softirq比较多,比较频繁,上面的处理过程中,又不断有softirq被pending*/
-
wakeup_softirqd();
lockdep_softirq_exit();
-
account_system_vtime(current);
-
_local_bh_enable();
-
}
三、使用softirq
softirq一般用在对实时性要求比较强的地方,当前的Linux内核中,只有两个子系统直接使用了softirq:网络子系统和块设备子系统。另外,
增加新的softirq需要重新编译内核,因此,除非必须需要,最好考虑tasklet和kernel timer是否适合当前需要。
如果必须需要使用softirq,那么需要考虑的一个重要的问题就是新增加的softirq的优先级,默认情况下,softirq的数值越小优先级越高,
根据实际经验,新增加的softirq最好在BLOCK_SOFTIRQ和TASKLET_SOFTIRQ之间。
softirq的处理函数通过open_softirq进行注册,此函数接收两个参数,一个是softirq的整数索引,另一个是该softirq对应的处理函数。例如在网络子系统中,注册了如下两个softirq及其处理函数:
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
前面提到,软中断处理函数注册后,还需要将该软中断激活,此软中断才能被执行,激活操作是通过raise_softirq函数来实现,在网络子系统中激活代码如下:
-
/* Called with irq disabled */
-
static inline void ____napi_schedule(struct softnet_data *sd,
-
struct napi_struct *napi)
-
{
-
list_add_tail(&napi->poll_list, &sd->poll_list);
-
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
-
}
这里的__raise_softirq_irqoff和raise_softirq的区别是,前者在事先已经关中断的情况下可以被使用,后者自己完成中断的关闭和恢复。