精华内容
下载资源
问答
  • 0.1 复位(RESET)(优先级=1)当处理器复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行。复位异常中断通常用在下面种情况:系统加电时;系统复位时;跳转到复位中断向量处执行成为软...

    0. ARM异常中断的种类

    ARM支持7种异常中断,其中包括复位、未定义指令异常、软中断异常、预取指令中止、数据中止、IRQ、FIQ。

    0.1 复位(RESET)(优先级=1)

    当处理器复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行。复位异常中断通常用在下面几种情况:系统加电时;系统复位时;跳转到复位中断向量处执行成为软复位。

    0.2  未定义指令(优先级=6)

    当ARM处理器或者是系统中的协处理器认为当前指令未定义时,产生未定义的指令异常中断,可以通过改异常中断机制仿真浮点向量运算。

    0.3  软中断(优先级=6)

    这是一个由用户定义的中断指令。用于用户模式下的程序调用特权操作指令。在实时操作系统中可以通过该机制实现系统功能调用。

    0.4  预取指令终止(Prefech Abort)(优先级=5)

    如果处理器预取的指令的地址不存在,或者该地址不允许当前指令访问,当被预取的指令执行时,处理器产生指令预取终止异常中断。

    0.5  数据终止(Data Abort)(优先级=2)

    如果数据访问指令的目标地址不存在,,或者该地址不允许当前指令访问,处理器产生数据访问终止异常中断。

    0.6   IRQ(优先级=4)

    当处理器的外部中断请求引脚有效,而且CPSR的寄存器的I控制位被**时,处理器产生外部中断请求异常中断。系统中个外设通过该异常中断请求处理服务。

    0.7 FIQ(优先级=3)

    当处理器的外部快速中断请求引脚有效,而且CPSR的F控制位被**时,处理器产生外部中断请求异常中断。

    0.8  异常中断向量表及异常中断优先级

    中断向量表7个异常中断所在的工作模式和存放地址,见下表。每个异常中断的地址存放了一个跳转指令或者一个向PC寄存器中赋值的数据访问指令。通过这两种指令,程序将跳转到相应的异常中断处理程序处执行。当几个异常中断同时发生时,就必须按照一定的次序来处理这些异常中断,下面继续分析。

    ARM的异常处理机制,异常是每一种处理器都必须考虑的问题之一,关键在于如何让处理,返回地址在什么位置都是需要考虑的。一般异常发生后,CPU都会进行一系列的操作,这些操作有一部分是CPU自动完成,有一部分是需要我们程序员完成,在此逐一的分析。

    1.CPU自动完成的操作

    首先说明CPU会自动完成的部分,用ARM结构手册中的代码描述如下:

    R14_= return link                //这个可以参看寄存器的说明,两个作用

    SPSR_< exception_mode > = CPSR

    CPSR[4:0] = exception mode number

    CPSR[5] = 0 ;                              //ARM指令

    If ==Reset or Fiq then

    CPSR[6] = 1 ;                              //只有在复位和FIQ模式下才会关闭FIQ中断

    CPSR[7] = 1 ;                             //任何异常模式下都会关闭IRQ中断

    PC = exception vector address

    从上面的代码中我们可以发现CPU自动处理的过程包括如下:

    1)  拷贝CPSR到SPSR_

    2)  设置适当的CPSR位: 改变处理器状态进入ARM状态;改变处理器模式进入相应的异常模式;设置中断禁止位禁止相应中断。

    3)  更新LR_,这个寄存器中保存的是异常返回时的链接地址

    4)  设置PC到相应的异常向量

    以上的操作都是CPU自动完成,然后CPU跳转到程序员定义的异常处理程序(函数),处理完成后返回之前的工作模式,此时的异常返回地址是需要我们留意的地方。

    2.异常返回地址问题

    不同的异常模式返回地址存在差异,这主要是因为各种异常的产生机理存在差别。这样我们需要在进入异常处理函数之前或者异常返回前调整返回地址,一般采用进入异常处理函数前进行手动调整。下面给出了每一种异常时链接寄存器R14保存的值,根据R14的值就可以知道怎样实现地址的返回,其中也包含了CPU自动处理的部分。

    2.1 复位异常:

    可以看出该模式下的先对来说返回地址也比较简单,不需要做太多的描述。

    2.2 未定义的指令异常:

    返回的方式也比较简单:

    MOVS  PC, R14

    2.3软中断异常:

    返回的方式也比较简单:

    MOVS  PC, R14

    2.4 预取指令中止异常:

    返回需要做下面的调整:

    SUBS      PC, R14, #4

    2.5 数据中止

    返回地址需要做下面的调整:

    如果需要重新访问数据则:SUBS      PC, R14, #8

    如果不需要重新访问数据则:SUBS      PC, R14, #4

    2.6 IRQ中断的处理过程:

    返回地址需要做下面的调整:       SUBS PC,R14,#4

    2.7 FIQ中断:

    返回地址需要做下面的调整:       SUBS  PC, R14 ,#4

    从上面的代码可以知道,对于每一种异常,保存的返回地址都是不一样的,一般都需要我们手动的跳转,当然调整的时机也需要我们选择,是在进入处理前跳转还是返回时调整都是需要程序员控制。

    3.异常返回地址的再次说明

    在ARM Developer Suite Developer Guide中对ARM处理器的异常处理操作提供能更加详细的解释,每一种异常下的处理方式如下文描述:异常返回时另一个非常重要的问题是返回地址的确定,在前面曾提到进入异常时处理器会有一个保存LR 的动作,但是该保存值并不一定是正确的返回地址,下面以一个简单的指令执行流水状态图来对此加以说明。

    我们知道在ARM 架构里,PC值指向当前执行指令的地址加8处,也就是说, 当执行指令A(地址0x8000)时,PC 等于指令C 的地址(0x8008)。假如指令A 是“BL”指令,则当执行该指令时,会把PC(=0x8008)保存到LR 寄存器里面,但是接下去处理器会马上对LR 进行一个自动的调整动作:LR=LR-0x4。这样,最终保存在 LR 里面的是 B 指令的地址,所以当从 BL 返回时,LR 里面正好是正确的返回地址。同样的调整机制在所有LR自动保存操作中都存在,比如进入中断响应时,处理器所做的LR 保存中,也进行了一次自动调整,并且调整动作都是LR=LR-0x4。

    下面,我们对不同类型的异常的返回地址依次进行说明:假设在指令A 处(地址0x8000)发生了异常,进入异常响应后,LR 上经过调整保存的地址值应该是B 的地址0x8004。

    3.1 如果发生的是软件中断,即A 是“SWI”指令

    异常是由指令本身引起的,从 SWI 中断返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。

    MOVS pc, lr

    3.2 发生的是Undefined instruction异常

    异常是由指令本身引起的,从异常返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。

    MOVS pc, lr

    3.3 发生的是IRQ或FIQ中断

    因为指令不可能被中断打断,所以A指令执行完以后才能响应中断,此时PC已更新,指向指令D的地址(地址0x800C),LR 上经过调整保存的地址值是C 的地址0x8008。中断返回后应该执行B指令,所以返回操作是:

    SUBS pc, lr, #4

    3.4 发生的是Prefetch Abort异常

    该异常并不是处理器试图从一个非法地址取指令时触发,取出的指令只是被标记为非法,按正常处理流程放在流水线上,在执行阶段触发Prefetch Abort异常,此时LR 上经过调整保存的地址值是B 的地址0x8004。异常返回应该返回到A指令,尝试重新取指令,所以返回操作是:

    SUBS pc, lr, #4

    3.5  发生的是“Data Abort”

    CPU访问存储器时触发该异常,此时PC指向指令D的地址(地址0x800C),LR 上经过调整保存的地址值是C 的地址0x8008。异常返回后,应回到指令A,尝试重新操作存储器,所以返回操作是:

    SUBS pc, lr, #8

    以上就是ARM异常的CPU操作部分,接下来就是程序员应该完成的操作。

    4.程序员需要做的事

    1) 由于CPU会自动跳转到对应的异常向量中,因此只需要在在各个异常向量中存放对应的操作,最简单的都是存放一个B指令跳转到对应的异常处理函数的操作即可。但由于B指令的跳转返回只有+-32M,而异常处理函数的地址可能会超过+-32M,因此可以采用另一种方式实现方式:在异常向量中保存一条指令LDR PC [addr],其中的addr中就保存了异常处理函数的地址,当然addr的相对地址要小于+-32M。这样也就解决了跳转范围的问题。

    2) 接下来就是异常处理函数对应的操作,可以在进入异常处理之前就进行返回地址的调整,这样后面就不用进行处理啦,当然也可以在返回过程中再调整。一般都是在这个过程中进行调整。进行压栈操作,保存对应的环境变量。调用实际的处理过程等。

    3) 出栈,恢复CPU的状态和寄存器的值。由于第一步中已经调整好返回地址,这一步不需要再次调整。当然如果之前没有调整,这里则需要进行相应的调整。

    5.  uC/OS-II中的异常处理

    在uC/OS-II的官网移植中采用通用异常处理函数的方式实现异常的处理,我们来分析其中的部分代码:

    首先是处理器部分的移植,包括异常向量、异常的ID号,存储异常处理函数地址的地址等:

    /*ARM的异常ID号,支持7种类型的异常,每一种异常都存在一个ID号*/

    #define  OS_CPU_ARM_EXCEPT_RESET                   0x00

    #define  OS_CPU_ARM_EXCEPT_UNDEF_INSTR       0x01

    #define  OS_CPU_ARM_EXCEPT_SWI                       0x02

    #define  OS_CPU_ARM_EXCEPT_PREFETCH_ABORT 0x03

    #define  OS_CPU_ARM_EXCEPT_DATA_ABORT        0x04

    #define  OS_CPU_ARM_EXCEPT_ADDR_ABORT        0x05

    #define  OS_CPU_ARM_EXCEPT_IRQ                        0x06

    #define  OS_CPU_ARM_EXCEPT_FIQ                        0x07

    #define  OS_CPU_ARM_EXCEPT_NBR                       0x08

    /*异常向量地址*/

    #define  OS_CPU_ARM_EXCEPT_RESET_VECT_ADDR       (OS_CPU_ARM_EXCEPT_RESET          * 0x04 + 0x00)              //0x00

    #define  OS_CPU_ARM_EXCEPT_UNDEF_INSTR_VECT_ADDR   (OS_CPU_ARM_EXCEPT_UNDEF_INSTR    * 0x04 + 0x00) //0x04

    #define  OS_CPU_ARM_EXCEPT_SWI_VECT_ADDR                (OS_CPU_ARM_EXCEPT_SWI            * 0x04 + 0x00)         //0x08

    #define  OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_VECT_ADDR     (OS_CPU_ARM_EXCEPT_PREFETCH_ABORT * 0x04 + 0x00)         //0x0c

    #define  OS_CPU_ARM_EXCEPT_DATA_ABORT_VECT_ADDR         (OS_CPU_ARM_EXCEPT_DATA_ABORT     * 0x04 + 0x00)     //0x10

    /*这个异常是ARM中不支持的异常*/

    #define  OS_CPU_ARM_EXCEPT_ADDR_ABORT_VECT_ADDR         (OS_CPU_ARM_EXCEPT_ADDR_ABORT     * 0x04 + 0x00)                            //0x14

    #define  OS_CPU_ARM_EXCEPT_IRQ_VECT_ADDR      (OS_CPU_ARM_EXCEPT_IRQ    * 0x04 + 0x00)                  //0x18

    #define  OS_CPU_ARM_EXCEPT_FIQ_VECT_ADDR             (OS_CPU_ARM_EXCEPT_FIQ            * 0x04 + 0x00)                       //0x1c

    /*存储异常处理函数地址的地址*/

    /* ARM exception handlers addresses                   */

    #define  OS_CPU_ARM_EXCEPT_RESET_HANDLER_ADDR       (OS_CPU_ARM_EXCEPT_RESET    * 0x04 + 0x20)             //0x20

    #define  OS_CPU_ARM_EXCEPT_UNDEF_INSTR_HANDLER_ADDR     (OS_CPU_ARM_EXCEPT_UNDEF_INSTR    * 0x04 + 0x20)           //0x24

    #define  OS_CPU_ARM_EXCEPT_SWI_HANDLER_ADDR             (OS_CPU_ARM_EXCEPT_SWI            * 0x04 + 0x20)        //0x28

    #define  OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_HANDLER_ADDR  (OS_CPU_ARM_EXCEPT_PREFETCH_ABORT * 0x04 + 0x20)     //0x2c

    #define  OS_CPU_ARM_EXCEPT_DATA_ABORT_HANDLER_ADDR      (OS_CPU_ARM_EXCEPT_DATA_ABORT     * 0x04 + 0x20)            //0x30

    #define  OS_CPU_ARM_EXCEPT_ADDR_ABORT_HANDLER_ADDR      (OS_CPU_ARM_EXCEPT_ADDR_ABORT     * 0x04 + 0x20)           //0x34

    #define  OS_CPU_ARM_EXCEPT_IRQ_HANDLER_ADDR             (OS_CPU_ARM_EXCEPT_IRQ            * 0x04 + 0x20)           //0x38

    #define  OS_CPU_ARM_EXCEPT_FIQ_HANDLER_ADDR             (OS_CPU_ARM_EXCEPT_FIQ            * 0x04 + 0x20)           //0x3c

    /*存储在异常向量中的内容,实质上是LDR PC,[PC,#0x18]的机器码*/

    #define  OS_CPU_ARM_INSTR_JUMP_TO_SELF              0xEAFFFFFE

    /* ARM "Jump To Exception Handler" asm instruction    */

    #define  OS_CPU_ARM_INSTR_JUMP_TO_HANDLER         0xE59FF018

    异常的初始化函数,首先,完成了在异常向量中存储指令的操作,采用机器码的形式就能避免直接访问寄存器什么的,其次,完成在固定的地址处存放对应异常处理函数的地址。其中采用了赋值的形式也是需要注意的,采用的强制类型转换和指针相结合的形式。保证了是修改地址处的内容。而不是修改地址。

    /*初始化异常中断向量*/

    void  OS_CPU_InitExceptVect (void)

    {

    /*

    OS_CPU_ARM_EXCEPT_UNDEF_INSTR_VECT_ADDR是对应中断向量表的地址

    OS_CPU_ARM_INSTR_JUMP_TO_HANDLER是保存了对应的     OS_CPU_ARM_INSTR_JUMP_TO_HANDLER(实质上是一个指令)

    实质上就是在异常向量中存放了:LDR PC [PC, #0x18],也就是让PC指向对应的异常处理地址中的内容,也就是实现到实际处理函数的跳转。异常处理地址中存储了实际的异常处理函数的地址

    其他的异常也有相同的操作,OS_CPU_ARM_INSTR_JUMP_TO_HANDLER是一个指令的机器码形式

    */

    (*(INT32U *)OS_CPU_ARM_EXCEPT_UNDEF_INSTR_VECT_ADDR)       =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_UNDEF_INSTR_HANDLER_ADDR)    = (INT32U)OS_CPU_ARM_ExceptUndefInstrHndlr;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_SWI_VECT_ADDR)               =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_SWI_HANDLER_ADDR)            = (INT32U)OS_CPU_ARM_ExceptSwiHndlr;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_VECT_ADDR)    =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_HANDLER_ADDR) = (INT32U)OS_CPU_ARM_ExceptPrefetchAbortHndlr;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_DATA_ABORT_VECT_ADDR)        =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_DATA_ABORT_HANDLER_ADDR)     = (INT32U)OS_CPU_ARM_ExceptDataAbortHndlr;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_ADDR_ABORT_VECT_ADDR)        =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_ADDR_ABORT_HANDLER_ADDR)     = (INT32U)OS_CPU_ARM_ExceptAddrAbortHndlr;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_IRQ_VECT_ADDR)               =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_IRQ_HANDLER_ADDR)            = (INT32U)OS_CPU_ARM_ExceptIrqHndlr;

    /*在异常向量中存储对应的操作,实质上就是将PC值调转*/

    (*(INT32U *)OS_CPU_ARM_EXCEPT_FIQ_VECT_ADDR)               =         OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;

    (*(INT32U *)OS_CPU_ARM_EXCEPT_FIQ_HANDLER_ADDR)            = (INT32U)OS_CPU_ARM_ExceptFiqHndlr;

    }

    异常类型Mode异常向量内容

    IRQ异常IRQ0x00000018LDR PC,[PC,#0x18]或者0xE59FF018

    IFQ异常IFQ0x0000001CLDR PC,[PC,#0x18]

    0xE59FF018

    0x00000038Address of OS_CPU_ARM_ExceptIrqHndlr()

    0x0000003CAddress of OS_CPU_ARM_ExceptFiqHndlr()

    有必要的讨论一下,为什么在向量中存储的是指令: LDR PC,[PC,#0x18],我们从上面的地址可以知道,IRQ异常处理函数地址被存储到了0x00000038中,异常向量与该地址之间的差值是0x20,那么为什么在其中存储的值只是0x18呢?这还要讨论ARM的流水线结构,当前执行的命令相比PC指向的地址差0x08。也就是当前执行的指令的地址是PC-0x08.当PC指向异常向量以后(取值),还需要等待一个时钟(译码)之后才会被执行(真正意义上的执行操作),而这时PC值已经被更新了。指向了Vector+0x8的位置,因此我们可以知道,当执行向量中的代码时,这时PC=Vector+0x8,而这时相对于固定的0x20-0x08=0x18,这也就是为什么是LDR PC,[PC,#0x18],而不是LDR PC,[PC,#0x20].

    采用上面的例子说明IRQ的向量为0x00000018,而设定好的固定地址用来存储对应异常处理函数地址的地址是0x00000038,当CPU执行完PC = 0x00000018以后,还需要译码、才能被执行,这时候PC值已经更新为PC = 0x00000018 + 0x08;这时候固定地址距离PC的相对位置位0x00000038 – PC = 0x18,而该地址中保存了IRQ中断的通用处理函数OS_CPU_ARM_ExceptIrqHndlr()的地址,LDR PC,[PC,#0x18]这条指令是指将PC+0x18地址处的内容加载到PC中,实质上也就完成跳转到异常处理函数的操作。

    这样处理的好处是因为LDR的加载范围是一个固定值+-32M,我们不能保证异常处理程序的地址刚好在+-32M左右,采用这种LDR PC, ADDR(固定地址)的形式就能实现大范围的跳转操作。

    我们仅仅以FIQ中断处理的形式进行讨论,其他的异常有一定的相似性,只是在返回地址上存在差别。这段代码主要是完成寄存器的压栈,返回地址的调整,保存等操作。具体的看下面的分析:

    AREA CODE, CODE, READONLY

    CODE32

    OS_CPU_ARM_ExceptFiqHndlr

    ;修改中断返回地址,这属于进入真正处理函数前的返回地址调整,具体的返回地址依据前面保存的R14进行相应的修改。

    SUB     LR, LR, #4                 ; LR offset to return from this exception: -4.

    ;压栈操作

    STMFD   SP!, {R0-R12, LR}         ; Push working registers.

    ;保存链接寄存器

    MOV     R2, LR                   ; S**e link register.

    ;设置好ID号,这是非常必要的,只有这样才能辨别属于那种异常

    MOV     R0, #OS_CPU_ARM_EXCEPT_FIQ    ; Set exception ID

    /*跳转到通用的异常处理函数,传递的参数是异常ID号*/

    B    OS_CPU_ARM_ExceptHndlr      ; Branch to global exception handler.

    OS_CPU_ARM_ExceptHndlr(except_type)是一个通用的异常处理函数,可以对除了IRQ以外的其他异常进行控制操作。在这个通用处理函数中又调用了下面的函数OS_CPU_ARM_ExceptHndlr_BreakExcept()或者OS_CPU_ARM_ExceptHndlr_BreakTask()。这两个函数中又调用了通用处理函数OS_CPU_ExceptHndlr(),然后OS_CPU_ExceptHndlr()中调用具体的中断处理操作。

    一般的OS_CPU_ExceptHndlr()处理形式,可以认为是一个模板如下:

    void OS_CPU_ExceptHndlr (INT32U except_type)

    {

    /* Determine beh**ior according to exception type (except_type) */

    /* If an IRQ or FIQ ,具体的可能要使用中断向量等形式实现*/

    while (there are interrupting devices) {

    /* Clear interrupting device */

    OS_CPU_SR_INT_En(); /* Enable nesting, if desired */

    /* Handle interrupt */

    }

    }

    这是其中的一段关于IRQ中断的文字复述:

    IRQ中断的基本的流程图如下:

    以上的uC/OS-II异常处理就分析完了,这种方式的实现实质上是采用了通用模板的形式,这样实现出来的形式只需要控制一定的,具体的一些IRQ中断处理函数(如定时器,GPIO等的中断),与具体的厂商有很大的关系,有的厂商采用硬件寄存器的方式进行设计,有的采用软件方式实现,因此具体的中断。

    后面我再分析我们通常认为的中断(实质上就是IRQ和IFQ中某一个具体中断)处理方式。

    本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

    展开全文
  • [原创] Linux 中断子系统 (二)- 通用框架处理背景Read the fucking source code!--By 鲁迅A picture is worth a thousand words.--By 高尔基说明:Kernel 版本: 4.14ARM64 处理器, Contex-A53, 双核使用工具: Source ...

    [原创] Linux 中断子系统 (二)- 通用框架处理

    背景

    Read the fucking source code!

    --By 鲁迅

    A picture is worth a thousand words.

    --By 高尔基

    说明:

    Kernel 版本: 4.14

    ARM64 处理器, Contex-A53, 双核

    使用工具: Source Insight 3.5, Visio

    1. 概述

    [原创] Linux 中断子系统(一)- 中断控制器及驱动分析讲到了底层硬件 GIC 驱动, 以及 Arch-Specific 的中断代码, 本文将研究下通用的中断处理的过程, 属于硬件无关层. 当然, 我还是建议你看一下上篇文章.

    这篇文章会解答两个问题:

    用户是怎么使用中断的(中断注册)?

    外设触发中断信号时, 最终是怎么调用到中断 handler 的(中断处理)?

    2. 数据结构分析

    先来看一下总的数据结构, 核心是围绕着 struct irq_desc 来展开:

    ab7653affab982b574eb7acc55df2e04.gif

    Linux 内核的中断处理, 围绕着中断描述符结构 struct irq_desc 展开, 内核提供了两种中断描述符组织形式:

    打开 CONFIG_SPARSE_IRQ 宏(中断编号不连续), 中断描述符以 radix-tree 来组织, 用户在初始化时进行动态分配, 然后再插入 radix-tree 中;

    关闭 CONFIG_SPARSE_IRQ 宏(中断编号连续), 中断描述符以数组的形式组织, 并且已经分配好;

    不管哪种形式, 最终都可以通过 Linux irq 号来找到对应的中断描述符;

    图的左侧灰色部分, 主要在中断控制器驱动中进行初始化设置, 包括各个结构中函数指针的指向等, 其中 struct irq_chip 用于对中断控制器的硬件操作, struct irq_domain 与中断控制器对应, 完成的工作是硬件中断号到 Linux irq 的映射;

    图的上侧灰色部分, 中断描述符的创建(这里指 CONFIG_SPARSE_IRQ), 主要在获取设备中断信息的过程中完成的, 从而让设备树中的中断能与具体的中断描述符 irq_desc 匹配;

    图中剩余部分, 在设备申请注册中断的过程中进行设置, 比如 struct irqaction 中 handler 的设置, 这个用于指向我们设备驱动程序中的中断处理函数了;

    中断的处理主要有以下几个功能模块:

    硬件中断号到 Linux irq 中断号的映射, 并创建好 irq_desc 中断描述符;

    中断注册时, 先获取设备的中断号, 根据中断号找到对应的 irq_desc, 并将设备的中断处理函数添加到 irq_desc 中;

    设备触发中断信号时, 根据硬件中断号得到 Linux irq 中断号, 找到对应的 irq_desc, 最终调用到设备的中断处理函数;

    上述的描述比较简单, 更详细的过程, 往下看吧.

    3. 流程分析

    3.1 中断注册

    这一次, 让我们以问题的方式来展开:

    先来让我们回答第一个问题: 用户是怎么使用中断的?

    熟悉设备驱动的同学应该都清楚, 经常会在驱动程序中调用 request_irq()接口或者

    request_threaded_irq()

    接口来注册设备的中断处理函数;

    request_irq()/request_threaded_irq

    接口中, 都需要用到 irq, 也就是中断号, 那么这个中断号是从哪里来的呢? 它是 Linux irq, 它又是如何映射到具体的硬件设备的中断号的呢?

    先来看第二个问题: 设备硬件中断号到 Linux irq 中断号的映射

    ab7653affab982b574eb7acc55df2e04.gif

    硬件设备的中断信息都在设备树 device tree 中进行了描述, 在系统启动过程中, 这些信息都已经加载到内存中并得到了解析;

    驱动中通常会使用 platform_get_irq 或

    irq_of_parse_and_map

    接口, 去根据设备树的信息去创建映射关系(硬件中断号到 Linux irq 中断号映射);

    [原创] Linux 中断子系统(一)- 中断控制器及驱动分析提到过 struct irq_domain 用于完成映射工作, 因此在

    irq_create_fwspec_mapping

    接口中, 会先去找到匹配的 irq domain, 再去回调该 irq domain 中的函数集, 通常 irq domain 都是在中断控制器驱动中初始化的, 以 ARM GICv2 为例, 最终回调到

    gic_irq_domain_hierarchy_ops

    中的函数;

    如果已经创建好了映射, 那么可以直接进行返回 Linux irq 中断号了, 否则的话需要

    irq_domain_alloc_irqs

    来创建映射关系;

    irq_domain_alloc_irqs

    完成两个工作:

    针对 Linux irq 中断号创建一个 irq_desc 中断描述符;

    调用 domain->ops->alloc 函数来完成映射, 在 ARM GICv2 驱动中对应

    gic_irq_domain_alloc

    函数, 这个函数很关键, 所以下文介绍一下;

    gic_irq_domain_alloc 函数如下:

    ab7653affab982b574eb7acc55df2e04.gif

    gic_irq_domain_translate

    : 负责解析出设备树中描述的中断号和中断触发类型(边缘触发, 电平触发等);

    gic_irq_domain_map: 将硬件中断号和 Linux 中断号绑定到一个结构中, 也就完成了映射, 此外还绑定了 irq_desc 结构中的其他字段, 最重要的是设置了

    irq_desc->handle_irq

    的函数指针, 这个最终是中断响应时往上执行的入口, 这个是关键, 下文讲述中断处理过程时还会提到;

    根据硬件中断号的范围设置

    irq_desc->handle_irq

    的指针, 共享中断入口为 handle_fasteoi_irq, 私有中断入口为handle_percpu_devid_irq

    ;

    上述函数执行完成后, 完成了两大工作:

    硬件中断号与 Linux 中断号完成映射, 并为 Linux 中断号创建了 irq_desc 中断描述符;

    数据结构的绑定及初始化, 关键的地方是设置了中断处理往上执行的入口;

    再看第一个问题: 中断是怎么来注册的?

    设备驱动中, 获取到了 irq 中断号后, 通常就会采用 request_irq/request_threaded_irq 来注册中断, 其中 request_irq 用于注册普通处理的中断, request_threaded_irq 用于注册线程化处理的中断;

    在讲具体的注册流程前, 先看一下主要的中断标志位:#defineIRQF_SHARED0x00000080// 多个设备共享一个中断号, 需要外设硬件支持

    #defineIRQF_PROBE_SHARED0x00000100// 中断处理程序允许 sharing mismatch 发生

    #define__IRQF_TIMER0x00000200// 时钟中断

    #defineIRQF_PERCPU0x00000400// 属于特定 CPU 的中断

    #defineIRQF_NOBALANCING0x00000800// 禁止在 CPU 之间进行中断均衡处理

    #defineIRQF_IRQPOLL0x00001000// 中断被用作轮训

    #defineIRQF_ONESHOT0x00002000// 一次性触发的中断, 不能嵌套, 1)在硬件中断处理完成后才能打开中断; 2)在中断线程化中保持关闭状态, 直到该中断源上的所有 thread_fn 函数都执行完

    #defineIRQF_NO_SUSPEND0x00004000// 系统休眠唤醒操作中, 不关闭该中断

    #defineIRQF_FORCE_RESUME0x00008000// 系统唤醒过程中必须强制打开该中断

    #defineIRQF_NO_THREAD0x00010000// 禁止中断线程化

    #defineIRQF_EARLY_RESUME0x00020000// 系统唤醒过程中在 syscore 阶段 resume, 而不用等到设备 resume 阶段

    #defineIRQF_COND_SUSPEND0x00040000// 与 NO_SUSPEND 的用户共享中断时, 执行本设备的中断处理函数

    ab7653affab982b574eb7acc55df2e04.gif

    request_irq 也是调用

    request_threaded_irq

    , 只是在传参的时候, 线程处理函数 thread_fn 函数设置成 NULL;

    由于在硬件中断号和 Linux 中断号完成映射后, irq_desc 已经创建好, 可以通过 irq_to_desc 接口去获取对应的 irq_desc;

    创建 irqaction, 并初始化该结构体中的各个字段, 其中包括传入的中断处理函数赋值给对应的字段;

    __setup_irq 用于完成中断的相关设置, 包括中断线程化的处理:

    中断线程化用于减少系统关中断的时间, 增强系统的实时性;

    ARM64 默认开启了

    CONFIG_IRQ_FORCED_THREADING

    , 引导参数传入 threadirqs 时, 则除了 IRQF_NO_THREAD 外的中断, 其他的都将强制线程化处理;

    中断线程化会为每个中断都创建一个内核线程, 如果中断进行共享, 对应 irqaction 将连接成链表, 每个 irqaction 都有 thread_mask 位图字段, 当所有共享中断都处理完成后才能 unmask 中断, 解除中断屏蔽;

    3.2 中断处理

    当完成中断的注册后, 所有结构的组织关系都已经建立好, 剩下的工作就是当信号来临时, 进行中断的处理工作.

    来回顾一下[原创] Linux 中断子系统(一)- 中断控制器及驱动分析中的 Arch-specific 处理流程:

    ab7653affab982b574eb7acc55df2e04.gif

    中断收到之后, 首先会跳转到异常向量表的入口处, 进而逐级进行回调处理, 最终调用到 generic_handle_irq 来进行中断处理.

    generic_handle_irq 处理如下图:

    ab7653affab982b574eb7acc55df2e04.gif

    generic_handle_irq 函数最终会调用到 desc->handle_irq(), 这个也就是对应到上文中在建立映射关系的过程中, 调用

    irq_domain_set_info

    函数, 设置好了函数指针, 也就是 handle_fasteoi_irq 和handle_percpu_devid_irq

    ;

    handle_fasteoi_irq: 处理共享中断, 并且遍历 irqaction 链表, 逐个调用 action->handler()函数, 这个函数正是设备驱动程序调用

    request_irq/request_threaded_irq

    接口注册的中断处理函数, 此外如果中断线程化处理的话, 还会调用

    __irq_wake_thread()

    唤醒内核线程;

    handle_percpu_devid_irq

    : 处理 per-CPU 中断处理, 在这个过程中会分别调用中断控制器的处理函数进行硬件操作, 该函数调用 action->handler()来进行中断处理;

    来看看中断线程化处理后的唤醒流程吧__handle_irq_event_percpu->__irq_wake_thread:

    ab7653affab982b574eb7acc55df2e04.gif

    __handle_irq_event_percpu->__irq_wake_thread

    将唤醒 irq_thread 中断内核线程;

    irq_thread 内核线程, 将根据是否为强制中断线程化对函数指针 handler_fn 进行初始化, 以便后续进行调用;

    irq_thread 内核线程将

    while(!irq_wait_for_interrupt)

    循环进行中断的处理, 当满足条件时, 执行 handler_fn, 在该函数中最终调用 action->thread_fn, 也就是完成了中断的处理;

    irq_wait_for_interrupt

    函数, 将会判断中断线程的唤醒条件, 如果满足了, 则将当前任务设置成 TASK_RUNNING 状态, 并返回 0, 这样就能执行中断的处理, 否则就调用 schedule()进行调度, 让出 CPU, 并将任务设置成 TASK_INTERRUPTIBLE 可中断睡眠状态;

    3.3 总结

    中断的处理, 总体来说可以分为两部分来看:

    从上到下: 围绕 irq_desc 中断描述符建立好连接关系, 这个过程就包括: 中断源信息的解析 (设备树), 硬件中断号到 Linux 中断号的映射关系, irq_desc 结构的分配及初始化(内部各个结构的组织关系), 中断的注册(填充 irq_desc 结构, 包括 handler 处理函数) 等, 总而言之, 就是完成静态关系创建, 为中断处理做好准备;

    从下到上, 当外设触发中断信号时, 中断控制器接收到信号并发送到处理器, 此时处理器进行异常模式切换, 并逐步从处理器架构相关代码逐级回调. 如果涉及到中断线程化, 则还需要进行中断内核线程的唤醒操作, 最终完成中断处理函数的执行.

    来源: https://www.cnblogs.com/LoyenWang/p/13052677.html

    展开全文
  • 背景Read the fucking source code! –By 鲁迅A picture is worth a thousand words. –By 高尔基说明:Kernel版本:4.14ARM... 概述【原创】Linux中断子系统(一)-中断控制器及驱动分析讲到了底层硬件GIC驱动,以及A...

    背景

    Read the fucking source code! –By 鲁迅

    A picture is worth a thousand words. –By 高尔基

    说明:

    Kernel版本:4.14

    ARM64处理器,Contex-A53,双核

    使用工具:Source Insight 3.5, Visio

    1. 概述

    【原创】Linux中断子系统(一)-中断控制器及驱动分析讲到了底层硬件GIC驱动,以及Arch-Specific的中断代码,本文将研究下通用的中断处理的过程,属于硬件无关层。当然,我还是建议你看一下上篇文章。

    这篇文章会解答两个问题:

    用户是怎么使用中断的(中断注册)?

    外设触发中断信号时,最终是怎么调用到中断handler的(中断处理)?

    2. 数据结构分析

    先来看一下总的数据结构,核心是围绕着struct irq_desc来展开:

    8fe77136469b034c802cd4872fdda9b0.png

    Linux内核的中断处理,围绕着中断描述符结构struct irq_desc展开,内核提供了两种中断描述符组织形式:

    打开CONFIG_SPARSE_IRQ宏(中断编号不连续),中断描述符以radix-tree来组织,用户在初始化时进行动态分配,然后再插入radix-tree中;

    关闭CONFIG_SPARSE_IRQ宏(中断编号连续),中断描述符以数组的形式组织,并且已经分配好;

    不管哪种形式,最终都可以通过

    图的左侧灰色部分,主要在中断控制器驱动中进行初始化设置,包括各个结构中函数指针的指向等,其中struct irq_chip用于对中断控制器的硬件操作,struct irq_domain与中断控制器对应,完成的工作是硬件中断号到Linux irq的映射;

    图的上侧灰色部分,中断描述符的创建(这里指CONFIG_SPARSE_IRQ),主要在获取设备中断信息的过程中完成的,从而让设备树中的中断能与具体的中断描述符irq_desc匹配;

    图中剩余部分,在设备申请注册中断的过程中进行设置,比如struct irqaction中handler的设置,这个用于指向我们设备驱动程序中的中断处理函数了;

    中断的处理主要有以下几个功能模块:

    硬件中断号到Linux irq中断号的映射,并创建好irq_desc中断描述符;

    中断注册时,先获取设备的中断号,根据中断号找到对应的irq_desc,并将设备的中断处理函数添加到irq_desc中;

    设备触发中断信号时,根据硬件中断号得到Linux irq中断号,找到对应的irq_desc,最终调用到设备的中断处理函数;

    上述的描述比较简单,更详细的过程,往下看吧。

    3. 流程分析

    3.1 中断注册

    这一次,让我们以问题的方式来展开:

    先来让我们回答第一个问题:用户是怎么使用中断的?

    熟悉设备驱动的同学应该都清楚,经常会在驱动程序中调用request_irq()接口或者request_threaded_irq()接口来注册设备的中断处理函数;

    request_irq()/request_threaded_irq接口中,都需要用到irq,也就是中断号,那么这个中断号是从哪里来的呢?它是Linux irq,它又是如何映射到具体的硬件设备的中断号的呢?

    先来看第二个问题:设备硬件中断号到Linux irq中断号的映射

    b3bf7d38f7634e224eaca1fc273b631f.png

    硬件设备的中断信息都在设备树device tree中进行了描述,在系统启动过程中,这些信息都已经加载到内存中并得到了解析;

    驱动中通常会使用platform_get_irq或irq_of_parse_and_map接口,去根据设备树的信息去创建映射关系(硬件中断号到

    【原创】Linux中断子系统(一)-中断控制器及驱动分析提到过struct irq_domain用于完成映射工作,因此在irq_create_fwspec_mapping接口中,会先去找到匹配的irq domain,再去回调该irq domain中的函数集,通常irq domain都是在中断控制器驱动中初始化的,以ARM GICv2为例,最终回调到gic_irq_domain_hierarchy_ops中的函数;

    如果已经创建好了映射,那么可以直接进行返回irq_domain_alloc_irqs来创建映射关系;

    irq_domain_alloc_irqs完成两个工作:

    针对irq_desc中断描述符;

    调用domain->ops->alloc函数来完成映射,在ARM GICv2驱动中对应gic_irq_domain_alloc函数,这个函数很关键,所以下文介绍一下;

    gic_irq_domain_alloc函数如下:

    1c115f4d1611ca388c7ffc4dad1a1405.png

    gic_irq_domain_translate:负责解析出设备树中描述的中断号和中断触发类型(边缘触发、电平触发等);

    gic_irq_domain_map:将硬件中断号和linux中断号绑定到一个结构中,也就完成了映射,此外还绑定了irq_desc结构中的其他字段,最重要的是设置了irq_desc->handle_irq的函数指针,这个最终是中断响应时往上执行的入口,这个是关键,下文讲述中断处理过程时还会提到;

    根据硬件中断号的范围设置irq_desc->handle_irq的指针,共享中断入口为handle_fasteoi_irq,私有中断入口为handle_percpu_devid_irq;

    上述函数执行完成后,完成了两大工作:

    硬件中断号与Linux中断号完成映射,并为Linux中断号创建了irq_desc中断描述符;

    数据结构的绑定及初始化,关键的地方是设置了中断处理往上执行的入口;

    再看第一个问题:中断是怎么来注册的?

    设备驱动中,获取到了irq中断号后,通常就会采用request_irq/request_threaded_irq来注册中断,其中request_irq用于注册普通处理的中断,request_threaded_irq用于注册线程化处理的中断;

    在讲具体的注册流程前,先看一下主要的中断标志位:

    #define IRQF_SHARED0x00000080 //多个设备共享一个中断号,需要外设硬件支持

    #define IRQF_PROBE_SHARED0x00000100 //中断处理程序允许sharing mismatch发生

    #define __IRQF_TIMER0x00000200 //时钟中断

    #define IRQF_PERCPU0x00000400 //属于特定CPU的中断

    #define IRQF_NOBALANCING0x00000800 //禁止在CPU之间进行中断均衡处理

    #define IRQF_IRQPOLL0x00001000 //中断被用作轮训

    #define IRQF_ONESHOT0x00002000 //一次性触发的中断,不能嵌套,1)在硬件中断处理完成后才能打开中断;2)在中断线程化中保持关闭状态,直到该中断源上的所有thread_fn函数都执行完

    #define IRQF_NO_SUSPEND0x00004000 //系统休眠唤醒操作中,不关闭该中断

    #define IRQF_FORCE_RESUME0x00008000 //系统唤醒过程中必须强制打开该中断

    #define IRQF_NO_THREAD0x00010000 //禁止中断线程化

    #define IRQF_EARLY_RESUME0x00020000 //系统唤醒过程中在syscore阶段resume,而不用等到设备resume阶段

    #define IRQF_COND_SUSPEND0x00040000 //与NO_SUSPEND的用户共享中断时,执行本设备的中断处理函数

    9a09bf4e3f18843396830670bbcdd349.png

    request_irq也是调用request_threaded_irq,只是在传参的时候,线程处理函数thread_fn函数设置成NULL;

    由于在硬件中断号和Linux中断号完成映射后,irq_desc已经创建好,可以通过irq_to_desc接口去获取对应的irq_desc;

    创建irqaction,并初始化该结构体中的各个字段,其中包括传入的中断处理函数赋值给对应的字段;

    __setup_irq用于完成中断的相关设置,包括中断线程化的处理:

    中断线程化用于减少系统关中断的时间,增强系统的实时性;

    ARM64默认开启了CONFIG_IRQ_FORCED_THREADING,引导参数传入threadirqs时,则除了IRQF_NO_THREAD外的中断,其他的都将强制线程化处理;

    中断线程化会为每个中断都创建一个内核线程,如果中断进行共享,对应irqaction将连接成链表,每个irqaction都有thread_mask位图字段,当所有共享中断都处理完成后才能unmask中断,解除中断屏蔽;

    3.2 中断处理

    当完成中断的注册后,所有结构的组织关系都已经建立好,剩下的工作就是当信号来临时,进行中断的处理工作。

    来回顾一下【原创】Linux中断子系统(一)-中断控制器及驱动分析中的Arch-specific处理流程:

    a8b49a09f983a74fc6b31c11f6b17832.png

    中断收到之后,首先会跳转到异常向量表的入口处,进而逐级进行回调处理,最终调用到generic_handle_irq来进行中断处理。

    generic_handle_irq处理如下图:

    c93f80d5c699115959f64d3df3aa02a7.png

    generic_handle_irq函数最终会调用到desc->handle_irq(),这个也就是对应到上文中在建立映射关系的过程中,调用irq_domain_set_info函数,设置好了函数指针,也就是handle_fasteoi_irq和handle_percpu_devid_irq;

    handle_fasteoi_irq:处理共享中断,并且遍历irqaction链表,逐个调用action->handler()函数,这个函数正是设备驱动程序调用request_irq/request_threaded_irq接口注册的中断处理函数,此外如果中断线程化处理的话,还会调用__irq_wake_thread()唤醒内核线程;

    handle_percpu_devid_irq:处理per-CPU中断处理,在这个过程中会分别调用中断控制器的处理函数进行硬件操作,该函数调用action->handler()来进行中断处理;

    来看看中断线程化处理后的唤醒流程吧__handle_irq_event_percpu->__irq_wake_thread:

    a38654daed896751d087264b7576b217.png

    __handle_irq_event_percpu->__irq_wake_thread将唤醒irq_thread中断内核线程;

    irq_thread内核线程,将根据是否为强制中断线程化对函数指针handler_fn进行初始化,以便后续进行调用;

    irq_thread内核线程将while(!irq_wait_for_interrupt)循环进行中断的处理,当满足条件时,执行handler_fn,在该函数中最终调用action->thread_fn,也就是完成了中断的处理;

    irq_wait_for_interrupt函数,将会判断中断线程的唤醒条件,如果满足了,则将当前任务设置成TASK_RUNNING状态,并返回0,这样就能执行中断的处理,否则就调用schedule()进行调度,让出CPU,并将任务设置成TASK_INTERRUPTIBLE可中断睡眠状态;

    3.3 总结

    中断的处理,总体来说可以分为两部分来看:

    从上到下:围绕irq_desc中断描述符建立好连接关系,这个过程就包括:中断源信息的解析(设备树),硬件中断号到Linux中断号的映射关系、irq_desc结构的分配及初始化(内部各个结构的组织关系)、中断的注册(填充irq_desc结构,包括handler处理函数)等,总而言之,就是完成静态关系创建,为中断处理做好准备;

    从下到上,当外设触发中断信号时,中断控制器接收到信号并发送到处理器,此时处理器进行异常模式切换,并逐步从处理器架构相关代码逐级回调。如果涉及到中断线程化,则还需要进行中断内核线程的唤醒操作,最终完成中断处理函数的执行。

    欢迎关注个人公众号,不定期分享Linux内核机制文章

    展开全文
  • FIQ与IRQ中断

    千次阅读 2021-02-02 10:50:58
    一般的中断控制器里我们可以配置与控制器相连的某个中断输入是FIQ还是IRQ,所以一个中断是可以指定为FIQ或者IRQ的,为了合理,要求系统更快响应,自身处理所耗时间也很短的中断设置为FIQ,否则就设置了IRQ。...

    快速中断请求(Fast Interrupt Request,FIQ)外部中断请求IRQ
    FIQ和IRQ是两种不同类型的中断,ARM为了支持这两种不同的中断,提供了对应的叫做FIQ和IRQ处理器模式(ARM有7种处理模式)。
    一般的中断控制器里我们可以配置与控制器相连的某个中断输入是FIQ还是IRQ,所以一个中断是可以指定为FIQ或者IRQ的,为了合理,要求系统更快响应,自身处理所耗时间也很短的中断设置为FIQ,否则就设置了IRQ。
    如果该中断设置为了IRQ,那么当该中断产生的时候,中断处理器通过IRQ请求线告诉ARM,ARM就知道有个IRQ中断来了,然后ARM切换到IRQ模式运行。类似的如果该中断设置为FIQ,那么当该中断产生的时候,中断处理器通过FIQ请求线告诉ARM,ARM就知道有个FIQ中断来了,然后切换到FIQ模式运行。
    FIQ比IRQ有更高优先级,如果FIQ和IRQ同时产生,那么FIQ先处理。
    FIQ的中断向量地址在0x0000001C,而IRQ的在0x00000018。(也有的在FFFF001C以及FFFF0018),写过完整汇编系统的都比较明白这点的差别,18只能放一条指令,为了不与1C处的FIQ冲突,这个地方只能跳转,而FIQ不一样,1C以后没有任何中断向量表了,这样可以直接在1C处放FIQ的中断处理程序,由于跳转的范围限制,至少少了一条跳转指令。
    IRQ的响应并不及时,从Verilog仿真来看,IRQ会延迟几个指令周期才跳转到中断向量处,看起来像是在等预取的指令执行完。FIQ的响应不清楚,也许比IRQ快。
    中断延迟:从外部中断请求信号发出到执行对应的中断服务程序ISR的第一条指令所需要的时间。通过软件程序设计来缩短中断延迟的方法有:中断优先级和中断嵌套。
    FIQ和IRQ(外部中断模式)之间有很大的区别。FIQ模式必须尽快处理,处理结束后离开这个模式;IRQ模式可以被FIQ模式中断,但IRQ不能中断FIQ模式;为使FIQ模式响应更快,FIQ模式具有更多的影子(Shadow)寄存器。FIQ模式必须禁用中断;如果一个中断例程必须重新启用中断,应使用IRQ模式而不是FIQ模式。
    ARM体系中异常中断种类:
    复位(Reset):当处理器的复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行。复位异常中断通常用在下面两种情况下:系统加电时,系统复位时,跳转到复位中断向量处执行,称为软复位。
    未定义指令(Undefined):当ARM处理器或者是系统中的协处理器认为当前指令未定义时,产生未定义的指令异常中断。可以通过该异常中断机制仿真浮点向量运算。
    软中断(software interrupt):这是一个由用户定义的中断指令。可用于用户模式下的程序调用特权操作指令。在实时操作系统(RTOS)中可以通过该机制实现系统功能调用。
    指令预取中止:如果处理器预取的指令的地址不存在,或者该地址不允许当前指令访问,当该被预取的指令执行时,处理器产生指令预取中止异常中断。
    数据访问中止:如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问,处理器产生数据访问中止异常中断。
    外部中断请求(IRQ):当处理器的外部中断请求引脚有效,而且CPSR寄存器中的I控制位被清除时,处理器产生外部中断请求异常中断。系统中各个外设通常通过该异常中断请求处理器服务。
    快速中断请求(FIQ):当处理器的外部快速中断请求引脚有效,而且CPSR寄存器中的F控制位被清除时,处理器产生外部中断请求(FIQ)异常中断。

    异常中断向量表及异常中断优先级:
    中断异常向量表中指定了个异常中断及其处理程序的对应关系。它通常存放在存储地址的低端。在ARM体系中,异常中断向量表的大小为32个字节。其中,每个异常中断占据4个字节大小,保留4个字节空间。
    每个异常中断对应的中断向量表中的4个字节的空间中存放一个跳转指令或者一个向PC寄存器中赋值的数据访问指令。通常这两种指令,程序将跳转到相应的异常中断处理程序处执行。
    当几个异常中断同时发生时,就必须按照一定的次序来处理这些异常中断。在ARM中通过给异常中断赋予一定的优先级来实现这种处理次序。当然有异常中断是不可能同时发生的,如指令预取中止异常和软中断(SWI)异常中断是由同一条指令的执行触发的,它们是不可能同时发生的。处理器执行某个特定的异常中断的过程中,称为处理器处于特定的中断模式。各异常中断的中断向量地址以及中断的处理优先级如下表所示。
    在这里插入图片描述
    异常中断使用的寄存器:
    各异常中断对应着一定的处理器模式。应用程序通常运行在用户模式下。ARM中的处理器模式如下表所示。
    在这里插入图片描述
    进入和退出异常中断的过程:
    下面介绍处理器对于各种异常中断的响应过程以及从异常中断处理程序中返回的方法。对于不同的异常中断处理程序,返回地址以及使用的指令是不同的。
    ARM处理器对异常中断的响应过程如下:
    (1).保存处理器当前状态、中断屏蔽位以及各条件标志位。这是通过将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中实现的。各异常中断有自己的物理SPSR寄存器。
    (2).设置当前程序状态寄存器CPSR中相应的位。包括设置CPSR中的位,使处理器进入相应的执行模式;设置CPSR中的位,禁止IRQ中断,当进入FIQ模式时,禁止IRQ中断。
    (3).将寄存器lr_mode设置成返回地址。
    (4).将程序计数器(PC)值,设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序处执行。
    ARM中断处理流程
    一、Linux中断的简易模型:
    当一个IRQ产生后,CPU会首先自动跳转到IRQ的中断向量(这个中断向量是可设置的),在这个IRQ中断向量里又是一个跳转指令,CPU再次跳转,跳转后的代码主要完成的工作是保存一些寄存器,然后读取中断寄存器经过计算(这个计算并不是单纯的跟INTPND对应)得到中断号,然后跳转到一个中断处理的通用函数,并把中断号传过去(汇编向C传参数)。
    在这个通用处理函数里,根据中断号找到我们自己设定的中断处理函数,然后执行。
    我们注意到,最后一步linux会去找我们自己设定的中断处理函数,这个函数是我们事先通过注册来挂接在一个结构数组(这个数组里的元素个数即为中断号的个数)上,linux会去遍历这个数组(如果使用到了共享中断,即一个中断号上注册多个中断服务程序,则每个中断服务程序都会被执行到)。
    可以进一步归纳为,linux的中断机制可以分为两部分:
    1、定义的中断处理函数是如何注册到linux系统的;
    2、当中断发生时,linux如何自动跳转并找出中断号,然后根据中断号来找到注册在该中断号上的中断处理函数并执行;
    接下来将从两个方面来详细说明这个机制,
    第一个方面是,自己定义的中断处理函数是如何注册到系统中断去的(必须要进行注册,linux才能找到并执行);
    另一个方面是,中断发生时,linux具体会做些什么进而找到我们注册的函数并执行。
    二、Linux中断注册过程:
    设备驱动的中断注册一般在probe函数中实现,以TP为例:
    在这里插入图片描述
    TP注册成功后,通过串口可以知道得到的中断号为 irq=298
    在Linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义,其源码如下:
    在这里插入图片描述
    irq:要申请的硬件中断号;
    handler:向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev参数将被传递给它;
    flags:中断处理的属性,若设置了IRQF_DISABLED ,则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED,则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM,表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)
    name:设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称。
    在这里插入图片描述
    dev:在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。

    另一种更容易理解的说法:
    irq:就是中断号,什么意思,就是说你要放在irq_desc[]这个数组的哪里,换句话就是申请哪个中断号,
    irq_handler_t handler:这个就是中断处理函数句柄,flags中断标志位是用来决定中断类型的,比如是快速中断或者共享中断等属性,
    name:就是设备驱动的名称
    dev:就是设备id,主要用在共享中断里面用来制定中断服务需要参考的数据地址,简单点就是在调用中断服务函数的时候会当作参数床给中断服务函数。
    过程是怎样的呢?当用户调用这个函数的时候,内核会创建一个irqaction结构体,然后把它加入到irq_desc[]数组
    中就可以了。当CPU收到中断请求后就通过中断好找到中断例程描述符再找到中断函数执行即可。
    request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。
    request_irq()函数做了封装,该函数调用了request_threaded_irq()如下:
    在这里插入图片描述
    而其实最终将自己定义的中断服务函数加入到Linux中还是__setup_irq()这个函数,
    在这里插入图片描述
    从上面的函数可以看到,通过这个while循环,将自己定义的中断服务函数被加入到这个中断号的中断函数链表末尾,在__setup_irq这个函数里,完成一个irqaction的加入,从而我们中断产生时,linux能顺利找到我们自己定义的中断处理函数。
    三、底层中断处理分析
    Linux里有中断号的概念,在中断发生时,linux会执行所有注册在该中断号上的所有函数。
    那么linux里的中断向量是怎么设定的呢?中断号是怎么算处理的呢?为什么所有注册在中断号上的函数会在中断产生时被执行呢?这里我们将对这些问题仔细分析。
    在中断发生时,处理器的第一个动作是自动(硬件自己完成)跳转到该中断的中断向量,
    中断向量表的起始地址可以有两个位置:arm的异常和复位向量表有两种选择,一种是低端向量,向量地址位于0x00000000,另一种是高端向量,向量地址位于0xffff0000,
    Linux选择使用高端向量模式,也就是说,当异常发生时,CPU会把PC指针自动跳转到始于0xffff0000开始的某一个地址上。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:
    V=0—0x00000000~0x0000001C
    V=1—0xffff0000~0xffff001C
    当配置好了中断向量起始地址后,只要有中断发生,CPU就会自动跳到这中断向量处,不管这个地址处挂的是SDRAM或ROM。
    当设置好了中断向量起始地址后,我们就需要在这些地址上放上我们的中断向量,由于linux启动时,内核代码会有一个从ROM中搬到RAM中的过程,中断向量也需要般过来,实际上在linux中,向量表建立的函数为
    在这里插入图片描述
    中断的分发:
    中断是处理器核异常的一种,所以处理器设计中,外设中断引起处理器异常,处理器跳转到异常向量表的相应异常入口取指执行。
    我们主要来看产生中断后,处理器跳转到中断异常入口后的执行流程。
    对于arm处理器,执行流程如下:
    vector_irq —>irq_handler —> arch_irq_handler_default —> asm_do_IRQ —>handle_IRQ
    在arch_irq_handler_default中调用get_irqnr_and_base获取中断号,传给asm_do_IRQ作为参数。
    get_irqnr_and_base由板级支持包实现。
    到这里顺便提醒下,中断注意事项:
    1.首次申请中断时,默认情况中断是打开时能的。
    2.中断上半部要使用disable_irq_nosync()而不是disable_irq(),否则会导致死锁。
    3disable_irq()/enable_irq()可以多次嵌套调用,但是一定要在数量上配对。
    4.执行中断上半部时当前中断线是被屏蔽的,包括其他cpu,当前其他irq number不会被屏蔽。
    5.一个软中断不会抢占另一个软中断,唯一可抢占它的只有中断处理程序,因为在处理软中断处理程序的时候,中断是开启的,但它和中断处理程序一样不能休眠。本地软中断被禁止,但是其他处理器上可执行软中断,甚至是同类的中断,那么处理函数就会被执行两次,数据就会遭到破坏了。因此如果没必要,我们就用tasklet, 它的同一个处理函数不会在两个处理器上同时运行,这样也就避免了加锁的麻烦。
    中断向量表在arch/arm/kernel/entry_armv.S文件的结尾部分,如下:
    在这里插入图片描述
    不过要注意位于__vectors_start和__vectors_end之间的是真正的向量跳转表,位于__stubs_start和__stubs_end之间的是处理跳转的部分。
    例如:
    vector_stub irq,IRQ_MODE, 4
    以上这一句把宏展开后实际上就是定义了vector_irq,根据进入中断前的cpu模式,分别跳转到__irq_usr或__irq_svc。
    系统启动阶段,位于arch/arm/kernel/traps.c中的early_trap_init()被调用:
    在这里插入图片描述
    以上两个memcpy会把__vectors_start开始的代码拷贝到0xffff0000处,把__stubs_start开始的代码拷贝到0xFFFF0000+0x1000处,这样,异常中断到来时,CPU就可以正确地跳转到相应中断向量入口并执行他们。
    对于系统的外部设备来说,通常都是使用IRQ中断,所以我们只关注__irq_usr和__irq_svc,两个函数最终都会进入irq_handler这个宏:
    在这里插入图片描述
    接下来这个宏在entry-macro-multi.S,到这里后,就跳转到C调用了,asm_do_IRQ。
    在这里插入图片描述
    此函数在irq.c函数中实现,
    在这里插入图片描述
    在该文件下查看调用关系之后,最后来到关机函数:
    在这里插入图片描述
    接下来就简要的介绍一下三个结构体中重要变量的具体含义:
    struct irq_desc {
    structirq_data irq_data; //保存中断请求irq和chip相关的数据
    irq_flow_handler_t handle_irq; //指向一个跟当前设备中断触发电信号类型相关的函数,比方说如果是边沿触发,那么就指向一个边沿触发类型的函数.在handle_irq指向的函数内部会调用
    //设备特定的中断服务例程.特定平台linux系统在初始化阶段会提供handle_irq的具体实现

    structirqaction       *action;        /* IRQ action list */ irqaction链表,是对某一具体设备的中断处理的抽象,设备驱动程序会通过
                       //request_irq来向这个链表中注册自己的中断处理函数,这个action结构体中有个next成员变量,会把
                       //注册的ISR串连起来,当然如果此irq line上只有一个设备,那么这个action就对应这个设备的中断处理程序
    
        unsignedint           status_use_accessors;//处理中断时的irq状态都保存在这个成员变量中
        constchar             *name;        //会显示在/proc/interrupts中
    


    };

    struct irq_data {
    unsigned int irq; //中断请求号
    structirq_chip *chip; //当前中断来自的PIC,chip是对PIC的一个抽象,屏蔽硬件平台上PIC的差异,软件提供统一的对PIC操作的接口.这些函数接口主要用来屏蔽或启用当前
    //中断,设定外部设备中断触发电信号的类型,向发出中断请求的设备发送中断响应信号,平台初始化函数负责实现该平台对应的PIC函数,并安装对irq_desc数组中

    };

    struct irqaction {
    irq_handler_t handler; //中断服务例程,即我们熟知的ISR,当我们调用request_irq时会把我们实现的ISR注册到这里
    void *dev_id; //调用handler时传递的参数,在多个设备共享一个irq号时特别重要,设备驱动程序就是通过此成员变量来标识自己,在free_irq时用到
    structirqaction *next; //串连下一个action

    };
    通过以上的介绍,我们可以知道,一个中断对应两个层次,一个是handle_irq,一个是action.前者对应irqline上的处理动作,后者对应设备相关的中断处理,也就是说,一条irq line对应一个handle_irq,而这条irq line上可以挂载多个设备,即多个设备都可以通过同一个irqline产生中断,action成员变量用来串连挂载在此irq line上的各个设备驱动的ISR。如果irq line上只有一个设备驱动注册,那么这个action成员变量即是此设备驱动的ISR。
    接着desc->handle_irq(irq, desc)这个调用走,handle_irq是在__irq_set_handler_locked()函数中赋值,得到处理句柄。
    在这里插入图片描述
    到处中断处理的大致流程走完。

    ARM对异常(中断)的处理过程:
    1、初始化:
    1)设置中断源,使其可以产生中断
    2)设置中断控制器(使能屏蔽、优先级)
    3)设置CPU的中断总开关(使能中断)
    2、执行程序,CPU每执行一条指令都会检查是否有异常(中断)产生--------------------硬件实现
    3、中断产生-------->CPU检查到有异常(中断)产生,开始处理:
    1)CPU针对不同的异常,会跳到不同的异常向量地址执行------------------------------硬件实现
    异常向量:是一个跳转指令,再跳去执行某个函数
    2)该函数A处理内容:
    a)保存现场
    b)处理异常(中断)---------------------->调用不同的函数B-------------软件实现
    c)恢复现场
    3)函数B实现功能:
    a)判断中断源-------------------------------------软件实现
    b)调用执行相应的中断服务函数
    中断源:产生中断信号-------------->中断控制器---------------->CPU(CPU每执行一条指令都会去检查是否有异常产生-------硬件实现)----------------->检查到中断------->CPU跳到相应的中断向量地址执行(硬件实现)(存放跳转指令)----------->再跳到某个函数A(保存现场;调用函数B;恢复现场)------------->再跳到某个函数B(判断中断源;执行相应 的中断服务函数)

    展开全文
  • 本文主要来说明 IOIOIO 外设触发的中断,总的来说一个中断的起末会经历设备,中断控制器,CPU 三个阶段:设备产生中断信号,中断控制器翻译信号,CPU 来实际处理信号。 本文用 xv6xv6xv6 的实例来讲解多处理器下的...
  • Java 提供了几个异常处理特性,以try,catch 和 finally 关键字的形式内建于语言自身之中。Java 编程语言也允许创建新的自定义异常,并通过使用 throw 和 throws关键字抛出它们。在Java编程中,Java 的异常处理不...
  • 系统休眠(System Suspend)和设备中断处理作者:linuxer 发布于:2017-4-21 12:02分类:电源管理子系统一、设备IRQ的suspend和resume本小节主要解决这样一问题:在系统休眠过程中,如何suspend设备中断(IRQ)?...
  • 2.21实例功能前面例子中分别介绍了按键控制发光二极管的亮灭,但是我们注意到,在程序中需要一直检测按键的状态,这样明显的浪费了单片机的资源,...在本例中,通过利用外部中断实现单片机对按键事件的响应和处理...
  • 中断初始化及服务程序的编写5402DSP中断有两大类,一类是可屏蔽中断:可以用软件来屏蔽或开放的硬件和软件中断。在5402中有INT3 ~INT0(外部中断),BRINT0,BXINT0(缓冲串行口中断),TRINT,TXINTW(时分多路串行口中断)...
  • 那 Linux 系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是「上半部和下半部分」。 上半部用来快速处理中断,一般会暂时关闭中断请求,主要负责处理跟硬件紧密相关或者时间...
  • 本文为原创,转载请注明:http://blog.chinaunix.net/uid/26772321.html回顾上篇文章已经描述了中断描述符表和中断描述符数组的初始化,由于在初始化期间系统关闭了中断(通过设置CPU的EFLAGS寄存器的IF标志位为0),...
  • 底半部中断实现详解

    2021-05-10 19:19:28
    Linux 系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是上半部和下半部分。 上半部用来快速处理中断,一般会暂时关闭中断请求,主要负责处理跟硬件紧密相关或者时间敏感的事情...
  • 关于JAVA异常处理的20最佳实践在我们深入了解异常处理最佳实践的深层概念之前,让我们从一最重要的概念开始,那就是理解在JAVA中有三种一般类型的可抛类: 检查性异常(checked exceptions)、非检查性异常...
  • Linux Kernel 中断简析

    2021-05-10 22:34:31
    一、中断基础概念所谓中断,指CPU在执行程序过程中,出现了某些突发事件即待处理,CPU必须暂停当前的程序。转去处理突发事件,处理完毕后CPU又返回原程序中断的位置并继续执行。1、中断分类a -- 内部中断和外部...
  • 这样,既可以弄清和理解linux内核对中断响应和服务的总体的格局和安排,还可以顺着这个过程介绍内核中的一些相关的基础设施。对此二者的了解和理解,有助于读者对整个内核的理解。 这里,我们假定外设的驱动程序都...
  • Linux中断子系统

    2021-01-03 18:21:54
    首先感谢原文作者 LoyenWang 的分享,可以点击章节阅读原作者原文,或者查看本文的转载地址,再次感谢原作者分享,已经在公众号上征得作者同意。... SGI:软件产生的中断(software-generated inter..
  • linux内核5-中断

    2021-01-18 01:44:15
    文章目录概念1.1 中断机制与策略分离1.2 中断子系统1.3 中断向量和描述符表中断处理机制2.1 中断描述表初始化2.2 中断处理过程2.3 中断请求队列2.4 IRQ数据结构从异常、中断,系统调用返回代码分析中断返回中断下半...
  • 文章系原创,如需转载请注明:转载自”Blog of UnicornX” (http://unicornx.github.io/)最新更新于:2016-04-19主要参考LKD3LDD3本文的内核代码以2.6.32.6为准...构成软中断机制的核心元素:有关构成软中断机制的核...
  • 5Java异常处理

    2021-03-09 21:36:19
    五、异常异常概念总结:练习一:异常的体系问题:1.请描述异常的继承体系2....异常继承体系为:异常的根类是 java.lang.Throwable,其下有两子类:java.lang.Error 与 java.util.Exception 。而Exc...
  • 中断是系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求 1.2 中断的魅力 为什么要有中断呢?举生活中的例子感受一下中断的魅力 比如订了一份...
  • [专业课笔记] 单片机 第五章 中断系统正文 正文
  • Linux 中断管理机制GIC 硬件原理GIC,Generic Interrupt Controller。是ARM公司提供的一通用的中断控制器。主要作用为:接受硬件中断信号,并经过一定处...
  • 用一不可中断进程的案例,带你学习了 iowait(也就是等待 I/O 的 CPU 使用率)升高时的分析方法。这里你要记住,进程的不可中断状态是系统的一种保护机制,可以保证硬件的交互过程不被意外打断。所以,短时间的不...
  • linux内核-时钟中断

    2021-11-06 21:15:27
    在所有的外部中断中,时钟中断起着特殊的作用,其作用远非单纯的计时所能相比。当然,即使是单纯的计时也已经足够重要了。别的不说,没有正确的时间关系,你用来重建内核的工具make就不能正常运行了,因为make是靠...
  • 》中,进程可能会在不可中断状态保持很久,导致系统中出现大量不可中断进程,等待 I/O 的进程一般是不可中断状态。iowait的升高也导致CPU使用率高,除了 iowait,软中断(softirq)CPU 使用率升高也是最常见的一种...
  • 本文介绍瑞萨RH850/F1L用户手册(user manual)的CAN接口部分的中文翻译。 博主会持续更新该用户手册,直到整个翻译完成,有兴趣的朋友可持续关注...Section 6 Exceptions/Interrupts(异常/中断) 6.1 Features 响应事件
  • MC9S12XS128 事件处理

    2021-10-02 21:37:43
    MC9S12XS128 事件处理
  • 在DMA出现之前,CPU与外设之间的数据传送方式有程序传送方式、中断传送方式。CPU是通过系统总线与其他部件连接并进行数据传输。 1.1程序传送方式 程序传送方式是指直接在程序控制下进行数据的输入/输出操作。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 62,572
精华内容 25,028
关键字:

中断处理过程包括几个阶段