2. ARMv7异常处理模型:异常向量表、异常优先级、异常返回机制、FIQ与IRQ的区别
异常处理,说白了就是CPU在遇到突发状况时的应急响应机制。不管是外部设备来了中断,还是程序自己跑飞了(比如除零),CPU都得有一套标准的流程去处理。ARMv7的异常模型设计得相当精巧,我当年刚接触时也觉得有点绕,但摸清楚之后,你会发现它其实非常符合直觉。
2.1 异常向量表:CPU的“紧急联络簿”
异常向量表,你可以把它想象成一张贴在CPU门口的电话号码表。每种异常类型对应一个固定的入口地址。当异常发生时,CPU会强制跳转到这个地址去执行那里的指令。
ARMv7的向量表基址由VBAR(Vector Base Address Register)寄存器控制。默认情况下,它指向0x00000000或0xFFFF0000,但现代操作系统(比如Linux)都会重新设置它。
下面这张表列出了ARMv7中常见的异常类型及其优先级和向量偏移:
| 异常类型 | 向量偏移 | 优先级 | 触发场景 |
|---|---|---|---|
| 复位 (Reset) | 0x00 | 1 (最高) | 上电或复位按键 |
| 未定义指令 | 0x04 | 6 | CPU不认识这条指令 |
| 软件中断 (SVC) | 0x08 | 6 | 执行SVC指令,用户态请求内核服务 |
| 预取中止 | 0x0C | 5 | 取指令时地址或权限错误 |
| 数据中止 | 0x10 | 2 | 读写数据时地址或权限错误 |
| IRQ | 0x18 | 4 | 普通中断请求 |
| FIQ | 0x1C | 3 | 快速中断请求 |
2.2 异常优先级:谁更紧急谁先上
当多个异常同时发生时,CPU得有个裁决机制。ARMv7的优先级是硬件固定的,不能像Cortex-M系列那样软件配置。复位优先级最高,数据中止排第二,FIQ第三,IRQ第四。
这里有个细节容易忽略:FIQ的优先级高于IRQ。为什么?因为FIQ设计之初就是为了处理那些对实时性要求极高的场景,比如高速数据传输。FIQ在响应时,CPU会自动屏蔽IRQ,确保自己不被干扰。
优先级逻辑其实很简单:
- 高优先级异常可以抢占低优先级异常。
- 低优先级异常不能打断高优先级异常的处理。
- 同级异常不会互相嵌套(除非你手动修改CPSR)。
2.3 异常返回机制:怎么进去的,怎么出来
异常返回,核心就是恢复现场。CPU在进入异常时,会自动做几件事:
- 保存当前PC到
LR(链接寄存器),但会根据异常类型做偏移修正。 - 保存当前
CPSR到SPSR(保存的程序状态寄存器)。 - 切换处理器模式,并屏蔽某些中断。
返回时,我们需要手动恢复。ARMv7提供了两种标准返回方式:
- 方式一:
MOVS PC, LR—— 将LR的值赋给PC,同时将SPSR恢复回CPSR。适用于SVC、未定义指令等异常。 - 方式二:
SUBS PC, LR, #4—— 先修正LR(因为IRQ/FIQ返回时需要回退一条指令),再恢复。适用于IRQ和FIQ。
为什么IRQ/FIQ要减4?因为CPU在响应中断时,当前指令可能还没执行完。返回时得重新执行那条被中断的指令,所以PC要回退4字节。
// 对于IRQ/FIQ
LR_irq = PC + 4; // 硬件自动保存的值
返回地址 = LR_irq - 4; // 需要手动修正
// 对于SVC/未定义指令
LR_svc = PC + 4; // 硬件自动保存的值
返回地址 = LR_svc; // 直接返回即可
2.4 FIQ与IRQ的区别:快与稳的抉择
FIQ和IRQ是ARMv7里最常用的两种外部中断。很多人觉得它们只是名字不同,其实内部差异很大。我整理了一个对比表,方便你理解:
| 对比项 | IRQ | FIQ |
|---|---|---|
| 优先级 | 较低(4) | 较高(3) |
| 响应延迟 | 较慢(需保存更多寄存器) | 较快(硬件自动保存寄存器少) |
| 专用寄存器 | 无(使用通用寄存器) | 有(R8~R12是FIQ模式专用) |
| 中断屏蔽 | 只能被FIQ或更高优先级异常打断 | 进入时自动屏蔽IRQ |
| 典型应用 | 按键、UART、定时器等普通外设 | DMA、高速数据流、实时性要求高的任务 |
FIQ为什么快?因为它有自己专属的寄存器组(R8_fiq ~ R12_fiq)。进入FIQ时,CPU不需要把这些寄存器压栈,直接切换寄存器组就行。而IRQ没有专用寄存器,必须手动保存R0~R12,这就多了不少开销。
2.5 异常处理流程总览
为了让你对整个流程有个直观印象,我画了一张图。它展示了从异常发生到返回的完整路径:
这张图把整个流程串起来了。你仔细看,从异常发生到查表、硬件保存现场、跳转执行、最后返回,每一步都是环环相扣的。我当年调试一个中断嵌套的问题时,就是靠这张图一步步排查,才发现是返回地址没修正对。
2.6 实战中的避坑点
最后,分享几个我在项目中踩过的坑:
- 向量表对齐: VBAR寄存器要求向量表32字节对齐。如果你把向量表放在一个未对齐的地址上,CPU会直接跑飞。我曾经犯过这个错,查了整整一天。
- SPSR的保存: 如果你的异常处理函数里又调用了其他函数(嵌套),记得先保存SPSR,否则子函数返回时会覆盖它。
- FIQ的“快”是双刃剑: FIQ虽然快,但它的专用寄存器只有R8~R12。如果你的处理逻辑需要大量局部变量,反而会因为寄存器不够用而被迫压栈,得不偿失。
嗯,异常处理这块内容比较多,但只要你把向量表、优先级、返回机制这三个核心点吃透,剩下的就是多写代码多调试了。