1. 异常类型

异常是能够引起程序程序流偏离正常流程的事件,当异常发生时,正在执行的程序就会被挂起,处理器转而执行一块与该事件相关的饿代码(异常处理)。事件可以是外部输入,也可以是内部产生的,外部产生的事件通常被称作中断或中断请求(IRQ)。

异常发生时,执行的软件代码为异常处理,而当异常处理与中断事件相关时,又可称作中断处理或者中断服务程序(ISR)。

当异常处理执行完后,就会返回到中断前的程序,并且继续执行之前的任务。因此,异常处理程序需要有种手段来记录中断前程序状态,并且在中断完成后能够将这些信息恢复。

Cortex-M0处理器内置了中断控制器,并且支持最多32个中断请求(IRQ)输入,以及一个不可屏蔽中断(NMI)输入。每个异常源都有一个单独的异常编号,NMI的编号为2,而片上外设和外部中断则为16-47。1~15的其它编号,用于处理器内部的系统异常。

异常编号表异常编号表.png

2. 异常流程概述

2.1 接受异常请求

处理器要接受一个异常,需要满足以下条件:

  • 对于中断和SysTick中断请求,中断必须使能;

  • 处理器正在执行的异常处理的优先级不能相同或更大;

  • PRIMASK中断屏蔽寄存器没有屏蔽掉异常;

2.2 压栈和出栈

当Cortex-M0处理器接受到一个异常以后,寄存器组中的一些寄存器(R0-R3、R12和R14)、返回地址(PC)以及程序状态寄存器(xPSR)会被自动压栈。

链接寄存器(LR/R14)则会被更新为异常返回时使用的特殊值(EXC_RETURN)来触发异常返回机制。处理器还会查看当前是否还有其它异常需要处理,若没有,处理器就恢复之前存储在栈空间的寄存器值,并继续执行中断前程序。

异常处理_压出栈异常处理_压出栈.png

2.3 末尾连锁

当其它异常处理完成后,还有异常处于挂起状态,这时处理器不会返回到中断前程序,而是重新进入异常处理流程,这也称作末尾连锁(Tail Channing)。

末尾连锁末尾连锁.png

2.4 延迟达到

延迟到达(Late arrival)是Cortex-M0的优化机制,它可以加快高优先级异常的处理,如果在低延迟优先级异常压栈过程中发生高优先级异常,处理器就会首先处理高优先级异常。

延迟到达延迟到达.png

由于每个中断都需要同样的压栈操作,后至的高优先级中断发生后将会继续之前的压栈过程。压栈完成后,高优先级的异常向量就会被取出以替代低优先级的那个。

如果没有延迟到达优化,在低优先级异常开始时,处理器必须抢占并且重新进入异常处理流程,这样会带来较长的延迟以及较大栈空间的使用。

2.5 EXC_RETURN

EXC_RETURN为架构定义的特殊值,用于异常返回机制,这个值在异常被接受并且压栈完成后会自动存储到链接寄存器(LR或R14)。

EXC_RETURN为32位数值,并且高28位置为1,第0-3位则提供了异常返回机制所需的信息。

EXC_RETURN寄存器EXC_RETURN寄存器.png

EXC_RETURN和合法值:

EXC_RETURN合法值.pngEXC_RETURN合法值.png

如下图,如果线程正在使用主栈(CONTROL寄存器的第一位为0),在进入第一个异常时,LR的值被置为0xFFFFFFF9,而再进入嵌套异常时则为0xFFFFFFF1。

中断嵌套异常值中断嵌套异常值.png

如下图,如果线程使用进程栈(CONTROL寄存器的第一位置1),在进入第一个异常时,LR的值被置为0xFFFFFFFD,而进入嵌套异常时则为0xFFFFFFF1。

返回线程栈返回线程栈.png

由于EXC_RETURN数值特殊格式,正常返回指令如果返回到0xFFFFFFFX范围地址,会被处理器当做异常返回,而不是普通的返回指令。由于0xFFFFFFFX范围内为保留的地址空间,在程序代码中不应出现,因此这也不是一个问题。

3. 异常入口流程

当异常发生时,以下的情况会随之发生:

  • 压栈并且栈指针更新;

  • 处理器取出异常向量并且将其写入PC;

  • 寄存器更新(LR、IPSR和NVIC寄存器);

3.1 寄存器压栈

当异常发生时,8个寄存器会被自动压栈,这些寄存器包括R0-R3、R12、R14(链接寄存器)、返回地址(下一条指令的地址或程序计数器)和程序状态寄存器(xPSR)。

对于嵌套异常,压栈时总是使用主栈,因为处理器当前处于处理模式,这种情况下只能使用主栈。

线程压栈线程压栈.png

压栈时保存到栈里的数据被统称为“栈帧(stack frame)”。在Cortex-M0处理器中,一个栈帧总是双字节对齐的,这样就能确保栈的使用遵循AAPCS标准。如果上一个压入的数据可能会处于非双字节对齐的,压栈机制就会将压栈的位置自动调整到下一个双字节对齐的地址上,并且在栈中的xPSR寄存器中设置标志,表明发生双字节栈调整。

栈帧字节对齐.png

在出栈过程中,处理器会检查栈中的xPSR的标志,并且根据标志的不同对栈指针做出相应的调整。

寄存器的压栈顺序,如下:

压栈寄存器压栈寄存器.png

当压栈结束后,栈指针会得到更新,并且主栈指针会被选择为当前栈指针(处理模式总是使用主栈),然后异常向量也会被取出来。

3.2 取出向量并更新PC

压栈完成后,处理器会从向量表中取出异常向量,然后将向量写入PC,并且将从这个地址开始异常处理的取指。

3.3 寄存器更新

异常处理完成后,LR的值会被更新为相应的EXC_RETURN值,这个值将会被用作异常返回,IPSR也会被更改为当前处理异常对应的异常编号。

4. 异常退出流程

当执行异常返回指令时(使用POP或者BX指令将EXC_RETURN加载到PC),异常退出流程就开始了,这个过程有两步:

  • 寄存器出栈

  • 恢复返回地址,取出并执行

4.1 寄存器出栈

使用POP将压栈过程中保存在栈的值取出,并恢复至相应的寄存器中。由于栈帧可以保存在主栈或者进程栈中,处理器会首先检查正在使用的EXC_RETURN的值。如果EXC_RETURN的第2位为0,处理器就开始从主栈中进行出栈操作;如果该位为1,则从进程栈中进行。

出栈完成后,栈指针需要调整。在压栈时,为了使栈帧为双字节对齐的,栈空间里可能包含了4字节的空隙。在这种情况下,栈中的xPSR的第9位为1,这样SP的值也相应去掉4字节的空隙。

另外,如果EXC_RETURN的第2,3位为1,表明异常退出将返回线程模式,当前的SP也应该切换回进程栈。

EXC_RETURN值,可参考章节: [2.5 EXC_RETURN]

4.2 从返回地址取值并执行

异常返回过程完成后,处理器将恢复的返回地址放到程序计数器中,并且继续执行中断之前的程序。

异常退出栈.png

注意:本站所有文章除特别说明外,均为原创,转载请务必以超链接方式并注明作者出处。 标签:ARM,Cortex-M0,Cortex-M0异常中断