日期:2014-05-16  浏览次数:20738 次

关于Linux3.0驱动里面是否需要关中断的探讨

        最近和同事讨论一个问题,Linux3.0驱动里面有没有调用disable_irq_nosync()的必要。有同事说没必要调用该接口,因为在中断产生后中断线会被屏蔽掉;也有同事说必须添加那个接口,因为这种做法已经很成熟了。大家都有自己的观点,但是都拿不出确凿的证据,于是我就花时间研究了下Linux的关中断和开中断。


        我们从C代码讲起,汇编部分略过。当外设产生中断时,与之相连的中断控制器上会产生电信号,并通知CPU有中断源产生。此时CPU会停止当前工作,保存当前的环境到CPSR中,然后跳转到预先设置的中断地址。C代码的入口是asm_do_IRQ()(我使用的CPU是ARM架构的,代码路径为arch/arm/kernel/irq.c),然后执行里面的接口handle_IRQ()。

handle_IRQ的代码如下:


        首先调用set_irq_regs()将中断现场保存到old_regs里面,然后调用generic_handle_irq()进行正常的中断处理,中断处理完毕后再调用set_irq_regs(old_regs),恢复到之前的中断现场。

        generic_handle_irq_desc调用中断描述符的handle_irq回调函数。对于不同的中断类型,handle_irq回调函数可能是handle_simple_irq、handle_level_irq、handle_fasteoi_irq、handle_edge_irq、handle_edge_eoi_irq、handle_percpu_irq。我们常用的电平触发使用的是handle_level_irq,边沿触发使用的是handle_edge_irq,这里我以电平触发方式为例解释中断处理流程,handle_level_irq()的代码路径为kernel/irq/chip.c。

handle_level_irq的代码如下:


        首先加自旋锁,然后调用mask_ack_irq(desc),将产生该中断信号的中断线屏蔽掉,接着判断该中断是否正在处理,如果是则退出,否则继续往下执行。在执行中断处理之前,会判读当前是否被屏蔽或者是否设置了触发方式,如果当前中断已经被关掉了或者没有设置该中断的触发方式,则直接退出,不对中断进行处理。真正的中断处理函数是handle_irq_event(),这个函数一步步的往下调用,最终会调用到注册中断时的中断处理函数。在处理完中断后会调用cond_unmask_irq()取消对该中断线的屏蔽。


        至此,中断的处理流程已经清楚了,既然中断产生的时候中断线已经被屏蔽了,那么到底需不需要在驱动里面执行关中断操作呢?

为了验证此问题,我作了一些测试。首先将驱动里面的disable_irq_nosync和enable_irq这个两个接口注释掉,然后将cond_unmask_irq()函数注释掉,编译后发现驱动里面收不到中断,因此断定mask_ack_irq()确实将中断线给屏蔽掉了。接着,我去掉了对cond_unmask_irq()的注释,编译后发现驱动里面有中断进来,但是中断下半部的处理很不稳定,经常被打断,严重影响外设的使用。有兴趣的童鞋可以做以上实验试试。


        于是我得到了如下结论:中断产生时,产生该中断的中断线会被屏蔽掉,中断上半部处理完毕后(注意,仅仅是上半部),中断线的屏蔽就会被取消;在屏蔽中断线之后,进入中断处理函数之前,会判断该中断是否被关掉,如果已经关了则跳过,否则执行中断处理,为了不使中断下半部不被频繁的打断,我们需要在驱动的中断处理函数里面添加disable_irq_nosync()接口将中断关掉,在下半部执行完毕后再打开。因此,在驱动里面调用disable_irq_nosync()和enable_irq()是非常有必要的,它能确保驱动有序、稳定的执行。

        以上是我的拙见,有不妥之处欢迎大家指出!