/* * 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); //打开其它软中断 } }
在唤醒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 */ 。。。。。。 };
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; }
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; }
上面我们讲到了在强制中断线程化的情况下,软中断的处理情况,回过去看,如果没有强制中断线程化:
if (!force_irqthreads) //如果没有强制中断线程化 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