2. ARM Cortex-A 异常模型:异常类型、向量表与处理模式

ARM Cortex-A 处理器的异常模型是中断控制器 GIC 驱动开发的底层硬件基础。理解异常类型、向量表基地址以及处理模式与寄存器组的关系,是编写可靠中断处理程序的前提。本章将系统梳理 ARMv7-A 和 ARMv8-A 架构下的异常模型核心概念。

2.1 异常类型与优先级

ARM Cortex-A 处理器支持多种异常源,每种异常对应一个固定的优先级和入口地址。下表列出了 ARMv7-A 架构下的主要异常类型及其属性:

异常类型 异常模式 优先级(0最高) 触发条件
Reset SVC 0 上电或复位信号
Data Abort Abort 1 数据访问失效(MMU 缺页/权限错误)
FIQ FIQ 2 快速中断请求(GIC 可配置为 FIQ)
IRQ IRQ 3 普通中断请求(GIC 默认输出)
Prefetch Abort Abort 4 指令预取失效
Undefined Instruction Undefined 5 执行未定义指令
Supervisor Call (SVC) SVC 6 执行 SVC 指令(系统调用)
关键理解:FIQ 的优先级高于 IRQ,且拥有独立的寄存器组(R8~R14_fiq),因此 FIQ 处理程序无需保存/恢复这些寄存器,响应速度更快。在 GIC 驱动中,可以将高优先级中断映射为 FIQ 信号。

2.2 异常向量表基地址

异常向量表是处理器在发生异常时跳转的目标地址集合。ARM Cortex-A 支持两种向量表基地址配置方式:

  • 低向量(Low Vectors):基地址固定为 0x00000000,用于传统裸机或简单系统。
  • 高向量(High Vectors):基地址固定为 0xFFFF0000,通常由 Linux 内核使用。

在 ARMv7-A 中,通过协处理器 CP15 的 c1 寄存器(SCTLR)的 V 位选择:

MRC p15, 0, r0, c1, c0, 0   ; 读取 SCTLR
ORR r0, r0, #(1 << 13)      ; 设置 V 位,选择高向量
MCR p15, 0, r0, c1, c0, 0   ; 写入 SCTLR

向量表在内存中的布局如下(每个向量占用 4 字节,存放一条跳转指令):

ARM Cortex-A 异常向量表布局(基地址 VBAR) 偏移 异常类型 处理模式 0x00 Reset SVC 0x04 Undefined Instr Undefined 0x08 Supervisor Call SVC 0x0C Prefetch Abort Abort 0x10 Data Abort Abort 0x18 IRQ IRQ 0x1C FIQ FIQ 说明: • 每个向量表条目占用 4 字节,通常存放一条跳转指令(B 或 LDR PC) • VBAR(Vector Base Address Register)在 ARMv7-A 中通过 CP15 配置 • 偏移 0x14 和 0x20~0x3C 为保留项,用于扩展

2.3 异常处理模式与寄存器组

ARM Cortex-A 处理器在进入异常时,会自动切换到对应的处理器模式。每种模式拥有独立的寄存器组(Banked Registers),用于保存上下文。下表展示了各模式下的寄存器映射关系:

寄存器 User/System FIQ IRQ SVC Abort Undefined
R0~R7 R0~R7 R0~R7 R0~R7 R0~R7 R0~R7 R0~R7
R8~R12 R8~R12 R8_fiq~R12_fiq R8~R12 R8~R12 R8~R12 R8~R12
R13 (SP) R13_usr R13_fiq R13_irq R13_svc R13_abt R13_und
R14 (LR) R14_usr R14_fiq R14_irq R14_svc R14_abt R14_und
SPSR — SPSR_fiq SPSR_irq SPSR_svc SPSR_abt SPSR_und
驱动开发要点:在 GIC 中断处理程序中,IRQ 模式使用独立的 R13_irq(栈指针)和 R14_irq(链接寄存器)。这意味着中断处理程序必须使用 IRQ 模式下的栈,并在退出前恢复被中断任务的上下文。FIQ 模式拥有更多的私有寄存器(R8~R12),因此 FIQ 处理程序可以避免保存这些寄存器,从而获得更快的响应速度。

2.4 异常处理流程

当 ARM Cortex-A 处理器检测到异常时,硬件自动执行以下操作:

  1. 保存返回地址:将当前 PC 值存入对应模式的 R14(LR)寄存器。
  2. 保存 CPSR:将当前 CPSR 值存入对应模式的 SPSR 寄存器。
  3. 切换模式:修改 CPSR 的模式位,切换到对应的异常模式。
  4. 禁用中断:设置 CPSR 的 I 位(IRQ 禁用)和 F 位(FIQ 禁用),防止嵌套中断。
  5. 跳转向量表:根据异常类型,从向量表基地址 + 偏移处读取指令并执行。

以下是一个典型的 IRQ 中断处理程序入口代码示例:

.section .vectors, "ax"
.global _start

