1.  中断流控层简介

早期的内核版本中,几乎所有的中断都是由__do_IRQ函数进行处理,但是,因为各种中断请求的电气特性会有所不同,又或者中断控制器的特性也不同,这会导致以下这些处理也会有所不同:

  • 何时对中断控制器发出ack回应;
  • mask_irq和unmask_irq的处理;
  • 中断控制器是否需要eoi回应?
  • 何时打开cpu的本地irq中断?以便允许irq的嵌套;
  • 中断数据结构的同步和保护;

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
为此,通用中断子系统把几种常用的流控类型进行了抽象,并为它们实现了相应的标准函数,我们只要选择相应的函数,赋值给irq所对应的irq_desc结构的handle_irq字段中即可。这些标准的回调函数都是irq_flow_handler_t类型:

[cpp] view plaincopy
  1. typedef void (*irq_flow_handler_t)(unsigned int irq,
  2. struct irq_desc *desc);

目前的通用中断子系统实现了以下这些标准流控回调函数,这些函数都定义在:kernel/irq/chip.c中,

  • handle_simple_irq  用于简易流控处理;
  • handle_level_irq  用于电平触发中断的流控处理;
  • handle_edge_irq  用于边沿触发中断的流控处理;
  • handle_fasteoi_irq  用于需要响应eoi的中断控制器;
  • handle_percpu_irq  用于只在单一cpu响应的中断;
  • handle_nested_irq  用于处理使用线程的嵌套中断;

驱动程序和板级代码可以通过以下几个API设置irq的流控函数:

  • irq_set_handler();
  • irq_set_chip_and_handler();
  • irq_set_chip_and_handler_name();

以下这个序列图展示了整个通用中断子系统的中断响应过程,flow_handle一栏就是中断流控层的生命周期:

图1.1  通用中断子系统的中断响应过程

2.  handle_simple_irq

该函数没有实现任何实质性的流控操作,在把irq_desc结构锁住后,直接调用handle_irq_event处理irq_desc中的action链表,它通常用于多路复用(类似于中断控制器级联)中的子中断,由父中断的流控回调中调用。或者用于无需进行硬件控制的中断中。以下是它的经过简化的代码:

[cpp] view plaincopy
  1. void
  2. handle_simple_irq(unsigned int irq, struct irq_desc *desc)
  3. {
  4. raw_spin_lock(&desc->lock);
  5. ......
  6. handle_irq_event(desc);
  7. out_unlock:
  8. raw_spin_unlock(&desc->lock);
  9. }

3.  handle_level_irq

该函数用于处理电平中断的流控操作。电平中断的特点是,只要设备的中断请求引脚(中断线)保持在预设的触发电平,中断就会一直被请求,所以,为了避免同一中断被重复响应,必须在处理中断前先把mask irq,然后ack irq,以便复位设备的中断请求引脚,响应完成后再unmask irq。实际的情况稍稍复杂一点,在mask和ack之后,还要判断IRQ_INPROGRESS标志位,如果该标志已经置位,则直接退出,不再做实质性的处理,IRQ_INPROGRESS标志在handle_irq_event的开始设置,在handle_irq_event结束时清除,如果监测到IRQ_INPROGRESS被置位,表明该irq正在被另一个CPU处理中,所以直接退出,对电平中断来说是正确的处理方法。但是我觉得在ARM系统中,这种情况根本就不会发生,因为在没有进入handle_level_irq之前,中断控制器没有收到ack通知,它不会向第二个CPU再次发出中断请求,而当程序进入handle_level_irq之后,第一个动作就是mask irq,然后ack irq(通常是联合起来的:mask_ack_irq),这时候就算设备再次发出中断请求,也是在handle_irq_event结束,unmask irq之后,这时IRQ_INPROGRESS标志已经被清除。我不知道其他像X86之类的体系是否有不同的行为,有知道的朋友请告知我一下。以下是handle_level_irq经过简化之后的代码:
[cpp] view plaincopy
  1. void
  2. handle_level_irq(unsigned int irq, struct irq_desc *desc)
  3. {
  4. raw_spin_lock(&desc->lock);
  5. mask_ack_irq(desc);
  6. if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
  7. goto out_unlock;
  8. ......
  9. if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
  10. goto out_unlock;
  11. handle_irq_event(desc);
  12. if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))
  13. unmask_irq(desc);
  14. out_unlock:
  15. raw_spin_unlock(&desc->lock);
  16. }

