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

这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层。内核版本基于3.3。虽然内核的版本不断地提升,不过自从上一次变更到当前的通用中断子系统后,大的框架性的东西并没有太大的改变

 

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

1.设备、中断控制器和CPU

一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器和CPU本身,下图展示了一个smp系统中的中断硬件的组成结构:


                                                                       图 1.1  中断系统的硬件组成

        设备  设备是发起中断的源,当设备需要请求某种服务的时候,它会发起一个硬件中断信号,通常,该信号会连接至中断控制器,由中断控制器做进一步的处理。在现代的移动设备中,发起中断的设备可以位于soc(system-on-chip)芯片的外部,也可以位于soc的内部,因为目前大多数soc都集成了大量的硬件IP,例如I2C、SPI、Display Controller等等。【说明I2C在SOC内部】

        中断控制器  中断控制器负责收集所有中断源发起的中断,现有的中断控制器几乎都是可编程的,通过对中断控制器的编程,我们可以控制每个中断源的优先级、中断的电器类型,还可以打开和关闭某一个中断源,在smp系统中,甚至可以控制某个中断源发往哪一个CPU进行处理。对于ARM架构的soc,使用较多的中断控制器是VIC(Vector Interrupt Controller),进入多核时代以后,GIC(General Interrupt Controller)的应用也开始逐渐变多。

        CPU  cpu是最终响应中断的部件,它通过对可编程中断控制器的编程操作,控制和管理着系统中的每个中断,当中断控制器最终判定一个中断可以被处理时,他会根据事先的设定,通知其中一个或者是某几个cpu对该中断进行处理,虽然中断控制器可以同时通知数个cpu对某一个中断进行处理,实际上,最后只会有一个cpu响应这个中断请求,但具体是哪个cpu进行响应可能是随机的,中断控制器在硬件上对这一特性进行了保证,不过这也依赖于操作系统对中断系统的软件实现。在smp系统中,cpu之间也通过IPI(inter processor interrupt)中断进行通信。


2.  IRQ编号

        系统中每一个注册的中断源,都会分配一个唯一的编号用于识别该中断,我们称之为IRQ编号。IRQ编号贯穿在整个Linux的通用中断子系统中。在移动设备中,每个中断源的IRQ编号都会在arch相关的一些头文件中,例如arch/xxx/mach-xxx/include/irqs.h。驱动程序在请求中断服务时,它会使用IRQ编号注册该中断,中断发生时,cpu通常会从中断控制器中获取相关信息,然后计算出相应的IRQ编号,然后把该IRQ编号传递到相应的驱动程序中。【中断需要注册?注册过程都发生了什么?   驱动程序使用IRQ编号注册中断的过程,本质是告诉内核,当IRQ num发生时,哪一个驱动的哪一个handler做出响应,IRQ是CPU从中断控制器获得的?????】


3.  在驱动程序中申请中断【换成注册某个中断更合适

Linux中断子系统向驱动程序提供了一系列的API,其中的一个用于向系统申请中断:

int request_threaded_irq(unsigned int irq, irq_handler_t handler,  
                         irq_handler_t thread_fn, unsigned long irqflags,  
                         const char *devname, void *dev_id)  

其中,

  • irq是要申请的IRQ编号,
  • handler是中断处理服务函数,该函数工作在中断上下文中,如果不需要,可以传入NULL,但是不可以和thread_fn同时为NULL;
  • thread_fn是中断线程的回调函数,工作在内核进程上下文中,如果不需要,可以传入NULL,但是不可以和handler同时为NULL;
  • irqflags是该中断的一些标志,可以指定该中断的电气类型,是否共享等信息;
  • devname指定该中断的名称;
  • dev_id用于共享中断时的cookie data,通常用于区分共享中断具体由哪个设备发起;

关于该API的详细工作机理我们后面再讨论。

4.  通用中断子系统(Generic irq)的软件抽象

在通用中断子系统(generic irq)出现之前,内核使用__do_IRQ处理所有的中断,这意味着__do_IRQ中要处理各种类型的中断,这会导致软件的复杂性增加,层次不分明,而且代码的可重用性也不好。事实上,到了内核版本2.6.38,__do_IRQ这种方式已经彻底在内核的代码中消失了。通用中断子系统的原型最初出现于ARM体系中,一开始内核的开发者们把3种中断类型区分出来,他们是:

  • 电平触发中断(level type)
  • 边缘触发中断(edge type)
  • 简易的中断(simple type)
