Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1685937
  • 博文数量: 511
  • 博客积分: 967
  • 博客等级: 准尉
  • 技术积分: 2560
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-06 14:19
文章分类

全部博文(511)

文章存档

2016年(11)

2015年(61)

2014年(257)

2013年(63)

2012年(119)

分类: LINUX

2014-05-17 22:38:39

本文的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)的地方:
1. 中断处理do_IRQ,
  1. unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
  2. {
  3.     ...... ......
  4.     /* 中断处理 */
  5.     ...... ......
  6.     
  7.     irq_exit();

  8.     set_irq_regs(old_regs);
  9.     return 1;
  10. }
  1. void irq_exit(void)
  2. {
  3.     ...... ......
  4.     /* 如果不是中断中且本CPU存在pending的软中断*/
  5.     if (!in_interrupt() && local_softirq_pending())
  6.         invoke_softirq();
  7.     ...... ......
  8. }
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。
  1. asmlinkage void do_softirq(void)
  2. {
  3.     unsigned long flags;
  4.     struct thread_info *curctx;
  5.     union irq_ctx *irqctx;
  6.     u32 *isp;
     /* 如果是中断中,则不执行软中断 */
  1.     if (in_interrupt())
  2.         return;
     /* 禁中断,并将当前的中断标志保存到flags中*/
  1.     local_irq_save(flags);
     /* 检查是否有pending的软中断 */
  1.     if (local_softirq_pending()) {
  2.         /* 为软中断的运行创建环境:这个与硬件相关,无须太关心 */
  3.         curctx = current_thread_info();
  4.         irqctx = __this_cpu_read(softirq_ctx);
  5.         irqctx->tinfo.task = curctx->task;
  6.         irqctx->tinfo.previous_esp = current_stack_pointer;

  7.         /* build the stack frame on the softirq stack */
  8.         isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
         /* 调用真正的worker __do_softirq */
  1.         call_on_stack(__do_softirq, isp);
  2.         /*
  3.          * Shouldnt happen, we returned above if in_interrupt():
  4.          */
  5.         WARN_ON_ONCE(softirq_count());
  6.     }
     /* 恢复中断标志 */
  1.     local_irq_restore(flags);
  2. }
从这个函数看,好像在做软中断时,中断是被关掉的——这与我们的认知是不同的。实际上在真正的worker __do_softirq会enable中断。所以软中断的处理时,并没有关中断,也因此可以被中断。
下面看__do_softirq
  1. asmlinkage void __do_softirq(void)
  2. {
  3.     struct softirq_action *h;
  4.     __u32 pending;
  5.     int max_restart = MAX_SOFTIRQ_RESTART;
  6.     int cpu;
     /* 取得软中断的pending状态 */
  1.     pending = local_softirq_pending();
  2.     account_system_vtime(current);
     /* disable BH */
  1.     __local_bh_disable((unsigned long)__builtin_return_address(0),
  2.                 SOFTIRQ_OFFSET);
  3.     lockdep_softirq_enter();

  4.     cpu = smp_processor_id();
  5. restart:
  6.     /* Reset the pending bitmask before enabling irqs */
  7.     /* 
  8.     将pending的软中断标志清零。
  9.     做这个操作时需要在关中断的情况下,因为中断会设置这个标志
  10.     */
  11.     set_softirq_pending(0);
     /* enable中断 */
  1.     local_irq_enable();

  2.     h = softirq_vec;
     /* 按位检测,处理所有的pending的软中断 */
  1.     do {
  2.         if (pending & 1) {
  3.             unsigned int vec_nr = h - softirq_vec;
  4.             int prev_count = preempt_count();

  5.             kstat_incr_softirqs_this_cpu(vec_nr);

  6.             trace_softirq_entry(vec_nr);
  7.             /* 调用对应的软中断处理函数 */
  8.             h->action(h);
  9.             trace_softirq_exit(vec_nr);
  10.             if (unlikely(prev_count != preempt_count())) {
  11.                 printk(KERN_ERR "huh, entered softirq %u %s %p"
  12.                        "with preempt_count %08x,"
  13.                        " exited with %08x?\n", vec_nr,
  14.                        softirq_to_name[vec_nr], h->action,
  15.                        prev_count, preempt_count());
  16.                 preempt_count() = prev_count;
  17.             }

  18.             rcu_bh_qs(cpu);
  19.         }
  20.         h++;
  21.         pending >>= 1;
  22.     } while (pending);
      
     /* 再关中断 */
  1.     local_irq_disable();
     /* 再检查pending的软中断,因为在之前的处理软中断的过程中,很可能发生了新的中断,并设置了新的软中断标志位,所以需要再次检查 */
  1.     pending = local_softirq_pending();
  2.     /* 
  3.     如果有了新的pending并且没有超过最多的循环限制,那么就继续处理软中断。
  4.     之所以要加一个最多的循环处理限制,因为在网络负载比较大的环境,网卡会产生很多软中断。如果没有循环,那么kernel就把大部分时间集中在软中断处理上,而无法处理其他工作。
  5.     */
  6.     if (pending && --max_restart)
  7.         goto restart;
     /* 还有软中断剩下没有处理,那么就唤醒softirqd去处理剩下的软中断 */
  1.     if (pending)
  2.         wakeup_softirqd();

  3.     lockdep_softirq_exit();

  4.     account_system_vtime(current);
  5.     /* enable BH*/
  6.     __local_bh_enable(SOFTIRQ_OFFSET);
  7. }