虽然handle_level_irq对电平中断的流控进行了必要的处理,因为电平中断的特性:只要没有ack irq,中断线会一直有效,所以我们不会错过某次中断请求,但是驱动程序的开发人员如果对该过程理解不透彻,特别容易发生某次中断被多次处理的情况。特别是使用了中断线程(action->thread_fn)来响应中断的时候:通常mask_ack_irq只会清除中断控制器的pending状态,很多慢速设备(例如通过i2c或spi控制的设备)需要在中断线程中清除中断线的pending状态,但是未等到中断线程被调度执行的时候,handle_level_irq早就返回了,这时已经执行过unmask_irq,设备的中断线pending处于有效状态,中断控制器会再次发出中断请求,结果是设备的一次中断请求,产生了两次中断响应。要避免这种情况,最好的办法就是不要单独使用中断线程处理中断,而是要实现request_threaded_irq()的第二个参数irq_handler_t:handler,在handle回调中使用disable_irq()关闭该irq,然后在退出中断线程回调前再enable_irq()。假设action->handler没有屏蔽irq,以下这幅图展示了电平中断期间IRQ_PROGRESS标志、本地中断状态和触发其他CPU的状态:

图3.1  电平触发中断状态
上图中颜色分别代表不同的状态:
状态 红色 绿色
IRQ_PROGRESS            TRUE        FALSE
是否允许本地cpu中断             禁止                 允许  
是否允许该设备再次触发中断(可能由其它cpu响应)             禁止           允许

4.  handle_edge_irq

该函数用于处理边沿触发中断的流控操作。边沿触发中断的特点是,只有设备的中断请求引脚(中断线)的电平发生跳变时(由高变低或者有低变高),才会发出中断请求,因为跳变是一瞬间,而且不会像电平中断能保持住电平,所以处理不当就特别容易漏掉一次中断请求,为了避免这种情况,屏蔽中断的时间必须越短越好。内核的开发者们显然意识到这一点,在正是处理中断前,判断IRQ_PROGRESS标志没有被设置的情况下,只是ack irq,并没有mask irq,以便复位设备的中断请求引脚,在这之后的中断处理期间,另外的cpu可以再次响应同一个irq请求,如果IRQ_PROGRESS已经置位,表明另一个CPU正在处理该irq的上一次请求,这种情况下,他只是简单地设置IRQS_PENDING标志,然后mask_ack_irq后退出,中断请求交由原来的CPU继续处理。因为是mask_ack_irq,所以系统实际上只允许挂起一次中断。
[cpp] view plaincopy
  1. if (unlikely(irqd_irq_disabled(&desc->irq_data) ||
  2. irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
  3. if (!irq_check_poll(desc)) {
  4. desc->istate |= IRQS_PENDING;
  5. mask_ack_irq(desc);
  6. goto out_unlock;
  7. }
  8. }
  9. desc->irq_data.chip->irq_ack(&desc->irq_data);

从上面的分析可以知道,处理中断期间,另一次请求可能由另一个cpu响应后挂起,所以在处理完本次请求后还要判断IRQS_PENDING标志,如果被置位,当前cpu要接着处理被另一个cpu“委托”的请求。内核在这里设置了一个循环来处理这种情况,直到IRQS_PENDING标志无效为止,而且因为另一个cpu在响应并挂起irq时,会mask irq,所以在循环中要再次unmask irq,以便另一个cpu可以再次响应并挂起irq:

[cpp] view plaincopy
  1. do {
  2. ......
  3. if (unlikely(desc->istate & IRQS_PENDING)) {
  4. if (!irqd_irq_disabled(&desc->irq_data) &&
  5. irqd_irq_masked(&desc->irq_data))
  6. unmask_irq(desc);
  7. }
  8. handle_irq_event(desc);
  9. } while ((desc->istate & IRQS_PENDING) &&
  10. !irqd_irq_disabled(&desc->irq_data));

IRQS_PENDING标志会在handle_irq_event中清除。

图4.1   边沿触发中断状态
上图中颜色分别代表不同的状态:
状态         红色         绿色
IRQ_PROGRESS         TRUE         FALSE
是否允许本地cpu中断         禁止         允许
是否允许该设备再次触发中断(可能由其它cpu响应)         禁止         允许
是否处于中断上下文     处于中断上下文     处于进程上下文

由图4.1也可以看出,在处理软件中断(softirq)期间,此时仍然处于中断上下文中,但是cpu的本地中断是处于打开状态的,这表明此时嵌套中断允许发生,不过这不要紧,因为重要的处理已经完成,被嵌套的也只是软件中断部分而已。这个也就是内核区分top和bottom两个部分的初衷吧。

5.  handle_fasteoi_irq

现代的中断控制器通常会在硬件上实现了中断流控功能,例如ARM体系中的GIC通用中断控制器。对于这种中断控制器,CPU只需要在每次处理完中断后发出一个end of interrupt(eoi),我们无需关注何时mask,何时unmask。不过虽然想着很完美,事情总有特殊的时候,所以内核还是给了我们插手的机会,它利用irq_desc结构中的preflow_handler字段,在正式处理中断前会通过preflow_handler函数调用该回调。
[cpp] view plaincopy
  1. void
  2. handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
  3. {
  4. raw_spin_lock(&desc->lock);
  5. if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
  6. if (!irq_check_poll(desc))
  7. goto out;
  8. ......
  9. if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
  10. desc->istate |= IRQS_PENDING;
  11. mask_irq(desc);
  12. goto out;
  13. }
  14. if (desc->istate & IRQS_ONESHOT)
  15. mask_irq(desc);
  16. preflow_handler(desc);
  17. handle_irq_event(desc);
  18. out_eoi:
  19. desc->irq_data.chip->irq_eoi(&desc->irq_data);
  20. out_unlock:
  21. raw_spin_unlock(&desc->lock);
  22. return;
  23. ......
  24. }

