4. ARMv7异常与中断处理:异常向量表、异常优先级、中断响应流程、FIQ与IRQ的区别

异常和中断,是嵌入式系统里最核心的机制之一。说白了,就是CPU在正常执行流里,突然被“拽”去处理一件更紧急的事。ARMv7的异常处理设计得相当精巧,尤其是FIQ和IRQ的区分,我在做高速数据采集卡时深有体会——选错中断类型,性能直接腰斩。

4.1 异常向量表:CPU的“紧急联络簿”

ARMv7的异常向量表,是一张固定在内存低地址(或高地址,由SCTLR.V控制)的表。每个异常类型对应一个32位的指令,通常是跳转指令。CPU检测到异常后,硬件自动跳转到对应表项执行。

这张表长这样:

地址偏移 异常类型 进入模式 优先级
0x00 复位 (Reset) SVC 1 (最高)
0x04 未定义指令 (Undef) Undef 6
0x08 软件中断 (SWI) SVC 6
0x0C 预取中止 (Prefetch Abort) Abort 5
0x10 数据中止 (Data Abort) Abort 2
0x14 保留 — —
0x18 IRQ IRQ 4
0x1C FIQ FIQ 3

关键点:每个表项只有4字节,所以通常放一条跳转指令(如 LDR PC, [PC, #offset] 或 B),跳到真正的处理函数。我见过新手直接把处理函数写在表项里,结果代码一超过4字节就把后面的表项覆盖了——调试时异常满天飞。

4.2 异常优先级:谁更紧急?

ARMv7的异常优先级是硬件固定的。复位最高,数据中止第二,FIQ第三,IRQ第四。为什么数据中止比FIQ优先级高?因为内存访问错误必须立即处理,否则系统状态可能彻底崩溃。

优先级顺序(从高到低):

  1. 复位 (Reset) — 最高,系统启动或重启
  2. 数据中止 (Data Abort) — 内存访问失败
  3. FIQ — 快速中断
  4. IRQ — 普通中断
  5. 预取中止 (Prefetch Abort) — 指令预取失败
  6. SWI 和 未定义指令 — 最低

注意:异常优先级只在同时发生多个异常时才有意义。一旦CPU进入某个异常处理,除非更高优先级的异常到来,否则不会被打断。我曾经在调试一个数据中止处理程序时,里面又触发了IRQ,结果IRQ被挂起直到数据中止处理完才响应——这其实是ARMv7的正常行为。

4.3 中断响应流程:硬件做了什么?

当IRQ或FIQ发生时,CPU硬件自动完成以下步骤:

  1. 保存返回地址:将当前PC保存到对应模式的LR寄存器(LR_irq 或 LR_fiq)
  2. 保存CPSR:将当前CPSR保存到SPSR_irq 或 SPSR_fiq
  3. 切换模式:CPU进入IRQ或FIQ模式,并禁用同级中断(IRQ模式下自动禁用IRQ,FIQ模式下自动禁用FIQ和IRQ)
  4. 跳转向量表:PC强制指向0x18(IRQ)或0x1C(FIQ)

处理完中断后,软件必须:

  1. 恢复现场(通用寄存器)
  2. 执行 SUBS PC, LR, #4 返回(注意IRQ和FIQ的LR需要减4,因为流水线原因)
  3. 恢复CPSR(硬件自动完成)

小技巧:我习惯在中断入口处先保存LR和SPSR到栈中,防止嵌套中断时被覆盖。虽然ARMv7的IRQ模式有自己的LR和SPSR,但一旦使能嵌套,这些寄存器就会被破坏。

4.4 FIQ与IRQ的区别:为什么FIQ更快?

FIQ(快速中断)和IRQ(普通中断)是ARMv7最经典的设计之一。FIQ的设计目标就是“快”,它比IRQ快在哪里?

特性 IRQ FIQ
优先级 较低(第4) 较高(第3)
向量表位置 0x18 0x1C(最后一项)
私有寄存器 无(共享R0-R12) 有R8-R12(FIQ模式独享)
中断屏蔽 只屏蔽IRQ 屏蔽FIQ和IRQ
典型用途 普通外设中断 高速数据流、DMA、实时性要求高的场景

FIQ快的核心原因有两个:

  • 寄存器银行:FIQ模式有自己专用的R8-R12,这意味着处理函数不需要压栈保存这些寄存器。IRQ模式则必须保存所有用到的寄存器。
  • 向量表位置:FIQ在向量表最后一项(0x1C),可以直接把处理函数放在表项后面,省去一次跳转。IRQ则必须跳转。

实战经验:我在做一款高速ADC采集卡时,数据采样率是10Msps,每次采样完成触发中断。一开始用IRQ,结果发现中断响应时间太长,导致数据丢失。后来改成FIQ,把R8-R12用于数据搬运,中断处理时间从2.3μs降到了0.8μs。嗯,这就是FIQ的威力。

4.5 异常处理流程图

下面这张图展示了ARMv7从异常发生到处理完成的完整流程:

ARMv7异常处理流程图 ARMv7异常处理流程 正常程序执行 异常/中断发生 硬件自动操作 保存PC到LR,CPSR到SPSR 切换模式,跳转向量表 向量表跳转 软件中断处理 保存现场 → 处理中断 → 恢复现场 SUBS PC, LR, #4 返回正常执行 说明 • 整个过程由硬件和软件 协同完成 • FIQ比IRQ少一次跳转

4.6 避坑指南:我踩过的那些坑

做ARMv7中断调试,有几个地方特别容易出问题:

  • LR没减4:IRQ和FIQ的返回地址需要 SUBS PC, LR, #4,因为流水线导致LR保存的是当前指令+8。我刚开始做时直接 MOV PC, LR,结果程序跑飞了。
  • SPSR没恢复:如果中断处理中修改了CPSR,返回时必须用 SUBS 指令(带S后缀),否则SPSR不会自动恢复到CPSR。
  • FIQ模式用了R0-R7:FIQ的快速优势在于R8-R12是私有的,如果处理函数里大量使用R0-R7,那和IRQ就没区别了。我建议FIQ处理函数尽量只用R8-R12。
  • 嵌套中断使能不当:在IRQ处理中使能IRQ(清除I位)可以实现嵌套,但必须确保栈空间足够。我曾经在嵌套中断里递归调用,结果栈溢出——嗯,那次调试花了我一整天。

个人习惯:我写中断处理函数时,会在入口处用 STMFD SP!, {R0-R12, LR} 保存所有寄存器,即使FIQ模式也这样做——虽然牺牲了一点性能,但调试时能省很多事。等系统稳定后,再优化成只保存必要的寄存器。

4.7 总结

ARMv7的异常和中断处理,核心就是向量表、优先级、硬件自动保存现场、软件恢复现场这四个环节。FIQ和IRQ的区别,说白了就是FIQ用硬件资源换速度——专用寄存器、更高优先级、更少的跳转。选哪个,取决于你的实时性要求。

我在项目中一般这样选:普通外设(UART、I2C、GPIO)用IRQ,高速数据流(DMA、ADC、以太网)用FIQ。记住一点:FIQ不是万能的,它的处理函数必须足够短,否则再快也没用。