_start:
    ldr pc, reset_handler        @ 0x00: Reset
    ldr pc, undef_handler        @ 0x04: Undefined Instruction
    ldr pc, svc_handler          @ 0x08: Supervisor Call
    ldr pc, pabt_handler         @ 0x0C: Prefetch Abort
    ldr pc, dabt_handler         @ 0x10: Data Abort
    nop                          @ 0x14: Reserved
    ldr pc, irq_handler          @ 0x18: IRQ
    ldr pc, fiq_handler          @ 0x1C: FIQ

irq_handler:
    sub lr, lr, #4               @ 修正返回地址
    srsdb sp!, #0x12             @ 保存 LR_irq 和 SPSR_irq 到 IRQ 栈
    cpsid i, #0x13               @ 切换到 SVC 模式,仍禁用 IRQ
    push {r0-r3, r12, lr}       @ 保存被调用者寄存器

    @ 调用 GIC 中断处理函数
    bl gic_handle_irq

    pop {r0-r3, r12, lr}        @ 恢复寄存器
    rfeia sp!                    @ 从栈恢复 PC 和 CPSR
常见错误:在 IRQ 处理程序中直接使用 MOVS PC, LR 返回是错误的,因为 IRQ 模式下 LR 的值需要先减去 4(由于 ARM 流水线特性)。正确的做法是使用 SUBS PC, LR, #4 或通过栈恢复。

2.5 ARMv8-A 异常模型差异

ARMv8-A(AArch64)引入了全新的异常模型,与 ARMv7-A 有显著不同:

  • 异常级别(Exception Levels):EL0(用户态)、EL1(内核态)、EL2(虚拟化)、EL3(安全监控)。
  • 向量表基地址:通过 VBAR_EL1、VBAR_EL2、VBAR_EL3 分别配置。
  • 向量表大小:每个向量条目占用 128 字节(32 条指令),支持同步异常、IRQ、FIQ 和 SError。
  • 寄存器组:每个异常级别拥有独立的 SP_ELx、SPSR_ELx、ELR_ELx 寄存器。

下表对比了 ARMv7-A 和 ARMv8-A 在中断处理方面的关键差异:

特性 ARMv7-A (AArch32) ARMv8-A (AArch64)
异常模式 7 种处理器模式(FIQ/IRQ/SVC 等) 4 个异常级别(EL0~EL3)
向量表基地址 VBAR(CP15 寄存器) VBAR_EL1/EL2/EL3
向量条目大小 4 字节(1 条指令) 128 字节(32 条指令)
中断信号 IRQ / FIQ IRQ / FIQ / SError
返回指令 MOVS PC, LR / SUBS PC, LR, #4 ERET
栈指针 各模式独立 SP 各异常级别独立 SP_ELx
GIC 驱动适配:在 ARMv8-A 系统中,GIC 驱动需要根据当前异常级别配置中断路由。例如,在 EL1 下运行的 Linux 内核,GIC 中断被配置为 IRQ 信号,通过 VBAR_EL1 中的 IRQ 向量入口处理。在 EL3 下的安全监控程序(如 ARM Trusted Firmware)则使用 VBAR_EL3 处理 FIQ。

2.6 异常模型与 GIC 的交互

GIC 作为中断控制器,其输出信号(IRQ/FIQ)直接连接到处理器的异常输入引脚。下图展示了从外设中断到处理器异常处理的完整链路:

GIC 中断信号到 ARM 异常处理链路 外设中断源 SPI / PPI / SGI GIC-400/500 中断分发 + CPU 接口 优先级/使能/确认 Cortex-A 处理器 IRQ/FIQ 引脚 异常向量表 中断处理流程: 1. 外设产生中断请求(电平/脉冲)→ GIC 分发器接收 2. GIC 根据优先级仲裁,选择最高优先级中断 3. GIC CPU 接口向处理器核发送 IRQ/FIQ 信号 4. 处理器硬件自动保存上下文,跳转到异常向量表 5. 向量表中的跳转指令进入 GIC 驱动中断处理程序

在 GIC 驱动开发中,需要重点关注以下与异常模型相关的配置点:

  • 中断信号类型:GIC 可以配置为输出 IRQ 或 FIQ,驱动需根据处理器模式选择对应的向量表条目。
  • 中断优先级分组:GIC 支持优先级分组(Group 0/1),Group 0 中断通常路由到 FIQ,Group 1 路由到 IRQ。
  • 中断确认与完成:处理器在中断处理程序中必须通过 GIC CPU 接口寄存器(ICC_EOIR)发送中断完成信号,否则 GIC 不会发送后续中断。
  • 嵌套中断:在 IRQ 处理程序中,如果需要支持高优先级中断抢占,必须手动重新使能 IRQ(清除 CPSR 的 I 位),并保存/恢复嵌套上下文。
实战经验:在编写 GIC 中断处理程序时,建议在 IRQ 入口处立即读取 ICC_IAR 寄存器获取中断 ID,然后切换到 SVC 模式执行实际处理逻辑。这样可以避免在 IRQ 模式下长时间运行,减少对栈指针(R13_irq)的压力,并允许在 SVC 模式下安全地使能嵌套中断。