本文共 4703 字,大约阅读时间需要 15 分钟。
摘自:http://blog.chinaunix.net/uid-23629988-id-327893.html
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
为了更好的并发性和更快的响应,软中断是网卡驱动使用bottom-half方式。软中断的处理方式有两种,一种是在各个检查点调用do_softirq去处理,还有就是由内核线程ksoftirqd周期检查,如有pending的软中断,仍然是调用do_softirq去处理。 首先看直接调用do_softirq(或__do_softirq)的地方: - unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
- {
- ...... ......
- /* 中断处理 */
- ...... ......
-
- irq_exit();
-
- set_irq_regs(old_regs);
- return 1;
- }
- void irq_exit(void)
- {
- ...... ......
- /* 如果不是中断中且本CPU存在pending的软中断*/
- if (!in_interrupt() && local_softirq_pending())
- invoke_softirq();
- ...... ......
- }
2. 系统调用,任何interrupt或exception返回时; 3. local_bh_enable中:当然了,重新enable BH,这时只要有pending的软中断,自然要执行了; 4. 在有的书中还说在kernel的代码中,还有一些地方由于执行时间可能稍长,也会调用do_softirq处理软中断——目前看的kernel代码不够多,暂未发现。并且这点不影响今天的主题。 处理软中断的函数就是do_softirq。因为软中断的处理有些是与平台相关的,所以有的平台需要自己的实现。此处以x86平台为例,do_softirq位于arch/x86/kernel/irq_32.c。 - asmlinkage void do_softirq(void)
- {
- unsigned long flags;
- struct thread_info *curctx;
- union irq_ctx *irqctx;
- u32 *isp;
- if (in_interrupt())
- return;
/* 禁中断,并将当前的中断标志保存到flags中*/ - local_irq_save(flags);
- if (local_softirq_pending()) {
- /* 为软中断的运行创建环境:这个与硬件相关,无须太关心 */
- curctx = current_thread_info();
- irqctx = __this_cpu_read(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));
/* 调用真正的worker __do_softirq */ - call_on_stack(__do_softirq, isp);
- /*
- * Shouldnt happen, we returned above if in_interrupt():
- */
- WARN_ON_ONCE(softirq_count());
- }
- local_irq_restore(flags);
- }
从这个函数看,好像在做软中断时,中断是被关掉的——这与我们的认知是不同的。实际上在真正的worker __do_softirq会enable中断。所以软中断的处理时,并没有关中断,也因此可以被中断。 - 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 */
- /*
- 将pending的软中断标志清零。
- 做这个操作时需要在关中断的情况下,因为中断会设置这个标志
- */
- set_softirq_pending(0);
- local_irq_enable();
-
- h = softirq_vec;
/* 按位检测,处理所有的pending的软中断 */ - do {
- if (pending & 1) {
- unsigned int vec_nr = h - softirq_vec;
- int prev_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())) {
- 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;
- } while (pending);
- local_irq_disable();
/* 再检查pending的软中断,因为在之前的处理软中断的过程中,很可能发生了新的中断,并设置了新的软中断标志位,所以需要再次检查 */ - pending = local_softirq_pending();
- /*
- 如果有了新的pending并且没有超过最多的循环限制,那么就继续处理软中断。
- 之所以要加一个最多的循环处理限制,因为在网络负载比较大的环境,网卡会产生很多软中断。如果没有循环,那么kernel就把大部分时间集中在软中断处理上,而无法处理其他工作。
- */
- if (pending && --max_restart)
- goto restart;
/* 还有软中断剩下没有处理,那么就唤醒softirqd去处理剩下的软中断 */ - if (pending)
- wakeup_softirqd();
-
- lockdep_softirq_exit();
-
- account_system_vtime(current);
- /* enable BH*/
- __local_bh_enable(SOFTIRQ_OFFSET);
- }
- static int run_ksoftirqd(void * __bind_cpu)
- {
- set_current_state(TASK_INTERRUPTIBLE);
-
- while (!kthread_should_stop()) {
- preempt_disable();
- /* 检查是否有pending的软中断 */
- if (!local_softirq_pending()) {
- /* 没有,那么就调用schedule,交出CPU控制权,让其他进程执行 */
- 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 */
- /*
- 因为ksoftirqd是运行在指定的cpu的,也就说每个cpu都有一个对应的ksoftirqd.
- 所以如果cpu_is_offline为真,那么一定运行在另外一个cpu上了。这个线程就需要退出处理。
- */
- 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;
-
- 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;
- }
虽然ksoftirqd是一个内核线程,但是由于众多的do_softirq的调用点,所以真正由ksoftirqd处理的软中断并不多。