后来又针对某些需要回应eoi(end of interrupt)的中断控制器,加入了fast eoi type,针对smp加入了per cpu type。把这些不同的中断类型抽象出来后,成为了中断子系统的流控层。要使所有的体系架构都可以重用这部分的代码,中断控制器也被进一步地封装起来,形成了中断子系统中的硬件封装层。我们可以用下面的图示表示通用中断子系统的层次结构:

                                       图 4.1  通用中断子系统的层次结构

        硬件封装层  它包含了体系架构相关的所有代码,包括中断控制器的抽象封装,arch相关的中断初始化,以及各个IRQ的相关数据结构的初始化工作cpu的中断入口也会在arch相关的代码中实现。

中断通用逻辑层通过标准的封装接口(实际上就是struct irq_chip定义的接口)访问并控制中断控制器的行为,体系相关的中断入口函数在获取IRQ编号后,通过中断通用逻辑层提供的标准函数,把中断调用传递到中断流控层中。我们看看irq_chip的部分定义:

struct irq_chip {  
    const char  *name;  
    unsigned int    (*irq_startup)(struct irq_data *data);  
    void        (*irq_shutdown)(struct irq_data *data);  
    void        (*irq_enable)(struct irq_data *data);  
    void        (*irq_disable)(struct irq_data *data);  
  
    void        (*irq_ack)(struct irq_data *data);  
    void        (*irq_mask)(struct irq_data *data);  
    void        (*irq_mask_ack)(struct irq_data *data);  
    void        (*irq_unmask)(struct irq_data *data);  
    void        (*irq_eoi)(struct irq_data *data);  
  
    int     (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);  
    int     (*irq_retrigger)(struct irq_data *data);  
    int     (*irq_set_type)(struct irq_data *data, unsigned int flow_type);  
    int     (*irq_set_wake)(struct irq_data *data, unsigned int on);  
        ......  
};  
看到上面的结构定义,很明显,它实际上就是对中断控制器的接口抽象,我们只要对每个中断控制器实现以上接口(不必全部),并把它和相应的irq关联起来上层的实现即可通过这些接口访问中断控制器。而且,同一个中断控制器的代码可以方便地被不同的平台所重用。

        中断流控层  所谓中断流控是指合理并正确地处理连续发生的中断,比如一个中断在处理中,同一个中断再次到达时如何处理,何时应该屏蔽中断,何时打开中断,何时回应中断控制器等一系列的操作。该层实现了与体系和硬件无关的中断流控处理操作,它针对不同的中断电气类型(level,edge......),实现了对应的标准中断流控处理函数,在这些处理函数中,最终会把中断控制权传递到驱动程序注册中断时传入的处理函数或者是中断线程中。目前内核提供了以下几个主要的中断流控函数的实现(只列出部分):

  • handle_simple_irq();  
  • handle_level_irq();  电平中断流控处理程序
  • handle_edge_irq();  边沿触发中断流控处理程序
  • handle_fasteoi_irq();  需要eoi的中断处理器使用的中断流控处理程序
  • handle_percpu_irq();  该irq只有单个cpu响应时使用的流控处理程序

        中断通用逻辑层  该层实现了对中断系统几个重要数据的管理,并提供了一系列的辅助管理函数。同时,该层还实现了中断线程的实现和管理,共享中断和嵌套中断的实现和管理,另外它还提供了一些接口函数,它们将作为硬件封装层和中断流控层以及驱动程序API层之间的桥梁,例如以下API:

  • generic_handle_irq();
  • irq_to_desc();
  • irq_set_chip();
  • irq_set_chained_handler();

        驱动程序API  该部分向驱动程序提供了一系列的API,用于向系统申请/释放中断,打开/关闭中断,设置中断类型和中断唤醒系统的特性等操作。驱动程序的开发者通常只会使用到这一层提供的这些API即可完成驱动程序的开发工作,其他的细节都由另外几个软件层较好地“隐藏”起来了,驱动程序开发者无需再关注底层的实现,这看起来确实是一件美妙的事情,不过我认为,要想写出好的中断代码,还是花点时间了解一下其他几层的实现吧。其中的一些API如下:

  • enable_irq();
  • disable_irq();
  • disable_irq_nosync();
  • request_threaded_irq();
  • irq_set_affinity();

这里不再对每一层做详细的介绍,我将会在本系列的其他几篇文章中做深入的探讨。

5.  irq描述结构:struct irq_desc

整个通用中断子系统几乎都是围绕着irq_desc结构进行,系统中每一个irq都对应着一个irq_desc结构,所有的irq_desc结构的组织方式有两种:

        基于数组方式  平台相关板级代码事先根据系统中的IRQ数量,定义常量:NR_IRQS,在kernel/irq/irqdesc.c中使用该常量定义irq_desc结构数组:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值