此外,内核还提供了另外一个eoi版的函数:handle_edge_eoi_irq,它的处理类似于handle_edge_irq,只是无需实现mask和unmask的逻辑。

6.  handle_percpu_irq

该函数用于smp系统,当某个irq只在一个cpu上处理时,我们可以无需用自旋锁对数据进行保护,也无需处理cpu之间的中断嵌套重入,所以函数很简单:
[cpp] view plaincopy
  1. void
  2. handle_percpu_irq(unsigned int irq, struct irq_desc *desc)
  3. {
  4. struct irq_chip *chip = irq_desc_get_chip(desc);
  5. kstat_incr_irqs_this_cpu(irq, desc);
  6. if (chip->irq_ack)
  7. chip->irq_ack(&desc->irq_data);
  8. handle_irq_event_percpu(desc, desc->action);
  9. if (chip->irq_eoi)
  10. chip->irq_eoi(&desc->irq_data);
  11. }

7.  handle_nested_irq

该函数用于实现其中一种中断共享机制,当多个中断共享某一根中断线时,我们可以把这个中断线作为父中断,共享该中断的各个设备作为子中断,在父中断的中断线程中决定和分发响应哪个设备的请求,在得出真正发出请求的子设备后,调用handle_nested_irq来响应中断。所以,该函数是在进程上下文执行的,我们也无需扫描和执行irq_desc结构中的action链表。父中断在初始化时必须通过irq_set_nested_thread函数明确告知中断子系统:这些子中断属于线程嵌套中断类型,这样驱动程序在申请这些子中断时,内核不会为它们建立自己的中断线程,所有的子中断共享父中断的中断线程。

[cpp] view plaincopy
  1. void handle_nested_irq(unsigned int irq)
  2. {
  3. ......
  4. might_sleep();
  5. raw_spin_lock_irq(&desc->lock);
  6. ......
  7. action = desc->action;
  8. if (unlikely(!action || irqd_irq_disabled(&desc->irq_data)))
  9. goto out_unlock;
  10. irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
  11. raw_spin_unlock_irq(&desc->lock);
  12. action_ret = action->thread_fn(action->irq, action->dev_id);
  13. raw_spin_lock_irq(&desc->lock);
  14. irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
  15. out_unlock:
  16. raw_spin_unlock_irq(&desc->lock);
  17. }

