Linux中断(interrupt)子系统之三:中断流控处理层【转】

转自:http://blog.csdn.net/droidphone/article/details/7489756

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[-]

  1. 中断流控层简介
  2. handle_simple_irq
  3. handle_level_irq
  4. handle_edge_irq
  5. handle_fasteoi_irq
  6. handle_percpu_irq
  7. handle_nested_irq

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 plain copy

  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 plain copy

  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.   
  8. out_unlock:  
  9.     raw_spin_unlock(&desc->lock);  
  10. }  

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 plain copy

  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.   
  7.     if (unlikely(irqd_irq_inprogress(&desc->irq_data)))  
  8.             goto out_unlock;  
  9.         ......  
  10.   
  11.     if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))  
  12.         goto out_unlock;  
  13.   
  14.     handle_irq_event(desc);  
  15.   
  16.     if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))  
  17.         unmask_irq(desc);  
  18. out_unlock:  
  19.     raw_spin_unlock(&desc->lock);  
  20. }  

虽然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 plain copy

  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.   
  10. 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 plain copy

  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.   
  9.     handle_irq_event(desc);  
  10.   
  11. } while ((desc->istate & IRQS_PENDING) &&  
  12.      !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 plain copy

  1. void  
  2. handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)  
  3. {  
  4.     raw_spin_lock(&desc->lock);  
  5.   
  6.     if (unlikely(irqd_irq_inprogress(&desc->irq_data)))  
  7.         if (!irq_check_poll(desc))  
  8.             goto out;  
  9.         ......  
  10.     if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {  
  11.         desc->istate |= IRQS_PENDING;  
  12.         mask_irq(desc);  
  13.         goto out;  
  14.     }  
  15.   
  16.     if (desc->istate & IRQS_ONESHOT)  
  17.         mask_irq(desc);  
  18.   
  19.     preflow_handler(desc);  
  20.     handle_irq_event(desc);  
  21.   
  22. out_eoi:  
  23.     desc->irq_data.chip->irq_eoi(&desc->irq_data);  
  24. out_unlock:  
  25.     raw_spin_unlock(&desc->lock);  
  26.     return;  
  27.         ......  
  28. }  

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

6.  handle_percpu_irq

该函数用于smp系统,当某个irq只在一个cpu上处理时,我们可以无需用自旋锁对数据进行保护,也无需处理cpu之间的中断嵌套重入,所以函数很简单:

[cpp] view plain copy

  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.   
  6.     kstat_incr_irqs_this_cpu(irq, desc);  
  7.   
  8.     if (chip->irq_ack)  
  9.         chip->irq_ack(&desc->irq_data);  
  10.   
  11.     handle_irq_event_percpu(desc, desc->action);  
  12.   
  13.     if (chip->irq_eoi)  
  14.         chip->irq_eoi(&desc->irq_data);  
  15. }  

7.  handle_nested_irq

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

[cpp] view plain copy

    1. void handle_nested_irq(unsigned int irq)  
    2. {  
    3.     ......  
    4.         might_sleep();  
    5.   
    6.     raw_spin_lock_irq(&desc->lock);  
    7.         ......  
    8.     action = desc->action;  
    9.     if (unlikely(!action || irqd_irq_disabled(&desc->irq_data)))  
    10.         goto out_unlock;  
    11.   
    12.     irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);  
    13.     raw_spin_unlock_irq(&desc->lock);  
    14.   
    15.     action_ret = action->thread_fn(action->irq, action->dev_id);  
    16.   
    17.     raw_spin_lock_irq(&desc->lock);  
    18.     irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);  
    19.   
    20. out_unlock:  
    21.     raw_spin_unlock_irq(&desc->lock);  
时间: 2024-11-01 09:39:41

Linux中断(interrupt)子系统之三:中断流控处理层【转】的相关文章

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

转自:http://blog.csdn.net/DroidPhone/article/details/7445825 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 设备中断控制器和CPU IRQ编号 在驱动程序中申请中断 通用中断子系统Generic irq的软件抽象 irq描述结构struct irq_desc 中断子系统的proc文件接口 这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区

