一、异常等级
ARM 的异常等级分为四个等级:
- EL0:非特权模式,用户态,运行用户应用程序
- EL1:特权模式,内核态,运行操作系统内核(VHE 模式下操作系统内核运行在 EL2)
- EL2:虚拟化模式,运行虚拟机监控器 Hypervisor
- EL3:安全模式,运行可信执行环境(TEE)

二、异常类型
当异常发生时,通常会 trap 到更高的 EL
级别进行处理。然后处理完通过eret指令返回到之前的 EL
级别继续执行。

返回到原先的 Level 后,我们应该执行原先发生异常的那条指令呢?还是跳过它继续执行下一条指令呢?这取决于异常的类型。
ARM64 异常类型分为同步异常和异步异常两种
- 同步异常一般指的是异常是由当前指令引起的,例如除零、非法指令等,因此这种异常需要由高级别的决定怎么处理,能否修复。除系统调用外,同步异常
eret返回后重新执行发生异常的那条指令。- 系统调用异常(SVC、HVC、SMC)
- MMU 引发的异常(缺页、访问没有权限的页面)
- 对齐检查异常(SP 和 PC 对齐检查)
- 未分配指令异常(非法指令、没有被定义的指令、在不恰当的等级访问没有权限的寄存器)
- 异步异常一般指的是异常是由外部事件引起的,例如中断等,当前正在执行的指令跟出现的异常是没有依赖关系的,因此这种异常处理完后直接继续执行下一条指令。
- IRQ 中断
- FIQ 中断
- SERROR 中断,没法被修复的错误
三、异常入口
异常入口是要清楚发生异常后,CPU 要做什么。 当异常发生时,CPU 会自动执行以下操作:
- PSTATE 保存到
SPSR_ELx中- PSTATE 包含当前的处理器状态信息,包括条件标志、中断使能位等
- 返回地址 PC 保存到
ELR_ELx中 - PSTATE 寄存器里的 DAIF 标志位会被设置为 1,相当于把调试异常、SError 异常、IRQ 异常和 FIQ 异常全部关闭,防止在异常处理过程中被打断。
- 更新
ESR_ELx寄存器,记录异常的类型和原因 - SP 执行
SP_ELx寄存器,切换到对应 EL 级别的栈 - 切换到对应的 EL 级别,跳转到异常向量表中对应的异常处理程序地址
四、异常返回

异常返回时,操作系统会执行 eret 指令,CPU
会自动执行以下操作:
- 从
ELR_ELx寄存器中恢复 PC 指针- 同步异常(非系统调用的同步异常):PC 执行异常现场的当条指令
- 异步异常:PC 执行异常现场的下一条指令
- 从
SPSR_ELx寄存器中恢复 PSTATE 状态SPSR.M[3:0]字段记录了要返回恢复到哪个 EL 级别SPSR.M[4]记录了异常现场的模式:- 0:AArch64 模式
- 1:AArch32 模式

五、异常处理的路由
异常处理的路由指的是发生异常后,要跳转到哪个 EL 级别进行处理。
当出现异常时,CPU 会根据当前的 EL 级别和异常类型,切换到相应的 EL 级别进行处理。通常情况下,异常会从较低的 EL 级别切换到较高的 EL 级别进行处理。例如,当在 EL0 运行的用户应用程序发生异常时,CPU 会切换到 EL1 进行处理。
异常也可以同级别处理,例如在 EL1 运行的内核代码发生异常时,CPU 仍然在 EL1 进行处理。但是 EL0 的异常不能在 EL0 进行处理,必须切换到高级别中。
为了实现跳转,每个 EL
等级需要在系统启动时分配一个对应的栈空间,一般 4KB
大小即可。当要做异常跳转的时候,让 SP_ELx 指向对应 EL
级别的栈地址。
六、实验:跳转到 EL1
QEMU 虚机跳转到 Benos 代码时,是出于 EL2 级别的,因此我们需要在 Benos 内核初始化代码中,切换到 EL1 级别。
因此,我们需要在 EL2 中触发一次异常,并在同级别 EL2 中处理这个异常,从而实现跳转到 EL1。
跳转到 EL1 需要配置 EL1 的一些相关寄存器做以下几步:
- 设置
HCR_EL2寄存器,这个寄存器是配置 hypervisor 的一些配置项,这里的RW,bit[31]域表示了 EL1 运行在 AArch64 模式还是 AArch32 模式- 0:AArch32 模式 (默认)
- 1:AArch64 模式
- 设置
SCTRL_EL1寄存器,这个寄存器是配置一些如 MMU、cache、大小端的配置项M,bit[0]域表示是否开启 MMU- 0:关闭 MMU(目前系统内核还没写内存翻译的代码,因此先关闭)
- 1:开启 MMU
EE,bit[25]域表示 EL1 的大小端模式- 0:小端模式(这里也设置成小端)
- 1:大端模式
- 设置
SPSR_EL2寄存器,这个寄存器是保存异常现场的 PSTATE 状态,需要关闭中断并设置返回的 EL 级别M,bit[3:0]域表示要切到哪个 EL 级别(我们这里是要切到 EL1)0b0101:EL1h 模式,使用 SP_EL1 寄存器作为栈指针
- 设置异常返回寄存器
ELR_EL2,让他返回到 EL1_entry 入口汇编函数里 - 执行
eret指令,跳转到 EL1 级别
1 | // sysregs.h |
1 | // boot.s |