Linux中断(interrupt)子系统之三:中断流控处理层相关推荐

  1. Linux中断(interrupt)子系统之四:驱动程序接口层 中断通用逻辑层

    在本系列文章的第一篇:Linux中断(interrupt)子系统之一:中断系统基本原理,我把通用中断子系统分为了4个层次,其中的驱动程序接口层和中断通用逻辑层的界限实际上不是很明确,因为中断通用逻辑层 ...

  2. Linux中断(interrupt)子系统之一:中断系统基本原理

    这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层.内核版本基于3.3.虽然内核的版本不断地提升,不 ...

  3. Linux中断(interrupt)子系统之一:中断系统基本原理【转】

    转自:http://blog.csdn.net/droidphone/article/details/7445825 这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于AR ...

  4. Linux中断(interrupt)子系统之二:arch相关的硬件封装层

    Linux的通用中断子系统的一个设计原则就是把底层的硬件实现尽可能地隐藏起来,使得驱动程序的开发人员不用关注底层的实现,要实现这个目标,内核的开发者们必须把硬件相关的内容剥离出来,然后定义一些列标准的 ...

  5. Linux 中断之中断处理浅析

    1. 中断的概念 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续 ...

  6. Linux时间子系统之三:时间的维护者:timekeeper

    专题文档汇总目录 Notes: 原文地址:Linux时间子系统之三:时间的维护者:timekeeper 本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内部时间的一些表示方法 ...

  7. 漫画-Linux中断子系统综述

    1.中断引发的面试教训 2.什么是中断? 中断: (英语:Interrupt)指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程. 即在程序运行过程中,系统出现了一个必须由 ...

  8. Linux中断子系统

    首先感谢原文作者 LoyenWang 的分享,可以点击章节阅读原作者原文,或者查看本文的转载地址,再次感谢原作者分享,已经在公众号上征得作者同意. 说明: Kernel版本:4.14 ARM64处理器 ...

  9. linux中断子系统(基于imx6ul arm32分析)

    0.说明 本文主要针对linux内核中断整个框架进行梳理,针对的是armv7架构,硬件平台是imx6ul,基于arm GIC控制器来分析. GIC是arm公司设计使用的中断控制器,全称Global I ...

最新文章

  1. js中对arry数组的各种操作小结
  2. leetcode1276. 不浪费原料的汉堡制作方案(贪心)
  3. JavaScript-Map和Set
  4. java的整型_java 整型
  5. python四分位数_分位函数(四分位数)概念与pandas中的quantile函数
  6. python locust 性能测试:HOOKS钩子方法
  7. 微信公众号-注册最全6种类型接口权限,注册哪个好?
  8. 利用BIRT ReportEngine API开发报表
  9. 【JavaWeb】1、XML、Tomcat
  10. 天刀服务器在线人数统计,天刀手游各大区活跃人数统计 神刀削弱后不降反增!...
  11. python柱状图标注均值标准差_OpenCV Python 图像矩阵的均值和标准差
  12. 晒一晒程序员桌面,你惊呆了没?
  13. 太实用了!Excel VBA常用代码!
  14. IOS 清理CALayer、CAShapeLayer的sublayers
  15. Unity 两张Texture叠加时用到的颜色混合
  16. 快过年了别着急玩耍,学会指针轻松一整年的学习
  17. VC删除注册表键值项
  18. matlab求状态反馈矩阵
  19. 大厂程序员元气满满的一天!
  20. 关于冗余和容错的一些总结(Redundancy and fault tolerance)

热门文章

  1. python读取yaml文件
  2. 使用py2neo构建neo4j图模型小demo
  3. Linux创建anaconda-navigator快捷图标并固定在dock上
  4. linux bash gt,linux之bash的基础特性(一)--gt;命令历史(history命令),命令补全,路径补全...
  5. python filter函数 字符串_Python实现filter函数实现字符串切分
  6. siege4安装和使用介绍
  7. QT调用百度语音REST API实现语音合成
  8. 基于Ajax+div的“左边菜单、右边内容”页面效果实现
  9. 使用LogParser分析IIS网站日志
  10. 各种字符串合并处理示例.sql