AArch64 异常模型 - 基础概念
本文介绍AArch64异常模型的基础概念。
1. Privilege and Exception levels
1.1 异常级别
什么是 Exception Level (EL)?
在 AArch64 架构中,权限(Privilege)的代名词是异常级别(Exception Level),简称 EL。数字越大的 EL 代表权限级别越高:
- EL0 权限最低,通常运行普通应用程序(Applications);
- EL1 通常运行操作系统内核(Rich OS,如 Linux、Windows);
- EL2 通常运行虚拟机监控程序(Hypervisor,如 KVM、Xen);
- EL3 权限最高,通常运行安全固件和安全监视器(Firmware / Secure Monitor)。
需要说明的是,Arm 架构本身并不强制规定哪类软件必须运行在哪个 EL 上,但 Linux、Android 等标准软件生态都默认遵循这一模型,绝大多数系统处理工作发生在 EL0 和 EL1。
异常级别的切换条件
EL 无法被软件随意修改,只有发生以下 5 种特定事件时,异常级别才会发生改变:
- 触发异常(Taking an exception);
- 从异常返回(Returning from an exception);
- 处理器复位(Processor reset);
- 进入调试状态(During Debug state);
- 退出调试状态(Exiting from Debug state)。
切换时方向是固定的:触发异常时 EL 只能提升或保持不变,绝不可能进入更低权限级别;从异常返回时 EL 只能降低或保持不变,绝不可能因此升到更高权限级别。
1.2 特权的类型
特权的两种类型
AArch64 中的特权受当前 EL 影响,主要分为两大类:
- 内存系统的特权(Memory Privilege):MMU 允许软件为内存区域配置读/写权限,分为特权访问(Privileged access)与非特权访问(Unprivileged access)。EL0 发起的内存访问按非特权权限检查,EL1、EL2、EL3 发起的内存访问按特权权限检查。
- 访问处理器资源的特权(Processor Resources Privilege):控制软件读写 CPU 系统寄存器(System Registers)的能力。
系统寄存器访问与命名规则
_ELx后缀:系统寄存器名称末尾的_ELx(如VBAR_EL1、SCTLR_EL2)表示访问该寄存器所需的最低异常级别(Lowest EL)。- 寄存器相互独立:名字相似但后缀不同的寄存器(如
SCTLR_EL1和SCTLR_EL2)在物理上是完全独立、拥有独立指令集编码的寄存器,并非同一个寄存器的影子或别名。 - 跨级别访问:每个 EL 负责配置自己的控制寄存器;更高的 EL 可以直接访问并配置更低 EL 的寄存器,例如 EL2 可以访问
SCTLR_EL1,用于虚拟机上下文切换;反过来,低级别访问高级别寄存器(如 EL0 访问SCTLR_EL1)会触发异常。
系统控制寄存器(SCTLR)的特殊说明
SCTLR_EL1:控制 EL0 和 EL1 的顶级系统配置(如 MMU、Cache 等);SCTLR_EL2:控制 EL2 的顶级系统配置;SCTLR_EL3:控制 EL3 的顶级系统配置;- 不存在
SCTLR_EL0:因为 EL0 和 EL1 共享同一套 MMU 配置,且 MMU 的控制权严格限定在特权级别(EL1 及以上)。
异常处理的核心系统寄存器一览
| 寄存器名称 | 缩写 | 核心作用描述 |
|---|---|---|
| Exception Link Register | ELR_ELx |
保存触发异常的那条指令地址(用于异常返回)。 |
| Exception Syndrome Register | ESR_ELx |
包含发生异常的具体原因和类型信息。 |
| Fault Address Register | FAR_ELx |
保存引发内存访问错误的虚拟地址 (VA)。 |
| Hypervisor Configuration Register | HCR_ELx |
控制虚拟化设置以及将异常拦截(Trap)到 EL2。 |
| Secure Configuration Register | SCR_ELx |
控制安全状态(Secure state)及拦截异常到 EL3。 |
| System Control Register | SCTLR_ELx |
控制标准内存、系统设施开关(如 MMU/Cache Enable)。 |
| Saved Program Status Register | SPSR_ELx |
发生异常时,保存跳转前 CPU 的运行状态(PSTATE)。 |
| Vector Base Address Register | VBAR_ELx |
保存跳转到当前 ELx 的异常向量表基地址。 |
2. Execution and Security states
2.1 Execution States 执行状态
什么是执行状态?
执行状态定义了 CPU 运行时通用寄存器的标准宽度以及支持的指令集:
- AArch64 是 64 位执行状态,支持 A64 指令集,通用寄存器宽度为 64 位;
- AArch32 是 32 位执行状态,向后兼容早期架构,支持 A32 和 T32(Thumb)指令集,通用寄存器宽度为 32 位。
执行状态的切换条件与规则
CPU 只能在两种情况下改变执行状态:处理器复位(Processor reset),或异常级别发生变化(Exception Level changes)。
切换方向也有讲究:
- 由低特权级别升入高特权级别(如 EL0 到 EL1)时,执行状态可以保持不变,也可以切换为 AArch64;
- 由高特权级别降回低特权级别(如 EL1 到 EL0)时,执行状态可以保持不变,也可以切换为 AArch32。
由此可以得到一个核心结论(向下兼容原理):高层(64 位)可以托管、运行低层(32 位),反过来不行。
- 64 位的操作系统内核(EL1)既能运行 64 位应用,也能运行 32 位应用;
- 32 位的操作系统内核(EL1)只能运行 32 位应用;
- 32 位的 Hypervisor(EL2)只能托管 32 位的 OS(EL1)。
谁来控制下一层的执行状态?
低一层(Lower EL)运行在 32 位还是 64 位,由更高一级的控制寄存器决定:
- EL2 的
HCR_EL2决定 EL1 的执行状态; - EL3 的
SCR_EL3决定 EL2 的执行状态。
Armv8-A 与 Armv9-A 的架构差异
| 架构版本 | 各 EL 的 AArch32 支持 | 复位后的默认状态 |
|---|---|---|
| Armv8-A | 允许硬件实现在所有 EL(EL0~EL3)支持 AArch32/AArch64。 | 由具体实现决定(例如 Cortex-A32 复位后固定为 AArch32)。 |
| Armv9-A | 所有 EL 必须支持 AArch64;仅 EL0 可自主选择是否保留对 AArch32 的支持。 | 复位后强制默认为 AArch64 状态。 |
执行状态由谁定义?
每个 EL 运行在 64 位还是 32 位,由上一级(Next Higher EL)的控制寄存器决定:
最顶层的 EL3 之上没有更高的软件控制层:若硬件支持状态切换,其复位后的执行状态直接由芯片外部的硬件引脚(External Pin)信号决定。
2.2 Security States 安全状态
什么是安全状态?
安全状态用于在硬件层面隔离、划分可信软件与不可信软件,它决定了三件事:当前能访问哪些异常级别(EL)、能访问哪些内存区域(物理地址空间),以及内存访问请求在系统总线上的表达方式。
传统双安全状态(Armv8-A / TrustZone)
大多数 Cortex-A 处理器默认支持两种状态:
- 非安全状态(Non-secure state / Normal world):只能访问非安全物理地址空间,只能访问允许非安全访问的系统寄存器,典型应用是普通操作系统(如 Android、Linux)和普通应用;
- 安全状态(Secure state / Secure world):可以同时访问安全和非安全物理地址空间,以及分组(Banked)寄存器的安全副本,典型应用包括指纹识别、支付系统、DRM 和可信 OS(TEE)。
安全状态的切换机制
切换安全状态的钥匙是 EL3 的 SCR_EL3.NS 位。无论从安全切到非安全、还是反向切换,都必须经过最高特权级 EL3 中转——EL3 相当于安全世界与非安全世界之间的守门人,由它决定 EL2、EL1、EL0 各层处于哪种安全状态,并在执行 ERET 返回时让新的 SCR_EL3.NS 配置生效。也正因如此,在 Armv8-A 中 EL3 始终处于安全状态,可以访问所有 Banked 系统寄存器的副本。
RME 扩展与 Armv9-A 新增安全状态
Armv9-A 引入了 RME(Realm Management Extension,领域管理扩展),用于实现机密计算(Confidential Computing),额外新增了两个安全状态:
- Realm 状态(领域状态):可访问非安全和 Realm 物理地址空间,用于运行独立的机密虚拟机(Realm VM),防止 Hypervisor 或云厂商偷看数据;
- Root 状态(根状态):可访问所有物理地址空间,仅在 EL3 可用。在支持 RME 的系统中,EL3 从 Secure 状态独立出来进入 Root 状态,作为全系统的硬件安全根与内存归属仲裁者。
2.3 硬件实现异常级别的影响
异常级别(EL)的实现要求
处理器是否在硬件层面支持某个异常级别,属于实现选项(Implementation choice):
- 必须实现(Mandatory):EL0 和 EL1。任何标准的 AArch64 处理器都必须具备,以保障最基本的应用和 OS 运行;
- 可选实现(Optional):EL2 和 EL3。未实现 EL2 会失去大部分硬件虚拟化支持(如依赖虚拟化的 KVM Linux 等系统将无法运行);未实现 EL3 则意味着无法改变安全状态——EL3 是唯一能切换安全状态的级别,若省略 EL3,处理器只能永久处于单一安全状态(具体处于哪个状态由硬件实现决定)。
Secure EL2 的历史演进
- Armv8.0-A:EL2 仅存在于非安全世界(Non-secure),Secure 侧没有 EL2 虚拟化支持;
- Armv8.4-A:引入可选的 S.EL2(Secure EL2)特性,并提供使能位
SCR_EL3.EEL2保持向下兼容; - Armv9-A:若芯片实现了 EL2,则必须在所有安全状态(Non-secure 和 Secure)中同时支持 EL2(禁用位依旧保留)。
执行状态的组合与限制
- 向下兼容传导:如果某个 EL 允许运行 AArch32,则它所有更低的 EL 也必须允许运行 AArch32;
- Armv9-A 的硬性约束:所有 EL 必须支持 AArch64,仅 EL0 可选择性保留对 AArch32 的支持。这意味着在 Armv9-A 上,老旧的 32 位应用(EL0)仍可运行,但 32 位的 OS 内核、Hypervisor 或固件已完全不再支持。
常见处理器的实现示例
| 处理器类型 | 支持的异常级别 / 执行状态配置 |
|---|---|
| Cortex-A35 | 所有 EL(EL0~EL3)均支持 AArch32 和 AArch64。 |
| Cortex-A32 | 仅支持 AArch32(纯 32 位处理器)。 |
| Neoverse-N2 | 仅 EL0 支持 AArch32,其余所有 EL(EL1~EL3)强制为纯 AArch64。 |
3. Exception types
3.1 同步异常
什么是同步异常?
同步异常由当前正在执行的指令直接引发或与之相关,具备三个核心特征:
- 因指令而生:异常是直接执行(或尝试执行)某条指令的结果;
- 返回地址可确定:异常处理完毕后返回的地址,与引发异常的指令之间存在严格定义的架构关系;
- 精确性(Precise):进入异常处理程序时,触发异常前的所有指令已全部执行完毕,之后的指令一条都未执行。
另外,一条指令可能同时触发多个同步异常,Arm 架构为此定义了固定的优先级处理顺序。
同步异常的四大诱因
无效指令与陷阱(Invalid instructions and trap exceptions)
执行未定义(UNDEFINED)指令、当前 EL 无权执行的指令或被禁用的指令,都会引发未定义异常。此外,OS 或 Hypervisor 还可以主动配置寄存器,把低 EL 的特定操作(如读取特定寄存器)拦截下来,这就是陷阱(Traps)机制。
一个典型的应用是 FP/SIMD 单元的延迟上下文切换:应用切换时,频繁保存/恢复庞大的 FP/SIMD 寄存器非常耗时。于是 EL1 的 OS 在切换应用时故意禁用 EL0 的 SIMD/FP 单元(设置陷阱);一旦应用后续使用浮点指令,硬件就触发陷阱陷入 EL1,OS 捕获异常后开启 FP 单元、恢复寄存器并打上”已使用”标记。下次切换时,没有标记的应用就无需保存 FP 寄存器,效率大幅提升。
内存访问错误(Memory accesses)
内存访问错误由 MMU 执行权限检查(如非特权代码访问特权地址、向只读内存写入)或内存系统返回错误引发。由于 MMU 检查是同步的,异常会在内存实际访问发生前被捕获,在 AArch64 中称为同步中止(Synchronous Abort)。
异常生成指令 / 系统调用(Exception-generating instructions)
软件可以故意执行特定指令主动发起异常,实现跨权限级别的服务调用(低 EL 向高 EL 请求服务):
SVC(Supervisor Call):EL0 应用向 EL1 OS 请求服务;HVC(Hypervisor Call):EL1 OS 向 EL2 Hypervisor 请求服务;SMC(Secure Monitor Call):EL1/EL2 向 EL3 固件请求服务。
这些调用有几条限制:不能向更低 EL 发起调用(如 EL2 发 SVC 不能回到 EL1);EL0 不能直接发 HVC 或 SMC(会触发未定义异常)。此外,Hypervisor 还可以拦截这些调用,例如设置 HCR_EL2.TSC = 1,把 Guest OS(EL1)发出的 SMC 截获到 EL2,阻止其直接访问 EL3。
调试异常(Debug exceptions)
由调试相关操作引发,如软件断点、硬件监视点(Watchpoints)、单步调试等。
3.2 异步异常
什么是异步异常?
异步异常由处理器外部生成的系统事件引发,与当前正在执行的指令流不同步。它非预期、不可预测,架构只要求 CPU 在有限时间内响应;发生时程序流程会被打断,跳转到专门的处理程序,因此俗称中断。
物理中断的三大类型
物理中断由外设等硬件信号触发,避免 CPU 频繁轮询外部设备(如 UART 接收数据):
SError(System Error / 系统错误)
SError 由内存系统在发生意外严重错误时生成,属于外部异步中止(Async Aborts)。由于触发错误的指令可能已经执行完毕(Retired),因此采用异步方式报告。常见场景包括:内存访问通过了 MMU 检查、但在系统总线上遇到错误;内置 Cache 等 RAM 触发奇偶校验或纠错码(ECC)错误;Cache Line 把脏数据写回(Write-back)外部存储器时触发中止。
IRQ 与 FIQ
IRQ 与 FIQ 是常规外设中断(如定时器到期、串口数据到达),代表预期的外部事件而非系统故障。它们有独立的路由控制,常配合 Arm Generic Interrupt Controller (GIC) 区分安全与非安全中断。一个重要的变化是:在 AArch64 中,FIQ 与 IRQ 具有相同的优先级——这与旧版 Arm 架构中把 FIQ 作为高优先级快速中断的做法不同。
虚拟中断(Virtual Interrupts)
在未启用虚拟化的传统系统中,外设中断直接发送给物理 OS(EL1);而在虚拟化架构中,Hypervisor(EL2)托管着多个 Guest OS(EL1 虚拟机),Guest OS 看到的并非物理外设而是虚拟外设。因此架构必须提供虚拟化中断的机制,即虚拟中断。
对应物理中断,虚拟中断也分为三种:vSError、vIRQ 和 vFIQ。它们只能被发送到 EL1(即虚拟机内的 Guest OS),EL0 应用层无权直接接收,EL2/EL3 则处理物理中断或更高级别的事件。
Hypervisor 向 vCPU 注入虚拟中断有两条路径:
- 路径 A:纯软件注入(通过
HCR_EL2控制位)。Hypervisor 先设置路由控制位(如HCR_EL2.IMO = 1,把物理 IRQ 拦截并路由至 EL2),再直接写入控制位:VSE置 1 触发 vSError,VI置 1 触发 vIRQ,VF置 1 触发 vFIQ。这种方式脱离了硬件中断控制器,属于”纯软件强行挂起中断”:每当 Guest OS(EL1)响应、应答或清除(EOI)该中断时,都会因为没有真正的中断控制器寄存器接口而再次陷入 EL2,造成频繁的上下文切换和性能开销。 - 路径 B:硬件辅助注入(结合 GIC 虚拟接口)。自 GICv2 及更高版本(GICv3/v4)起,中断控制器硬件内置了 Virtual CPU Interface(虚拟 CPU 接口)和 List Registers(LRs)。Hypervisor 只需向 List Register 配置一条映射规则(如”把物理外设的中断映射为 vCPU 的 33 号 vIRQ”);外部中断触发时,GIC 硬件自动完成信号转换,通过虚拟 CPU 接口向 vCPU 发送 vIRQ;Guest OS(EL1)处理中断时直接与虚拟接口硬件寄存器交互,完成应答与清除。全程无需 Hypervisor 频繁干预与 Trap 截获,极大降低了虚拟化下的中断延迟与开销。
典型工作流(以物理网卡直通 Passthrough 为例):
- 初始化配置(Hypervisor 仅参与一次):启动虚拟机时,Hypervisor(EL2)在 GIC 的 List Register 中配置一条规则:把物理网卡中断(如 48 号)映射给 Guest A 的虚拟中断(如 33 号 vIRQ);
- 硬件自动注入(运行时零软件开销):物理网卡产生中断号 48 进入 GIC,GIC 硬件自动查表匹配,直接通过虚拟 CPU 接口向 vCPU 硬件引脚拉高 33 号 vIRQ 信号,全程无需触发 Trap,Hypervisor 无需执行任何代码或逻辑判断;
- 虚拟中断响应与清除:Guest A(EL1)直接读取 GIC 虚拟接口寄存器(如
GICC_IAR)响应中断,处理完毕后写入GICC_EOIR结束中断,硬件自动完成状态清理,无需陷入 EL2。
中断屏蔽(Masking)与 NMI
- 异步异常可屏蔽:物理和虚拟异步异常均可被临时屏蔽,屏蔽期间中断保持挂起(Pending)状态,直到解除屏蔽后才被响应(常用于处理嵌套异常);
- 同步异常不可屏蔽:同步异常由指令直接引发,若被屏蔽忽略,程序将无法继续执行;
- 不可屏蔽中断(NMI):Armv8.8-A 和 Armv9.3-A 引入了 NMI 特性,赋予某些中断超级优先级(Superpriority):即便普通中断被屏蔽,也能被 CPU 强制响应处理。