linux设备驱动:中断处理中的hardirq与softirq详细流程

中断处理的整体框架:
图1
内核用于标识中断上下文(in_interrupt())的变量preempt_count的布局:
图2

  • 按照x86处理器在外部中断发生时的硬件逻辑,在do_IRQ被调用时,处理器已经屏蔽了对外部中断的响应。在图中我们看 到中断的处理大体上被分成两部分HARDIRQ和SOFTIRQ,对应到代码层面,do_IRQ()中调用irq_enter函数可以看做hardirq 部分的开始,而irq_exit函数的调用则标志着softirq部分的开始:
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
        ...
        irq_enter();

        //handle external interrupt (ISR)
        ...
        irq_exit();

        return 1;
}
  • irq_enter()函数的核心调用是__irq_enter(),后者的主要作用是在图2的 preempt_count变量的HARDIRQ部分+1,即标识一个hardirq的上下文,所以可以认为do_IRQ()调用irq_enter函数 意味着中断处理进入hardirq阶段。此时处理器响应外部中断的能力依然是被disable掉的(EFLAG.IF=0),因为ISR基本上属于设备驱动程序涉足的领域,内核无法保证在设备驱动程序的ISR中会否将EFLAG.IF置1(此处涉及可能的中断嵌套以及中断栈溢出问题,可以参考ARM中断处 理的那个帖子),所以我们会在内核代码中看到内核开发者为了尽可能避免非受控的ISR部分给系统带来的损害所做的努力。按照现在内核的设计理念,在 hardirq部分最好不要打开中断,所以处在hardirq上下文中的设备驱动程序的ISR应该尽可能快地返回(所谓只完成最关键的任务),而将耗时的 中断处理善后工作留到softirq部分,因为在softirq部分,内核会使EFLAG.IF=1,所以也意味着softirq部分可以随时被外部中断 所打断。

  • 下面我们重点讨论一下softirq部分的实现原理,do_IRQ()中调用irq_exit函数标志softirq部分 的开始。 irq_exit()函数会首先在图2的preempt_count变量的HARDIRQ部分-1,目的是清除hardirq的上下文标记。然后它有个关键的调用invoke_softirq,用于实际的softirq处理工作:
void irq_exit(void)
{
        ...
        sub_preempt_count(IRQ_EXIT_OFFSET);
        if (!in_interrupt() && local_softirq_pending())
                invoke_softirq();

        rcu_irq_exit();
        ...
}
  • 其中对in_interrupt()函数的叙述很明确:"…其主要用意是根据当前preempt_count变 量,来判断当前代码是否在一个中断上下文中执行。根据in_interrupt的定义来看,Linux内核认为HARDIRQ、SOFTIRQ以及NMI 都属于interrupt范畴…",所以softirq部分是否被执行,取决于:

    • 1.当前是否在中断上下文, 2. 是否有pending的softirq需要处理。
  • 第一个条件,主要用来防止softirq部分的重入,因为一旦有pending的softirq需要处理,那么invoke_softirq()的调用 (实际的工作发生在__do_softirq函数中)首先会将图2中SOFTIRQ部分+1,这样若在当前正在处理softirq过程中发生了外部中 断,hardirq部分标识了一个pending softirq,那么在irq_exit函数中将直接返回,而不会调用到invoke_softirq。

  • softirq部分的核心代码段如下:

asmlinkage void __do_softirq(void)
{
        ...
        //获得__softirq_pending变量上保存的pending softirq数据
        pending = local_softirq_pending();
        //将图2中SOFTIRQ部分+1,标识softirq上下文,此时in_interrupt()返回true.
        __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);
        ...
restart:
        /* Reset the pending bitmask before enabling irqs */
        set_softirq_pending(0);
        //注意此处,内核调用local_irq_enable打开处理器响应外部中断能力,EFLAG.IF=1,所以接下来的softirq处理时外部中断可以进入处理器
        local_irq_enable();

        h = softirq_vec;
        do {
                if (pending & 1) {
                        ...
                        //调用每个类型的softirq所对应的处理函数,见下图3
                        h->action(h);
                        ...
                }
                h++;
                pending >>= 1;
        } while (pending);
        //softirq处理结束前,重新关闭中断。中断的再次打开发生在中断的返回,也就是在图1中处理器在执行iret指令时的硬件逻辑,POP EFLAG, EFLAG.IF=1
        local_irq_disable();
        
        //再次检测是否有新的pending softirq,因为softirq执行时可能有新的外部中断进来,如果有,此处一并处理。
        pending = local_softirq_pending();
        if (pending && --max_restart)
                goto restart;
        ...
        //将图2中SOFTIRQ部分-1,标识softirq上下文的结束
        __local_bh_enable(SOFTIRQ_OFFSET);
}
  • 对每个类型的softirq的处理发生在上面的do…while…循环中,原理其实非常简单,为节省文字,用下面一个草图来做说明(图3):
    图3
  • 所以do…while…循环实际上是从bit 0遍历__softirq_pending变量,目前该变量的0~9分别对应10个不同类型的softirq,每个softirq对应不同的处理函数,比如HI_SOFTIRQ对应的action为tasklet_hi_action,它与TASKLET_SOFTIRQ的action的原理完全一样,也就是我们平常所说的tasklet。__softirq_pending是个per-CPU型的变量,因为SMP系统中每个处理器都可以独立处理各自到来 的外部中断,也都对应有各自的hardirq栈和softirq栈。

参考链接:详解Linux中断处理中的hardirq与softirq机制
参考书籍:深入Linux设备驱动程序内核机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>