/*
* 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();
}
在do_IRQ的最后,调用irq_exit()以退出中断上下文的时候,可能会激活softirq():
static inline void invoke_softirq(void)
{
if (!force_irqthreads) //如果没有强制中断线程化
do_softirq(); //调用do_softirq()
else { //如果强制中断线程化
__local_bh_disable((unsigned long)__builtin_return_address(0), //屏蔽掉本CPU上的其它软中断
SOFTIRQ_OFFSET);
wakeup_softirqd(); //唤醒中断守护进程
__local_bh_enable(SOFTIRQ_OFFSET); //打开其它软中断
}
}
何为中断守护进程?中断守护进程的任务是与其余内核代码异步执行软中断,系统中的每个处理器都分配了自身的守护进程,名为ksoftirqd(《深入Linux内核架构》)。
在唤醒softirq的时候,如果我们没有强制中断线程化,那么调用do_softirq,而且此对应的软中断服务会立即被执行,直到其完成或者自己阻塞,而其他进程得不到调度的机会,因为中断任务的优先级是很高的。如果这个过程的很长的话,那就很糟糕了(参看博文《中断队列初始化》中有关中断线程化的解释)。
如果强制了中断线程化,那么调用wakeup_softirqd,我们来看一下做了什么?不过,我们应该留心的是在wakeup_softirq前后发生的事情。在调用wakeup_softirq之前会屏蔽掉其他软中断,直到这个软中断执行结束,说明每个CPU上同一时间只能处理一个softirq。
/*
* 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 void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
将tsk指向ksoftirqd,之后尝试唤醒tsk,但是tsk不一定会被执行哦,这样看调度器的调度结果(最好好好理解一下注释给出的解释)。
让我们来看一下ksoftirqd,与以往不同的是,在3.0的内核中ksoftirqd表示的是一个进程,而不是之前版本的内核中的函数,这个进程的声明如下:
/*
* CPU type, hardware bug flags, and per-CPU state. Frequently used
* state comes earlier:
*/
struct cpuinfo_ia64 {
。。。。。。
unsigned int ptce_stride[2];
struct task_struct *ksoftirqd; /* kernel softirq daemon for this CPU */
。。。。。。
};
可见,ksoftirq是percpu结构的。那它的初始化是在哪个地方呢?
static int __cpuinit cpu_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
。。。。。。
case CPU_UP_PREPARE_FROZEN:
p = kthread_create_on_node(run_ksoftirqd,
hcpu,
cpu_to_node(hotcpu),
"ksoftirqd/%d", hotcpu);
if (IS_ERR(p)) {
printk("ksoftirqd for %i failed\n", hotcpu);
return notifier_from_errno(PTR_ERR(p));
}
kthread_bind(p, hotcpu);
per_cpu(ksoftirqd, hotcpu) = p;
break;
。。。。。。
return NOTIFY_OK;
}
可见,ksoftirqd被指向了run_ksoftirqd:
static int run_ksoftirqd(void * __bind_cpu)
{
set_current_state(TASK_INTERRUPTIBLE); //softirq是可以被中断(调度)的
while (!kthread_should_stop()) { //当CPU被移除或者其他原因导致softirqd被停止时,退出本循环
preempt_disable(); //不允许内核抢占
if (!local_softirq_pending()) { //如果没有待决软中断,则将CPU转与其他任务。
preempt_enable_no_resched();
schedule();
preempt_disable();
}
__set_current_state(TASK_RUNNING); //设置进程状态为TASK_RUNNING
while (local_softirq_pending()) { //尚有待决软中断
/* Preempt disable stops cpu going offline. 因为处于preempt_disable状态,因此CPU是不能让出CPU的,否则一定是出错了。
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
local_irq_disable(); //关闭中断。因为每个CPU在一个时刻之允许处理一个中断。
if (local_softirq_pending())
__do_softirq(); //***************************************************************
local_irq_enable(); //开中断
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;
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;
}
现在,有一个问题:在这段代码中看到,在__do_softirq()是在关中断的前提下执行的,那么,也就是说如果中断A的软中断正在执行,它会阻止同一个中断源上的另一个中断B的发生么?当然,我们说的是在同一个CPU上。如果是这样的话,是不是说某一中断的softirq一旦开始执行,就必须执行完毕才行呢?
上面我们讲到了在强制中断线程化的情况下,软中断的处理情况,回过去看,如果没有强制中断线程化:
if (!force_irqthreads) //如果没有强制中断线程化
do_softirq(); //调用do_softirq()
看到,不过是否强制了中断线程化,最终都要调用__do_softirq函数。这之前,我们先来看do_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()) { //存在尚未被处理的softirq
curctx = current_thread_info();
irqctx = __this_cpu_read(softirq_ctx);
irqctx->tinfo.task = curctx->task;
irqctx->tinfo.previous_esp = current_stack_pointer; //因为我们用的是8K的thread_union,因此中断上下文会借用当前进程的上下文。
/* build the stack frame on the softirq stack */
isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
call_on_stack(__do_softirq, isp);
/*
* Shouldn't 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;
int cpu;
pending = local_softirq_pending();
account_system_vtime(current);
__local_bh_disable((unsigned long)__builtin_return_address(0), // 这里加上软中断计数,这样在本函数中开中断后,发生中断后不会再重入本函数
SOFTIRQ_OFFSET);
lockdep_softirq_enter(); //调试用
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0); //将软中断对应的掩码清0
local_irq_enable(); //开中断,避免长时间不能中断
h = softirq_vec;
do {
if (pending & 1) {
unsigned int vec_nr = h - softirq_vec; //获取对应的软中断的编号
int prev_count = preempt_count(); //保存抢占计数preempt_count,避免其受到破坏
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h); //执行相应的软中断服务程序
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) { //如果软中断破坏了抢占计数器,那么恢复原来的preempt_count
printk(KERN_ERR "huh, entered softirq %u %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", vec_nr,
softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++; //处理下一个软中断
pending >>= 1; //右移pending
} while (pending); //如果没有挂起的软中断,那么结束循环
local_irq_disable(); //关中断
pending = local_softirq_pending();
if (pending && --max_restart) //max_restart = 10;因此,最多轮询10次。以防止执行处理软中断的时间过长,而将应用程序饿死。
goto restart;
if (pending) //如果还是没有将所有软中断处理完,那么唤醒softirqd,放弃CPU占用权,让其它应用程序进来。
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
__local_bh_enable(SOFTIRQ_OFFSET);
}
参考文章:http://blog.chinaunix.net/space.php?uid=25845340&do=blog&id=2983385