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

异常与中断处理,可以说是ARM处理器最核心的机制之一。我做了这么多年嵌入式,见过太多系统崩溃、响应超时的问题,十有八九都是中断处理没搞明白。说白了,处理器就是个“单线程”的家伙,它怎么同时响应键盘、定时器、网络数据包?靠的就是异常中断机制。

4.1 异常向量表——处理器的“紧急电话本”

异常向量表,你可以把它想象成一张“紧急电话本”。处理器遇到不同异常时,会查这个表,找到对应的处理函数入口。

ARM的异常向量表通常放在内存的低地址端(0x00000000开始),或者高地址端(0xFFFF0000开始)。具体位置由系统控制协处理器决定。我个人习惯把向量表放在低地址,调试起来更直观。

异常类型 向量地址 优先级 模式
复位 0x00000000 1(最高) SVC
未定义指令 0x00000004 6 UND
软件中断(SWI) 0x00000008 6 SVC
预取指令中止 0x0000000C 5 ABT
数据中止 0x00000010 2 ABT
IRQ 0x00000018 4 IRQ
FIQ 0x0000001C 3 FIQ

每个向量表项只有4字节空间。4字节能干嘛?放一条跳转指令呗。比如:

; 典型的ARM异常向量表
    LDR PC, Reset_Handler      ; 0x00 复位
    LDR PC, Undef_Handler      ; 0x04 未定义指令
    LDR PC, SWI_Handler        ; 0x08 软件中断
    LDR PC, Prefetch_Handler   ; 0x0C 预取中止
    LDR PC, Data_Handler       ; 0x10 数据中止
    NOP                        ; 0x14 保留
    LDR PC, IRQ_Handler        ; 0x18 IRQ
    LDR PC, FIQ_Handler        ; 0x1C FIQ

嗯,这里要注意:LDR PC, xxx 是一条伪指令,它会从内存中加载目标地址到PC。为什么不用 B 指令?因为 B 有跳转范围限制(±32MB),而 LDR PC 可以跳到任意地址。

核心要点:异常向量表必须放在正确的位置,且每个表项必须是跳转指令。我曾经见过一个项目,向量表被意外覆盖了,结果一触发中断就跑飞——那场面,真是“叫天天不应”。

4.2 异常优先级——谁先谁后?

多个异常同时发生时,处理器得有个“先来后到”的规矩。ARM的异常优先级从高到低是这样的:

  1. 复位(最高)—— 谁也别跟我抢
  2. 数据中止 —— 内存访问出问题了,得赶紧处理
  3. FIQ —— 快速中断,优先级高
  4. IRQ —— 普通中断
  5. 预取指令中止 —— 指令取址失败
  6. SWI 和 未定义指令(最低)—— 软件触发的,不急

你想想看,为什么复位优先级最高?这还用说吗,系统都重启了,其他异常都得靠边站。数据中止比FIQ还高,是因为内存访问错误如果不立即处理,可能导致数据损坏。

实战经验:我在做一款工业控制器时,发现FIQ和IRQ的优先级设置直接影响系统实时性。FIQ处理高速数据流,IRQ处理按键等低速事件。如果反过来,高速数据就可能丢包。

4.3 中断响应流程——处理器到底干了啥?

当一个中断发生时,处理器内部会执行一系列“自动操作”。我建议你把这个流程刻在脑子里:

  1. 保存现场:自动保存当前PC到LR(链接寄存器),保存CPSR到SPSR
  2. 切换模式:切换到IRQ或FIQ模式,并禁用同级中断
  3. 跳转向量表:PC指向对应的异常向量地址
  4. 执行中断服务程序(ISR):你写的处理代码
  5. 恢复现场:从SPSR恢复CPSR,从LR恢复PC

用代码表示就是:

; IRQ中断服务程序示例
IRQ_Handler:
    SUB   LR, LR, #4          ; 修正返回地址(ARM模式下)
    STMFD SP!, {R0-R12, LR}   ; 保存寄存器
    BL    C_IRQ_Handler       ; 调用C语言处理函数
    LDMFD SP!, {R0-R12, PC}^  ; 恢复寄存器并返回

