分享

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

 写意人生 2014-06-09

转载于:

http://blog.csdn.net/wlwl0071986/article/details/8721583?reload, 如有侵权,请告知,谢谢           

最近和同事讨论一个问题,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的代码如下:

  1. void handle_IRQ(unsigned int irq, struct pt_regs *regs)  
  2. {  
  3.     struct pt_regs *old_regs = set_irq_regs(regs);  
  4.   
  5.     perf_mon_interrupt_in();  
  6.     irq_enter();  
  7.   
  8.     /* 
  9.      * Some hardware gives randomly wrong interrupts.  Rather 
  10.      * than crashing, do something sensible. 
  11.      */  
  12.     if (unlikely(irq >= nr_irqs)) {  
  13.         if (printk_ratelimit())  
  14.             printk(KERN_WARNING "Bad IRQ%u\n", irq);  
  15.         ack_bad_irq(irq);  
  16.     } else {  
  17.         generic_handle_irq(irq);  
  18.     }  
  19.   
  20.     /* AT91 specific workaround */  
  21.     irq_finish(irq);  
  22.   
  23.     irq_exit();  
  24.     set_irq_regs(old_regs);  
  25.     perf_mon_interrupt_out();  
  26. }  
  27.   
  28.    

        首先调用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的代码如下:

  1. void handle_level_irq(unsigned int irq, struct irq_desc *desc)  
  2. {  
  3.     raw_spin_lock(&desc->lock);  
  4.     mask_ack_irq(desc);  
  5.   
  6.     if (unlikely(irqd_irq_inprogress(&desc->irq_data)))  
  7.         if (!irq_check_poll(desc))  
  8.             goto out_unlock;  
  9.   
  10.     desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);  
  11.     kstat_incr_irqs_this_cpu(irq, desc);  
  12.   
  13.     /* 
  14.      * If its disabled or no action available 
  15.      * keep it masked and get out of here 
  16.      */  
  17.     if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))  
  18.         goto out_unlock;  
  19.   
  20.     handle_irq_event(desc);  
  21.   
  22.     cond_unmask_irq(desc);  
  23.   
  24. out_unlock:  
  25.     raw_spin_unlock(&desc->lock);  
  26. }  

        首先加自旋锁,然后调用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()是非常有必要的,它能确保驱动有序、稳定的执行。

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


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多