Linux中断(interrupt)子系统之四:驱动程序接口层 & 中断通用逻辑层【转】

转自:http://blog.csdn.net/droidphone/article/details/7497787 在本系列文章的第一篇:Linux中断(interrupt)子系统之一:中断系统基本原理,我把通用中断子系统分为了4个层次,其中的驱动程序接口层和中断通用逻辑层的界限实际上不是很明确,因为中断通用逻辑层的很多接口,既可以被驱动程序使用,也可以被硬件封装层使用,所以我把这两部分的内容放在一起进行讨论. 本章我将会讨论这两层对外提供的标准接口和内部实现机制,几乎所有的接口都是围绕着ir

Linux 中断详解 【转】

转自:http://blog.csdn.net/tiangwan2011/article/details/7891818 原文地址 http://www.yesky.com/20010813/192117.shtml 方法之三:以数据结构为基点,触类旁通 结构化程序设计思想认为:程序 =数据结构 +算法.数据结构体现了整个系统的构架,所以数据结构通常都是代码分析的很好的着手点,对Linux内核分析尤其如此.比如,把进程控制块结构分析清楚 了,就对进程有了基本的把握:再比如,把页目录结构和页表结构

linux中断--中断原理分析

  中断之原理篇 前言: 中断是计算机发展中一个重要的技术,它的出现很大程度上解放了CPU,提高了CPU的执行效率. 在中断出现之前,CPU对IO采用的是轮询的方式进行服务,这使的CPU纠结在某一个IO上,一直在等待它的响应,如果它不响应,CPU就在原地一直的等下去.这样就导致了其他IO口也在等待CPU的服务,如果某个IO出现了important or emergency affairs,CPU也抽不出身去响应这个IO. 为了解决这个纠结的问题就------>出现了中断 中断控制的主要优点是只有

浅谈Linux内核无线子系统

Linux 内核是如何实现无线网络接口呢?数据包是通过怎样的方式被发送和接收呢?今天跟着 LinuxStory 小编一起来探索一番吧! 刚开始工作接触 Linux 无线网络时,我曾迷失在浩瀚的基础代码中,寻找具有介绍性的材料来回答如上面提到的那些高层次的问题.跟踪探索了一段时间的源代码后,我写下了这篇总结,希望在 Linux 无线网络的工作原理上,读者能从这篇文章获得一个具有帮助性的概览. 1 全局概览 在开始探索 Linux 无线具体细节之前,让我们先来把握一下 Linux 无线子系统整体结构

理解Linux中断 (2)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425692 版权声明:本文为博主原创文章,未经博主允许不得转载. 3.内核的中断处理 3.1.中断处理入口 由上节可知,中断向量的对应的处理程序位于interrupt数组中,下面来看看interrupt: [html] view plain copy print? 341 .data #数据段 342 ENTRY(interrupt) 343 .text 344 345 vector=0 3

linux中断系统那些事之----中断处理过程【转】

转自:http://blog.csdn.net/xiaojsj111/article/details/14129661 以外部中断irq为例来说明,当外部硬件产生中断时,linux的处理过程.首先先说明当外部中断产生时,硬件处理器所做的工作如下: R14_irq = address of next instruction to be executed + 4/*将寄存器lr_mode设置成返回地址,即为当前pc的值,因为pc是当前执行指令的下两条指令*/        SPSR_irq = CP

linux中断--中断嵌套&中断请求丢失

  关于中断嵌套: 在linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被立即处理,还是被pending,等当前中断处理完成后,再做处理. 在2.4和2.6内核里,关于这一块是否有什么不同. 一般申请中断的时候都允许开中断,即不使用SA_INTERRUPT标志.如果允许共享则加上 SA_SHIRQ,如果可以为内核熵池提供熵值(譬如你写的驱动是i

linux中断--内核中断编程

Linux中断内核编程 前言 在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序: 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法.如果不熟悉的话请大家看,module_param()的学习和Linux内核模块编程,在此不作解释. 1.程序interrupt.c [c-sharp] view plaincopy 1 /* 2 *file name :inte