这里有个坑:SUB LR, LR, #4 是必须的。为什么?因为ARM是流水线架构,中断发生时PC已经指向当前指令+8了。如果不减4,返回后会跳过一条指令。我曾经在这个问题上折腾了一整天,最后发现是流水线导致的偏移。

避坑指南:我曾经在STM32F4上调试一个中断,发现每次返回后程序都跑飞。查了半天,原来是中断返回时忘了用 ^ 后缀(表示从SPSR恢复CPSR)。记住:LDMFD SP!, {R0-R12, PC}^ 中的 ^ 不能少!

4.4 FIQ与IRQ的区别——快在哪里?

FIQ(Fast Interrupt Request)和IRQ(Interrupt Request)是ARM处理器的两种中断类型。很多人以为FIQ只是优先级高一点,其实远不止如此。

我总结了一下,FIQ比IRQ“快”在三个地方:

特性 IRQ FIQ
优先级 较低(4) 较高(3)
专用寄存器 无(共用R0-R12) 有(R8-R12专用)
向量表位置 0x18 0x1C(最后一项)
中断嵌套 可被FIQ打断 不可被打断
典型应用 普通外设中断 高速数据流、DMA

第一,FIQ有专用寄存器。 ARM在FIQ模式下提供了R8_fiq到R12_fiq这5个专用寄存器。这意味着进入FIQ中断时,不需要保存这5个寄存器,省去了压栈出栈的时间。IRQ模式下没有这个待遇,所有寄存器都得保存。

第二,FIQ在向量表的末尾。 你想想看,FIQ的向量地址是0x1C,而IRQ是0x18。这意味着你可以直接在FIQ向量地址处写代码,不用跳转。比如:

; FIQ可以直接在向量表处写代码
FIQ_Handler:
    ; 直接在这里处理,不用跳转
    ; 因为R8-R12是专用的,不用保存
    ; 处理完直接返回
    SUBS PC, LR, #4

而IRQ必须跳转到别处,多了一次跳转开销。

第三,FIQ优先级更高,且不可被中断。 一旦进入FIQ,处理器会自动禁用IRQ和FIQ。这意味着FIQ不会被任何中断打断,保证了实时性。IRQ则可以被FIQ打断。

我的建议:在实际项目中,把对时间要求苛刻的任务(比如高速ADC采样、PWM精确控制)放在FIQ里处理。把按键扫描、LED显示这类“不急”的任务放在IRQ里。我在一个电机控制项目中就是这么做的,效果非常好。

4.5 异常处理的“潜规则”

最后,分享几个我在项目中踩过的坑:

  • 中断服务程序要短小精悍:不要在ISR里做复杂运算,能用标志位就用标志位。我见过有人在ISR里做浮点运算,结果系统直接卡死。
  • 注意中断嵌套深度:虽然ARM支持中断嵌套,但嵌套太深会导致栈溢出。我一般限制嵌套不超过3层。
  • FIQ不是万能的:FIQ虽然快,但因为它不可被打断,如果处理时间过长,会阻塞其他中断。我建议FIQ处理时间控制在10微秒以内。
  • 向量表对齐:ARM要求向量表32字节对齐。如果不对齐,处理器可能跳转到错误地址。

好了,关于ARM异常与中断处理的核心内容就这些。记住一句话:中断是嵌入式系统的灵魂,搞懂了中断,你就掌握了ARM处理器的命脉。

ARM异常与中断处理核心流程 异常/中断发生 保存PC到LR,CPSR到SPSR 切换模式,禁用同级中断 查异常向量表,跳转ISR 执行中断服务程序(ISR) 恢复现场,返回原程序 关键说明 • 异常向量表在0x00或0xFFFF0000 • FIQ有专用寄存器R8-R12 • IRQ可被FIQ打断 • 复位优先级最高 • 数据中止优先级第二 • FIQ优先级第三 • IRQ优先级第四 • 预取中止优先级第五 • SWI/未定义指令最低 避坑提醒: • 向量表必须32字节对齐 • ISR要短小精悍 • 注意流水线偏移(减4) • 返回时用^恢复CPSR

专注资料整理