关闭

do_softirq

289人阅读 评论(0) 收藏 举报
分类:
如果要启动某个软中断,则需要置位irq_stat[cpu].__softirq_pending中的相应位,而后续的处理工作则由do_softirq处理。设置__softirq_pending的操作则由函数raise_softirq_irqoff来实现。可以通过以下三种方法启动软中断:
1)在中断上下文中,通过调用函数raise_softirq,置位irq_stat[cpu].__softirq_pending中的相应软中断位,则会在硬中断结束后在函数irq_exit中调用invoke_softirq,实现软中断处理;
2)在非中断上下文中,通过调用raise_softirq_irqoff,置位irq_stat[cpu].__softirq_pending中的相应软中断位,并唤醒软中断守护进程,通过软中断守护进程实现软中断的处理;
3)在__do_softirq中,当该函数执行完时还有未决的软中断,则唤醒软中断守护进程,由软中断守护进程继续处理未决的软中断;
以上3种方法中,不管是通过调用函数invoke_softirq,还是通过软中断守护进程来处理软中断,最终都会调用函数do_softirq、__do_softirq。

通过硬中断进入软中断的途径:do_irq->irq_exit->invoke_softirq:
softirq.c:
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED  
# define invoke_softirq()     __do_softirq()
#else
# define invoke_softirq()     do_softirq()
#endif

__ARCH_IRQ_EXIT_IRQS_DISABLED是个体系架构相关的宏,用来决定HARQIRQ部分结束时,有没有关闭处理器响应外部中断的能力(即有没有关本地中断),如果某个体系架构的CPU定义了该宏(如ARM体系架构),说明HARQIRQ处理结束时,外部中断是关闭的,这样就可以直接执行SOFTIRQ了,此时就可以直接调用__do_softirq了(因为__do_softirq没有关闭本地中断的操作),否则要调用do_softirq,但该函数最终还会调用__do_softirq,不过在do_softirq函数调用__do_softirq之前要做关闭中断的操作,这样就保证__do_softirq在开始执行时中断是关闭的。

i386/softirq.c:


local_softirq_pending函数:
/* 选择本地CPU软中断位掩码  */
#define local_softirq_pending()     __IRQ_STAT(smp_processor_id(), __softirq_pending)

__IRQ_STAT宏:
#define __IRQ_STAT(cpu, member)     (irq_stat[cpu].member)

extern irq_cpustat_t irq_stat[];


内核用一个无符号整数__softirq_pending来表示当前正在等待被处理的softirq,每一种softirq在__softirq_pending中占据一位,每个CPU都拥有自己的__softirq_pending变量。

当需要softirq启动的时候,调用raise_softirq_irqoff(softirq_num)来将irq_stat[cpu].__softirq_pending对应的bit置位,而后续的处理工作则由do_softirq处理。每个cpu都有一个独立的irq_stat,同一个softirq的bit可能同时在多个cpu的irq_stat中被置位,因此一个softirq handler可能在多个cpu上同时被执行,因此softirq handler需要考虑同时执行的互斥问题。

static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
softirq_vec是个全局量,系统中每个CPU所看到的是同一个数组。但是,每个CPU各有其自己的“软中断控制/状态”结构,这些数据结构形成一个以CPU编号为下标的数组irq_stat[](定义在include/asm-i386/hardirq.h中)不同的CPU可以同时进入对软中断服务例程的执行,每个CPU分别执行各自所请求的软中断服务。从这个意义上说,软中断服务例程的执行是“并发的”、多序的。但是,这些软中断服务例程的设计和实现必须十分小心,不能让它们相互干扰(例如通过共享的全局变量)。

