2. ARMv8 异常模型:异常等级与向量表
ARMv8 架构的异常模型是理解 GICv3 中断控制器驱动的基础。异常(Exception)是处理器在执行指令流时,由于内部或外部事件而需要暂停当前程序流,转而执行特定处理程序(Handler)的机制。ARMv8 通过异常等级(Exception Level, EL)和异常向量表(Exception Vector Table)实现了对系统资源的分级保护与隔离。
2.1 异常等级(EL0 – EL3)
ARMv8 定义了四个异常等级,数字越大,特权越高。每个异常等级拥有独立的虚拟内存地址空间、系统寄存器视图和物理地址空间访问权限。
| 异常等级 | 名称 | 典型用途 | 执行状态 |
|---|---|---|---|
| EL0 | 用户态(User) | 应用程序(App) | AArch64 / AArch32 |
| EL1 | 内核态(Kernel) | 操作系统内核(Linux Kernel) | AArch64 / AArch32 |
| EL2 | 虚拟机监控器(Hypervisor) | 虚拟化层(KVM / Xen) | AArch64 |
| EL3 | 安全监控器(Secure Monitor) | 安全固件(ARM Trusted Firmware) | AArch64 |
关键概念:在 GICv3 中断控制器驱动中,中断处理程序通常运行在 EL1(内核态)。当发生安全中断(Secure Interrupt)时,处理会陷入 EL3(安全监控器),由 ATF 进行路由和分发。
异常等级之间通过异常陷入(Exception Trap)和异常返回(ERET)进行切换。低等级向高等级切换称为“陷入”,高等级向低等级切换称为“返回”。
异常陷入条件:
- 执行
SVC(Supervisor Call)指令:从 EL0 陷入 EL1。 - 执行
HVC(Hypervisor Call)指令:从 EL1 陷入 EL2。 - 执行
SMC(Secure Monitor Call)指令:从 EL2 陷入 EL3。 - 外部中断(IRQ / FIQ)或系统错误(SError)触发异步异常。
2.2 同步异常与异步异常
ARMv8 将异常分为两大类:同步异常(Synchronous Exception)和异步异常(Asynchronous Exception)。两者的根本区别在于异常的产生是否与当前正在执行的指令直接相关。
| 特性 | 同步异常 | 异步异常 |
|---|---|---|
| 触发源 | 指令执行本身(如未定义指令、数据中止) | 外部事件(如中断、系统错误) |
| 可预测性 | 可预测,与指令流强相关 | 不可预测,随机发生 |
| 返回地址 | 指向触发异常的指令(或下一条) | 指向被中断的指令 |
| 典型例子 | SVC、HVC、SMC、Page Fault、Alignment Fault |
IRQ、FIQ、SError |
驱动开发注意:在中断控制器驱动中,GICv3 产生的 IRQ 和 FIQ 属于异步异常。这意味着中断处理程序必须保存和恢复被中断的上下文,不能假设处理器状态与中断发生前一致。
2.3 异常向量表基础
异常向量表(Exception Vector Table, EVT)是 ARMv8 处理器在发生异常时,用于查找处理程序入口地址的跳转表。每个异常等级拥有独立的向量表,基地址由 VBAR_ELn(Vector Base Address Register)寄存器指定。
每个异常等级的向量表包含 16 个入口,每个入口占用 128 字节(32 条指令)。16 个入口按照以下四个维度组织:
- 异常类型:同步异常、IRQ、FIQ、SError。
- 异常来源:来自当前 EL(Same EL)或来自低 EL(Lower EL)。
- 栈指针选择:使用 SP_ELn(当前 EL 的栈指针)或 SP_EL0(EL0 的栈指针)。
- 执行状态:AArch64 或 AArch32。
向量表布局如下(以 AArch64 为例):
| 偏移 | 异常类型 | 描述 |
|---|---|---|
| 0x000 | Sync (Current EL, SP_ELn) | 当前 EL 同步异常,使用 SP_ELn |
| 0x080 | IRQ (Current EL, SP_ELn) | 当前 EL IRQ 中断,使用 SP_ELn |
| 0x100 | FIQ (Current EL, SP_ELn) | 当前 EL FIQ 中断,使用 SP_ELn |
| 0x180 | SError (Current EL, SP_ELn) | 当前 EL 系统错误,使用 SP_ELn |
| 0x200 | Sync (Current EL, SP_EL0) | 当前 EL 同步异常,使用 SP_EL0 |
| 0x280 | IRQ (Current EL, SP_EL0) | 当前 EL IRQ 中断,使用 SP_EL0 |
| 0x300 | FIQ (Current EL, SP_EL0) | 当前 EL FIQ 中断,使用 SP_EL0 |
| 0x380 | SError (Current EL, SP_EL0) | 当前 EL 系统错误,使用 SP_EL0 |
| 0x400 | Sync (Lower EL, AArch64) | 低 EL 同步异常,目标为 AArch64 |
| 0x480 | IRQ (Lower EL, AArch64) | 低 EL IRQ 中断,目标为 AArch64 |
| 0x500 | FIQ (Lower EL, AArch64) | 低 EL FIQ 中断,目标为 AArch64 |
| 0x580 | SError (Lower EL, AArch64) | 低 EL 系统错误,目标为 AArch64 |
| 0x600 | Sync (Lower EL, AArch32) | 低 EL 同步异常,目标为 AArch32 |
| 0x680 | IRQ (Lower EL, AArch32) | 低 EL IRQ 中断,目标为 AArch32 |
| 0x700 | FIQ (Lower EL, AArch32) | 低 EL FIQ 中断,目标为 AArch32 |
| 0x780 | SError (Lower EL, AArch32) | 低 EL 系统错误,目标为 AArch32 |
向量表配置示例(Linux 内核):
/* arch/arm64/kernel/entry.S */
.align 11
ENTRY(vectors)
ventry el1_sync_invalid // Sync EL1t
ventry el1_irq_invalid // IRQ EL1t
ventry el1_fiq_invalid // FIQ EL1t
ventry el1_error_invalid // Error EL1t
ventry el1_sync // Sync EL1h
ventry el1_irq // IRQ EL1h
ventry el1_fiq_invalid // FIQ EL1h
ventry el1_error_invalid // Error EL1h
ventry el0_sync // Sync 64-bit EL0
ventry el0_irq // IRQ 64-bit EL0
ventry el0_fiq_invalid // FIQ 64-bit EL0
ventry el0_error_invalid // Error 64-bit EL0
ventry el0_sync_compat // Sync 32-bit EL0
ventry el0_irq_compat // IRQ 32-bit EL0
ventry el0_fiq_invalid // FIQ 32-bit EL0
ventry el0_error_invalid // Error 32-bit EL0
END(vectors)
2.4 异常处理流程与中断路由
当 GICv3 向处理器发送中断信号时,ARMv8 异常模型按照以下步骤处理:
- 中断信号到达:GICv3 通过
IRQ或FIQ线向处理器核发送中断请求。 - 处理器状态保存:处理器自动将
PSTATE(程序状态寄存器)保存到SPSR_ELn,并将返回地址保存到ELR_ELn。 - 向量表查找:根据当前异常等级和中断类型,从
VBAR_ELn指向的向量表中找到对应的入口。 - 跳转执行:处理器跳转到向量表入口,执行中断处理程序。
- 中断处理:在 Linux 内核中,
el1_irq或el0_irq入口会调用handle_arch_irq,最终进入 GICv3 驱动的中断分发函数。 - 异常返回:处理完成后,执行
ERET指令,从ELR_ELn恢复返回地址,从SPSR_ELn恢复PSTATE,返回被中断的程序。
GICv3 与异常等级的关联:GICv3 支持将中断配置为 Group 0(安全中断,路由到 EL3)或 Group 1(非安全中断,路由到 EL1/EL2)。在 Linux 内核中,所有普通设备中断均配置为 Group 1,通过 IRQ 线注入 EL1。
2.5 本章小结
- ARMv8 定义了 EL0 ~ EL3 四个异常等级,实现从用户态到安全监控器的分级保护。
- 异常分为同步异常(指令相关)和异步异常(外部事件),GICv3 中断属于异步异常。
- 异常向量表是中断处理的入口,每个异常等级拥有独立的 16 入口 × 128 字节 的向量表。
- 中断处理流程包括:保存上下文 → 向量表查找 → 执行处理程序 → ERET 返回。