下面看看ksoftirqd的代码
  1. static int run_ksoftirqd(void * __bind_cpu)
  2. {
  3.     set_current_state(TASK_INTERRUPTIBLE);

  4.     while (!kthread_should_stop()) {
  5.         preempt_disable();
  6.         /* 检查是否有pending的软中断 */
  7.         if (!local_softirq_pending()) {
  8.             /* 没有,那么就调用schedule,交出CPU控制权,让其他进程执行 */
  9.             preempt_enable_no_resched();
  10.             schedule();
  11.             preempt_disable();
  12.         }

  13.         __set_current_state(TASK_RUNNING);
         /* 有pending的软中断 */
  1.         while (local_softirq_pending()) {
  2.             /* Preempt disable stops cpu going offline.
  3.              If already offline, we'll be on wrong CPU:
  4.              don't process */
  5.             /*
  6.             因为ksoftirqd是运行在指定的cpu的,也就说每个cpu都有一个对应的ksoftirqd.
  7.             所以如果cpu_is_offline为真,那么一定运行在另外一个cpu上了。这个线程就需要退出处理。 
  8.             */
  9.             if (cpu_is_offline((long)__bind_cpu))
  10.                 goto wait_to_die;
  11.             /* 处理软中断 */
  12.             do_softirq();
  13.             preempt_enable_no_resched();
  14.             /* 根据运行的时间和其它因素,决定是否被调度出去 */
  15.             cond_resched();
  16.             preempt_disable();
  17.             rcu_note_context_switch((long)__bind_cpu);
  18.         }
  19.         preempt_enable();
  20.         set_current_state(TASK_INTERRUPTIBLE);
  21.     }
  22.     __set_current_state(TASK_RUNNING);
  23.     return 0;

  24. wait_to_die:
  25.     preempt_enable();
  26.     /* Wait for kthread_stop */
  27.     set_current_state(TASK_INTERRUPTIBLE);
  28.     while (!kthread_should_stop()) {
  29.         schedule();
  30.         set_current_state(TASK_INTERRUPTIBLE);
  31.     }
  32.     __set_current_state(TASK_RUNNING);
  33.     return 0;
  34. }
虽然ksoftirqd是一个内核线程,但是由于众多的do_softirq的调用点,所以真正由ksoftirqd处理的软中断并不多。

Ok,这就是软中断处理的详细流程分析。


阅读(372) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~