异常流程进阶 —— 进出异常时的压栈与出栈
在之前的文章:详解 Cortex-M 的异常流程 中介绍了 Cortex-M 处理器在执行异常时的大体流程,包括异常的进入、执行与返回。并且在进入与返回时提到了两个概念:压栈和出栈。而在上一篇文章:Cortex-M 异常处理的 C 实现、栈帧以及 EXC_RETURN 中已经详细解释了栈帧以及在异常返回时使用到的一个特殊值:EXC_RETURN。今天我们就来结合上述几个概念,对异常进出时的压栈与出栈操作进行一个进阶的理解。
异常进入和压栈
当异常产生且被处理器接受时,压栈流程会将寄存器压入栈中并组织栈帧,如下图所示:
Cortex-M3 和 Cortex-M4 处理器具有多个总线接口:
这样,在压栈操作的同时(通常系统总线),处理器还可以开始取向量(一般通过 I-CODE 总线)和取指。这样,由于压栈操作可以和 FLASH 存储器访问同时进行,因此哈佛总线架构可以降低中断等待时间。若向量表被重定向到 SRAM 或异常处理也位于 SRAM,则中断等待时间会略微增加。
需要注意的是,压栈期间的栈访问顺序和栈帧中寄存器的顺序不同。例如下图:
Cortex-M3 会在其他寄存器之前首先将 PC 和 xPSR 压栈,这样在取向量时就会尽可能快地更新 PC。由于 AHB Lite 接口的流水线特性,数据传输会至少落后地址一个时钟周期。
压栈操作中使用的栈可以为主栈(使用主栈指针, MSP)或进程栈(使用进程栈,PSP)。若处理器运行在线程模式且使用 MSP(CONTROL 寄存器的第 1 位为 0,这也是默认的配置),则压栈操作在执行时使用主栈 MSP,如下图所示:
若处理器运行在线程模式且使用进程栈(CONTROL 寄存器的第 1 位为 1),则压栈操作执行时使用进程栈 PSP。在进入处理模式后,处理器必须使用 MSP,所有嵌套中断的压栈操作执行时都使用主栈 MSP,如下图所示:
异常返回和出栈
在异常处理结束时,异常入口处生成的 EXC_RETURN 数值的 Bit2 用于确定提取栈帧时所使用的指针。若 Bit2 为 0,则处理器就知道之前压栈时使用的是主栈,如下图所示:
若 Bit2 为 1,处理器就会知道压栈时使用的是进程栈,可参考下图的第二个出栈操作:
在每次出栈操作结束时,处理器还会检查出栈 xPSR 数值的第 9 位,并且若压栈时插入了空位,则会将其去除,如下图所示:
为了降低出栈所需的时间,处理器会首先取出返回地址(压栈的 PC),从而可以使取指和剩下的出栈操作同时进行。