__do_softirq:

        //local_bh_disable就保证了每个CPU上同时运行的软中断只有一次,避免软中断再次触发。


         //清除本CPU的软中断标志位__softirq_pending,因为这里已经在服务软中断了;
         //到这里才打开中断,注意:以前运行状态一直是关中断运行(见上文描述),这里开中断后,下面在软中断处理程序执行过程中可能会被硬件中断抢占。
         //也就是说在进入软中断时不是一开始就会被硬件中断抢占。只有在这里以后的代码才可能被硬件中断抢占。

 
        //如果软中断对应的pending位置位,则表明需要进一步处理为该软中断注册的处理函数;并直到把软中断向量表中所有pending的软中断函数处理完成。


        //软中断处理函数执行完后,要关掉中断,因为下面的代码要在关中断情况下执行。


          //前面提到过,在刚才开硬件中断执行环境时只能被硬件中断抢占,在这个时候是无法处理软中断的,因为刚才开中断执行过程中可能多次被硬件中断抢占,
          //每抢占一次就有可能注册一个软中断,所以要再重新取一次所有的软中断(__softirq_pending),以便下面的代码进行处理后跳回到restart处重复执行。


          //如果在上面的开中断执行环境中触发了硬件中断,且每个都注册了一个软中断的话,这个软中断会设置 pending 位,但在当前一直屏蔽软中断的环境下无法得到执行,
          //前面提到过,因为 irq_exit() 和 do_softirq() 根本无法进入到这个处理过程中来(preempt_count不为0)。这个在上面周详的记录过了。那么在这里又有了一个执行的机会。
          //注意:虽然当前环境一直是处于屏蔽软中断执行的环境中,但在这里又给出了一个执行刚才在开中断环境过程中触发硬件中断时所注册的软中断的机会,
          //其实只要理解了软中断机制就会知道,无非是在一些特定环境下调用ISR注册到软中断向量表里的函数而已。 如果刚才触发的硬件中断注册了软中断,
          //并且重复执行次数没有到10次的话,那么则跳转到restart标志处重复以上所介绍的所有步骤:设置软中断标志位,重新开中断执行... 
          //注意:这里是要两个条件都满足的情况下才可能重复以上步骤。
 
   
         //如果以上步骤重复了 10 次后还有 pending 的软中断的话,那么系统在一定时间内可能达到了一个峰值,为了平衡这点。系统专门建立了一个 ksoftirqd 线程来处理,
         //这样避免在一定时间内负荷太大。这个 ksoftirqd 线程本身是个大循环,在某些条件下为了不负载过重,他是能被其他进程抢占的,但注意,
         //他是显示的调用了 preempt_xxx() 和 schedule()才会被抢占和转换的。这么做的原因是因为在他一旦调用local_softirq_pending() 函数检测到有 pending 的软中断需要处理的时候,
         //则会显示的调用 do_softirq() 来处理软中断。也就是说,下面代码唤醒的 ksoftirqd 线程有可能会回到这个函数当中来,尤其是在系统需要响应非常多软中断的情况下,他的调用入口是 do_softirq(),
         //这也就是为什么在 do_softirq()的入口处也会用 in_interrupt()  函数来判断是否有软中断正在处理的原因了,目的还是为了防止重入。        
       
       
         //到最后才开软中断执行环境,允许软中断执行。注意:这里使用的不是 local_bh_enable(),不会再次触发 do_softirq()的调用。


注:
local_bh_disable与__local_bh_enable一对,将preempt_count中,softirq对应的计数器加一与减一
/* SoftIRQ primitives.  */
#define local_bh_disable() \
          do { add_preempt_count(SOFTIRQ_OFFSET); barrier(); } while (0)
#define __local_bh_enable() \
          do { barrier(); sub_preempt_count(SOFTIRQ_OFFSET); } while (0)

而local_bh_enable函数中调用的是sub_preempt_count(SOFTIRQ_OFFSET - 1);
注意,这里不是sub_preempt_count(SOFTIRQ_OFFSET); 所以,它还会将抢占计数加1(preempt_count低8位),以禁止抢占。换句话说,它将softirq对应位减一,同时将抢占计数加一。 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:125279次
    • 积分:2696
    • 等级:
    • 排名:第13413名
    • 原创:119篇
    • 转载:151篇
    • 译文:1篇
    • 评论:6条
    最新评论