Linux 2.6 内核软中断(softirq)执行分析

今天无意中看了眼 Linux 2.6 内核的软中断实现,发现和以前我看到的大不相同(以前也是走马观花,不大仔细),能说改动非常大。连 softirq 的调用点都不相同了,以前是三个调用点,今天搜索了一下原始码,发目前多出了ksoftirqd 这个东西后,softirq 在系统中的调用点仅是在 ISR 返回时和使用了 local_bh_enable() 函数后被调用了。网卡部分的显示调用,我觉得应该不算是系统中的调用点。ksoftirqd 返回去调用 do_softirq() 函数应该也只能算是其中的一个分支,因为其本身从源头上来讲也还是在 ISR 返回时 irq_exit() 调用的。这样一来就和前些日子写的那份笔记(视窗系统/Linux/Solaris 软中断机制)里介绍的 Linux 内核部分的软中断有出处了,看来以后讨论 Linux kernel 代码一定要以内核版本为前题来说,要不非乱了不可。看来得买本 Linux 方面的书了,每次上来直接看相关代码也不是回事,时间也不允许。

linux kernel source 2.6.19.1

/kernel/softirq.c

  1. //   
  2. // do_IRQ 函数执行完硬件 ISR 后退出时调用此函数。   
  3. //   
  4. void irq_exit(void)   
  5. {   
  6.         account_system_vtime(current);   
  7.         trace_hardirq_exit();   
  8.         sub_preempt_count(IRQ_EXIT_OFFSET);   
  9.         //   
  10.         // 判断当前是否有硬件中断嵌套,并且是否有软中断在   
  11.         // pending 状态,注意:这里只有两个条件同时满足   
  12.         // 时,才有可能调用 do_softirq() 进入软中断。也就是   
  13.         // 说确认当前所有硬件中断处理完成,且有硬件中断安装了   
  14.         // 软中断处理时理时才会进入。   
  15.         //    
  16.         if (!in_interrupt() && local_softirq_pending())   
  17.                 //   
  18.                 // 其实这里就是调用 do_softirq() 执行   
  19.                 //   
  20.                 invoke_softirq();   
  21.         preempt_enable_no_resched();   
  22. }   
  23. #ifndef __ARCH_HAS_DO_SOFTIRQ   
  24. asmlinkage void do_softirq(void)   
  25. {   
  26.         __u32 pending;   
  27.         unsigned long flags;   
  28.         //   
  29.         // 这个函数判断,如果当前有硬件中断嵌套,或   
  30.         // 有软中断正在执行时候,则马上返回。在这个   
  31.         // 入口判断主要是为了和 ksoftirqd 互斥。   
  32.         //   
  33.         if (in_interrupt())   
  34.                 return;   
  35.         //   
  36.         // 关中断执行以下代码   
  37.         //   
  38.         local_irq_save(flags);   
  39.         //   
  40.         // 判断是否有 pending 的软中断需要处理。   
  41.         //   
  42.         pending = local_softirq_pending();   
  43.         //   
  44.         // 如果有则调用 __do_softirq() 进行实际处理   
  45.         //   
  46.         if (pending)   
  47.                 __do_softirq();   
  48.         //   
  49.         // 开中断继续执行   
  50.         //   
  51.         local_irq_restore(flags);   
  52. }   
  53. //   
  54. // 最大软中断调用次数为 10 次。   
  55. //   
  56. #define MAX_SOFTIRQ_RESTART 10   
  57. asmlinkage void __do_softirq(void)   
  58. {   
  59.         //   
  60.         // 软件中断处理结构,此结构中包括了 ISR 中   
  61.         // 注册的回调函数。   
  62.         //   
  63.         struct softirq_action *h;   
  64.         __u32 pending;   
  65.         int max_restart = MAX_SOFTIRQ_RESTART;   
  66.         int cpu;   
  67.         //   
  68.         // 得到当前所有 pending 的软中断。   
  69.         //    
  70.         pending = local_softirq_pending();   
  71.         account_system_vtime(current);   
  72.         //   
  73.         // 执行到这里要屏蔽其他软中断,这里也就证实了   
  74.         // 每个 CPU 上同时运行的软中断只能有一个。   
  75.         //   
  76.         __local_bh_disable((unsigned long)__builtin_return_address(0));   
  77.         trace_softirq_enter();   
  78.         //   
  79.         // 针对 SMP 得到当前正在处理的 CPU   
  80.         //   
  81.         cpu = smp_processor_id();   
  82. //   
  83. // 循环标志   
  84. //   
  85. restart:   
  86.         //   
  87.         // 每次循环在允许硬件 ISR 强占前,首先重置软中断   
  88.         // 的标志位。   
  89.         //   
  90.         /* Reset the pending bitmask before enabling irqs */  
  91.         set_softirq_pending(0);   
  92.         //   
  93.         // 到这里才开中断运行,注意:以前运行状态一直是关中断   
  94.         // 运行,这时当前处理软中断才可能被硬件中断抢占。也就   
  95.         // 是说在进入软中断时不是一开始就会被硬件中断抢占。只有   
  96.         // 在这里以后的代码才可能被硬件中断抢占。   
  97.         //   
  98.         local_irq_enable();   
  99.         //   
  100.         // 这里要注意,以下代码运行时能被硬件中断抢占,但   
  101.         // 这个硬件 ISR 执行完成后,他的所注册的软中断无法马上运行,   
  102.         // 别忘了,目前虽是开硬件中断执行,但前面的 __local_bh_disable()   
  103.         // 函数屏蔽了软中断。所以这种环境下只能被硬件中断抢占,但这   
  104.         // 个硬中断注册的软中断回调函数无法运行。要问为什么,那是因为   
  105.         // __local_bh_disable() 函数设置了一个标志当作互斥量,而这个   
  106.         // 标志正是上面的 irq_exit() 和 do_softirq() 函数中的   
  107.         // in_interrupt() 函数判断的条件之一,也就是说 in_interrupt()    
  108.         // 函数不仅检测硬中断而且还判断了软中断。所以在这个环境下触发   
  109.         // 硬中断时注册的软中断,根本无法重新进入到这个函数中来,只能   
  110.         // 是做一个标志,等待下面的重复循环(最大 MAX_SOFTIRQ_RESTART)   
  111.         // 才可能处理到这个时候触发的硬件中断所注册的软中断。   
  112.         //   
  113.         //   
  114.         // 得到软中断向量表。   
  115.         //   
  116.         h = softirq_vec;   
  117.         //   
  118.         // 循环处理所有 softirq 软中断注册函数。   
  119.         //    
  120.         do {   
  121.                 //   
  122.                 // 如果对应的软中断设置 pending 标志则表明   
  123.                 // 需要进一步处理他所注册的函数。   
  124.                 //   
  125.                 if (pending & 1) {   
  126.                         //   
  127.                         // 在这里执行了这个软中断所注册的回调函数。   
  128.                         //   
  129.                         h->action(h);   
  130.                         rcu_bh_qsctr_inc(cpu);   
  131.                 }   
  132.         //   
  133.         // 继续找,直到把软中断向量表中所有 pending 的软   
  134.         // 中断处理完成。   
  135.         //   
  136.                 h++;   
  137.                 //   
  138.                 // 从代码里能看出按位���作,表明一次循环只   
  139.                 // 处理 32 个软中断的回调函数。   
  140.                 //   
  141.                 pending >>= 1;    
  142.         } while (pending);   
  143.         //   
  144.         // 关中断执行以下代码。注意:这里又关中断了,下面的   
  145.         // 代码执行过程中硬件中断无法抢占。   
  146.         //   
  147.         local_irq_disable();   
  148.         //   
  149.         // 前面提到过,在刚才开硬件中断执行环境时只能被硬件中断   
  150.         // 抢占,在这个时候是无法处理软中断的,因为刚才开中   
  151.         // 断执行过程中可能多次被硬件中断抢占,每抢占一次就有可   
  152.         // 能注册一个软中断,所以要再重新取一次所有的软中断。   
  153.         // 以便下面的代码进行处理后跳回到 restart 处重复执行。   
  154.         //   
  155.         pending = local_softirq_pending();   
  156.         //   
  157.         // 如果在上面的开中断执行环境中触发了硬件中断,且每个都   
  158.         // 注册了一个软中断的话,这个软中断会设置 pending 位,   
  159.         // 但在当前一直屏蔽软中断的环境下无法得到执行,前面提   
  160.         // 到过,因为 irq_exit() 和 do_softirq() 根本无法进入到   
  161.         // 这个处理过程中来。这个在上面周详的记录过了。那么在   
  162.         // 这里又有了一个执行的机会。注意:虽然当前环境一直是   
  163.         // 处于屏蔽软中断执行的环境中,但在这里又给出了一个执行   
  164.         // 刚才在开中断环境过程中触发硬件中断时所注册的软中断的   
  165.         // 机会,其实只要理解了软中断机制就会知道,无非是在一些特   
  166.         // 定环境下调用 ISR 注册到软中断向量表里的函数而已。   
  167.         //   
  168.         //   
  169.         // 如果刚才触发的硬件中断注册了软中断,并且重复执行次数   
  170.         // 没有到 10 次的话,那么则跳转到 restart 标志处重复以上   
  171.         // 所介绍的所有步骤:设置软中断标志位,重新开中断执行...   
  172.         // 注意:这里是要两个条件都满足的情况下才可能重复以上步骤。    
  173.         //   
  174.         if (pending && --max_restart)   
  175.                 goto restart;   
  176.         //   
  177.         // 如果以上步骤重复了 10 次后更有 pending 的软中断的话,   
  178.         // 那么系统在一定时间内可能达到了一个峰值,为了平衡这点。   
  179.         // 系统专门建立了一个 ksoftirqd 线程来处理,这样避免在一   
  180.         // 定时间内负荷太大。这个 ksoftirqd 线程本身是个大循环,   
  181.         // 在某些条件下为了不负载过重,他是能被其他进程抢占的,   
  182.         // 但注意,他是显示的调用了 preempt_xxx() 和 schedule()   
  183.         // 才会被抢占和转换的。这么做的原因是因为在他一旦调用    
  184.         // local_softirq_pending() 函数检测到有 pending 的软中断   
  185.         // 需要处理的时候,则会显示的调用 do_softirq() 来处理软中   
  186.         // 断。也就是说,下面代码唤醒的 ksoftirqd 线程有可能会回   
  187.         // 到这个函数当中来,尤其是在系统需要响应非常多软中断的情况   
  188.         // 下,他的调用入口是 do_softirq(),这也就是为什么在 do_softirq()   
  189.         // 的入口处也会用 in_interrupt()  函数来判断是否有软中断   
  190.         // 正在处理的原因了,目的还是为了防止重入。ksoftirqd 实现   
  191.         // 看下面对 ksoftirqd() 函数的分析。   
  192.         //   
  193.         if (pending)   
  194.                //   
  195.                // 此函数实际是调用 wake_up_process() 来唤醒 ksoftirqd   
  196.                //    
  197.                 wakeup_softirqd();   
  198.         trace_softirq_exit();   
  199.         account_system_vtime(current);   
  200.         //   
  201.         // 到最后才开软中断执行环境,允许软中断执行。注意:这里   
  202.         // 使用的不是 local_bh_enable(),不会再次触发 do_softirq()   
  203.         // 的调用。   
  204.         //    
  205.         _local_bh_enable();   
  206. }   
  207. static int ksoftirqd(void * __bind_cpu)   
  208. {   
  209.         //   
  210.         // 显示调用此函数设置当前进程的静态优先级。当然,   
  211.         // 这个优先级会随调度器策略而变化。   
  212.         //   
  213.         set_user_nice(current, 19);   
  214.         //   
  215.         // 设置当前进程不允许被挂启   
  216.         //   
  217.         current->flags |= PF_NOFREEZE;   
  218.         //   
  219.         // 设置当前进程状态为可中断的状态,这种睡眠状   
  220.         // 态可响应信号处理等。   
  221.         //    
  222.         set_current_state(TASK_INTERRUPTIBLE);   
  223.         //   
  224.         // 下面是个大循环,循环判断当前进程是否会停止,   
  225.         // 不会则继续判断当前是否有 pending 的软中断需   
  226.         // 要处理。   
  227.         //   
  228.         while (!kthread_should_stop()) {   
  229.                 //   
  230.                 // 如果能进行处理,那么在此处理期间内禁止   
  231.                 // 当前进程被抢占。   
  232.                 //   
  233.                 preempt_disable();   
  234.                 //   
  235.                 // 首先判断系统当前没有需要处理的 pending 状态的   
  236.                 // 软中断   
  237.                 //   
  238.                 if (!local_softirq_pending()) {   
  239.                         //   
  240.                         // 没有的话在主动放弃 CPU 前先要允许抢占,因为   
  241.                         // 一直是在不允许抢占状态下执行的代码。   
  242.                         //   
  243.                         preempt_enable_no_resched();   
  244.                         //   
  245.                         // 显示调用此函数主动放弃 CPU 将当前进程放入睡眠队列,   
  246.                         // 并转换新的进程执行(调度器相关不记录在此)   
  247.                         //   
  248.                         schedule();   
  249.                         //   
  250.                         // 注意:如果当前显示调用 schedule() 函数主动转换的进   
  251.                         // 程再次被调度执行的话,那么将从调用这个函数的下一条   
  252.                         // 语句开始执行。也就是说,在这里当前进程再次被执行的   
  253.                         // 话,将会执行下面的 preempt_disable() 函数。   
  254.                         //   
  255.                         //   
  256.                         // 当进程再度被调度时,在以下处理期间内禁止当前进程   
  257.                         // 被抢占。   
  258.                         //   
  259.                         preempt_disable();   
  260.                 }   
  261.                 //   
  262.                 // 设置当前进程为运行状态。注意:已设置了当前进程不可抢占   
  263.                 // 在进入循环后,以上两个分支不论走哪个都会执行到这里。一是   
  264.                 // 进入循环时就有 pending 的软中断需要执行时。二是进入循环时   
  265.                 // 没有 pending 的软中断,当前进程再次被调度获得 CPU 时继续   
  266.                 // 执行时。   
  267.                 //   
  268.                 __set_current_state(TASK_RUNNING);   
  269.                 //   
  270.                 // 循环判断是否有 pending 的软中断,如果有则调用 do_softirq()   
  271.                 // 来做具体处理。注意:这里又是个 do_softirq() 的入口点,   
  272.                 // 那么在 __do_softirq() 当中循环处理 10 次软中断的回调函数   
  273.                 // 后,如果更有 pending 的话,会又调用到这里。那么在这里则   
  274.                 // 又会有可能去调用 __do_softirq() 来处理软中断回调函数。在前   
  275.                 // 面介绍 __do_softirq() 时已提到过,处理 10 次还处理不完的   
  276.                 // 话说明系统正处于繁忙状态。根据以上分析,我们能试想如果在   
  277.                 // 系统非常繁忙时,这个进程将会和 do_softirq() 相互交替执行,   
  278.                 // 这时此进程占用 CPU 应该会非常高,虽然下面的 cond_resched()    
  279.                 // 函数做了一些处理,他在处理完一轮软中断后当前处理进程可能会   
  280.                 // 因被调度而减少 CPU 负荷,不过在非常繁忙时这个进程仍然有可   
  281.                 // 能大量占用 CPU。   
  282.                 //   
  283.                 while (local_softirq_pending()) {   
  284.                         /* Preempt disable stops cpu going offline.  
  285.                            If already offline, we’ll be on wrong CPU:  
  286.                            don’t process */  
  287.                         if (cpu_is_offline((long)__bind_cpu))   
  288.                                 //   
  289.                                 // 如果当前被关联的 CPU 无法继续处理则跳转   
  290.                                 // 到 wait_to_die 标记出,等待结束并退出。   
  291.                                 //    
  292.                                 goto wait_to_die;   
  293.                         //   
  294.                         // 执行 do_softirq() 来处理具体的软中断回调函数。注   
  295.                         // 意:如果此时有一个正在处理的软中断的话,则会马上   
  296.                         // 返回,还记得前面介绍的 in_interrupt() 函数么。   
  297.                         //   
  298.                         do_softirq();   
  299.                         //   
  300.                         // 允许当前进程被抢占。   
  301.                         //   
  302.                         preempt_enable_no_resched();   
  303.                            
  304.                         //   
  305.                         // 这个函数有可能间接的调用 schedule() 来转换当前   
  306.                         // 进程,而且上面已允许当前进程可被抢占。也就是   
  307.                         // 说在处理完一轮软中断回调函数时,有可能会转换到   
  308.                         // 其他进程。我认为这样做的目的一是为了在某些负载   
  309.                         // 超标的情况下不至于让这个进程长时间大量的占用 CPU,   
  310.                         // 二是让在有非常多软中断需要处理时不至于让其他进程   
  311.                         // 得不到响应。   
  312.                         //   
  313.                         cond_resched();   
  314.                         //   
  315.                         // 禁止当前进程被抢占。   
  316.                         //   
  317.                         preempt_disable();   
  318.                         //   
  319.                         // 处理完所有软中断了吗?没有的话继续循环以上步骤   
  320.                         //   
  321.                 }   
  322.                 //   
  323.                 // 待一切都处理完成后,允许当前进程被抢占,并设置   
  324.                 // 当前进程状态为可中断状态,继续循环以上所有过程。   
  325.                 //   
  326.                 preempt_enable();   
  327.                 set_current_state(TASK_INTERRUPTIBLE);   
  328.         }   
  329.       
  330.         //   
  331.         // 如果将会停止则设置当前进程为运行状态后直接返回。   
  332.         // 调度器会根据优先级来使当前进程运行。   
  333.         //   
  334.         __set_current_state(TASK_RUNNING);   
  335.         return 0;   
  336. //   
  337. // 一直等待到当前进程被停止   
  338. //   
  339. wait_to_die:   
  340.         //   
  341.         // 允许当前进程被抢占。   
  342.         //   
  343.         preempt_enable();   
  344.         /* Wait for kthread_stop */  
  345.         //   
  346.         // 设置当前进程状态为可中断的状态,这种睡眠状   
  347.         // 态可响应信号处理等。   
  348.         //    
  349.         set_current_state(TASK_INTERRUPTIBLE);   
  350.         //   
  351.         // 判断当前进程是否会被停止,如果不是的话   
  352.         // 则设置进程状态为可中断状态并放弃当前 CPU   
  353.         // 主动转换。也就是说这里将一直等待当前进程   
  354.         // 将被停止时候才结束。   
  355.         //   
  356.         while (!kthread_should_stop()) {   
  357.                 schedule();   
  358.                 set_current_state(TASK_INTERRUPTIBLE);   
  359.         }   
  360.         //   
  361.         // 如果将会停止则设置当前进程为运行状态后直接返回。   
  362.         // 调度器会根据优先级来使当前进程运行。   
  363.         //   
  364.         __set_current_state(TASK_RUNNING);   
  365.         return 0;   
  366. }  

1、加入了对软中断线程 ksoftirqd 的分析。

2、修正了对 softirq 调用点的解释,因为当时 local_bh_enable() 函数

   也在 /kernel/softirq.c 中,所以看遗漏了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值