11 KiB
中断与异常
AArch64 异常模型
参考:ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile: capture D1.1, D1.7, D1.10, D1.11, D1.13, D1.14, D1.16.
在 AArch64 中,各种中断被统称为异常(exception),包括:
- Reset.
- Interrupts.
- Memory system aborts.
- Undefined instructions.
- Supervisor calls (SVCs), Secure Monitor calls (SMCs), and hypervisor calls (HVCs).
- Debug exceptions.
这些异常可分为同步异常(synchronous exception)与异步异常(asynchronous exception)两大类:
- 同步异常:发生在执行一条特定的指令时,包括执行系统调用指令(
svc
、hvc
)、断点指令(debug exceptions)、Instruction Aborts、Data Aborts 等。 - 异步异常:发生的时机不确定,又被称为中断(interrupt),是发送给处理机的信号,包括 SError、IRQ、FIQ 等。
异常处理
当发生异常时,CPU 会根据异常的类型,跳转到特定的地址执行,该地址被称为异常向量(exception vector)。
不同类型异常的异常向量通过统一的向量基地址寄存器(Vector Base Address Register, VBAR)加上不同的偏移得到。在 EL1、EL2、EL3 下各有一个 VBAR 寄存器 VBAR_ELx
。此时异常被分为 4 大类,每一类根据异常来源的不同又可分为 4 类,于是共有 16 个异常向量。
4 种类型的异常分别为:
- Synchronous exception
- IRQ (Interrupt Request)
- FIQ (Fast Interrupt Request)
- SError (System Error)
4 种异常来源分为:
- Current Exception level with
SP_EL0
. 即发生异常时的异常级别与当前(指跳转到异常向量后)一样,且SP = SP_EL0
(SPsel = 0
)。 - Current Exception level with
SP_ELx
,x>0
. 即发生异常时的异常级别与当前一样,且SP = SP_ELx
(SPsel = 1
)。 - Lower Exception level, where the implemented level immediately lower than the target level is using AArch64. 即发生异常时的异常级别低于当前级别,且运行于 AArch64 模式。
- Lower Exception level, where the implemented level immediately lower than the target level is using AArch32. 即发生异常时的异常级别低于当前级别,且运行于 AArch32 模式。
16 种异常对应的异常向量相对于 VBAR 的偏移如下表所示:
Exception taken from | Synchronous | IRQ | FIQ | SError |
---|---|---|---|---|
CurrentSpEl0 | 0x000 |
0x080 |
0x100 |
0x180 |
CurrentSpElx | 0x200 |
0x280 |
0x300 |
0x380 |
LowerAArch64 | 0x400 |
0x480 |
0x500 |
0x580 |
LowerAArch32 | 0x600 |
0x680 |
0x700 |
0x780 |
如果该异常是 Synchronous 或 SError,异常症状寄存器(Exception Syndrome Register, ESR)将被设置,用于记录具体的异常类别 EC (exception class) 与 ISS (Instruction Specific Syndrome)。在 EL1、EL2、EL3 下各有一个 ESR 寄存器 ESR_ELx
。具体的 EC、ISS 编码见官网文档 ARMv8 Reference Manual D1.10.4 节。
异常屏蔽
某些异常可以被屏蔽(mask),即发生时不跳转到相应的异常向量。可被屏蔽的异常包括所有异步异常与调试时的同步异常(debug exceptions),共 4 种,分别由 PSTATE 中 DAIF
字段的 4 个位控制:
D
: Debug exceptionA
: SError interruptI
: IRQ interruptF
: FIQ interrupt
异常返回
当发生异常时,异常返回地址会被设置,保存在异常链接寄存器(Exception Link Register, ELR) ELR_ELx
中;当前的进程状态 PSTATE 会保存在保存的进程状态寄存器(Saved Process Status Register, SPSR) SPSR_ELx
中。
异常返回使用 eret
指令完成。当异常返回时,pc
会根据当前特权级被恢复为 ELR_ELx
中的,PSTATE 也会被恢复为 SPSR_ELx
中的。通过修改 SPSR_ELx
中相应的位并进行异常返回,就能使 PSTATE 被修改,从而实现异常级别切换、异常开启/屏蔽等功能。
系统调用
一般使用 svc
指令(supervisor call)完成,将触发一个同步异常。
RustOS 中的实现
中断与异常部分的代码主要位于模块 kernel/src/arch/aarch64/interrupt 中。
异常启用与屏蔽
在 interrupt/mod.rs 中,通过写入 DAIFClr
与 DAIFSet
特殊寄存器修改 PSTATE,分别实现了异常的启用与屏蔽(仅针对 IRQ),代码如下:
/// Enable the interrupt (only IRQ).
#[inline(always)]
pub unsafe fn enable() {
asm!("msr daifclr, #2");
}
/// Disable the interrupt (only IRQ).
#[inline(always)]
pub unsafe fn disable() {
asm!("msr daifset, #2");
}
此外,也可在异常返回前修改保存的 SPSR_EL1
寄存器,使得异常返回时 PSTATE 改变,从而实现启用或屏蔽异常,详见 interrupt/context.rs 中的 TrapFrame::new_kernel_thread()
与 TrapFrame::new_user_thread()
函数。
异常向量
全局符号 __vectors
定义了异常向量基址,并在 interrupt/mod.rs 的 init()
函数中通过 msr vbar_el1, x0
指令,将 VBAR_EL1
设为了 __vectors
。
16 个异常向量分别通过宏 HANDLER source kind
定义在 interrupt/vector.S 中,代码如下:
.macro HANDLER source kind
.align 7
stp lr, x0, [sp, #-16]!
mov x0, #\source
movk x0, #\kind, lsl #16
b __alltraps
.endm
不同的异常向量对应的异常处理例程结构相同,仅有 source
和 kind
不同。source
与 kind
将会被合并成一个整数并存到寄存器 x0
中,以便作为参数传给 Rust 编写的异常处理函数。
由于不同异常向量的间距较少(仅为 0x80
字节),所以不在 HANDLER
中做细致的处理,而是统一跳转到 trap.S 的 __alltraps
中进行处理。
异常处理
统一异常处理例程 __alltraps
的代码如下:
.global __alltraps
__alltraps:
SAVE_ALL
# x0 is set in HANDLER
mrs x1, esr_el1
mov x2, sp
bl rust_trap
.global __trapret
__trapret:
RESTORE_ALL
eret
流程如下:
- 首先通过宏
SAVE_ALL
保存各寄存器,构成TrapFrame
; - 然后构造函数参数
x0
、x1
、x2
,分别表示异常类型、异常症状 ESR、TrapFrame
,并调用 Rust 异常处理函数rust_trap()
; - 当该函数返回时,通过宏
RESTORE_ALL
从TrapFrame
中恢复各寄存器; - 最后通过
eret
指令进行异常返回。
TrapFrame
定义在 interrupt/context.rs中,结构如下:
pub struct TrapFrame {
pub elr: usize,
pub spsr: usize,
pub sp: usize,
pub tpidr: usize, // currently unused
// pub q0to31: [u128; 32], // disable SIMD/FP registers
pub x1to29: [usize; 29],
pub __reserved: usize,
pub x30: usize, // lr
pub x0: usize,
}
目前保存的寄存器包括:通用寄存器 x0~x30
、异常返回地址 elr_el1
、用户栈指针 sp_el0
、进程状态 spsr_el1
。由于在 aarch64-blog_os.json
中禁用了 NEON 指令,不需要保存 q0~q31
这些 SIMD/FP 寄存器。
rust_trap()
函数定义在 interrupt/handler.rs 中。首先判断传入的 kind
:
- 如果是
Synchronous
:在 interrupt/syndrome.rs 中解析 ESR,根据具体的异常类别分别处理断点指令、系统调用、缺页异常等。 - 如果是 IRQ:调用
handle_irq()
函数处理 IRQ。 - 其他类型的异常(SError interrupt、Debug exception)暂不做处理,直接调用
crate::trap::error()
。
系统调用
如果 ESR 的异常类别是 SVC,则说明该异常由系统调用指令 svc
触发,紧接着会调用 handle_syscall()
函数。
RustOS 的系统调用方式如下(实现在 user/ucore-ulib/src/syscall.rs 中):
- 将系统调用号保存在寄存器
x8
,将 6 参数分别保存在寄存器x0~x5
中; - 执行系统调用指令
svc 0
; - 系统调用返回值保存在寄存器
x0
中。
在 handle_syscall()
函数中,会从 TrapFrame
保存的寄存器中恢复系统调用参数,并调用 crate::syscall::syscall()
进行具体的系统调用。
缺页异常
缺页异常只会在 MMU 启用后,虚拟地址翻译失败时产生,这时候根据是取指还是访存,分别触发 Instruction Abort 与 Data Abort。此时 ISS 中还记录了具体的状态码,例如:
- Address size fault, level 0~3.
- Translation fault, level 0~3.
- Access flag fault, level 0~3.
- Permission fault, level 0~3.
- Alignment fault.
- TLB conflict abort.
- ...
其中 level 表示在第几级翻译表产生异常。当状态码是 translation fault、access flag fault、permission fault 时,将被判断为是缺页异常,并调用 handle_page_fault()
处理缺页异常。
发生 Instruction Abort 与 Data Abort 的虚拟地址将会被保存到 FAR_ELx
系统寄存器中。此时再调用 crate::memory::page_fault_handler(addr)
来做具体的缺页处理。
IRQ
如果该异常是 IRQ,则会调用 kernel/src/arch/aarch64/board/raspi3/irq.rs 中的 handle_irq()
函数。该函数与具体的硬件板子相关,即使都是在 AArch64 下,不同 board 的 IRQ 处理方式也不一样,所以放到了模块 kernel/src/arch/aarch64/board/raspi3 中,表示是 RPi3 特有的 IRQ 处理方式。
该函数首先会判断是否有时钟中断,如果有就先处理时钟中断:
let controller = bcm2837::timer::Timer::new();
if controller.is_pending() {
super::timer::set_next();
crate::trap::timer();
}
其中使用了 crate bcm2837,位于 crate/bcm2837 中,是一个封装良好的访问 RPi3 底层外围设备的库。
然后会遍历所有其他未处理的 IRQ,如果该 IRQ 已注册,就调用它的处理函数:
for int in Controller::new().pending_interrupts() {
if let Some(handler) = IRQ_HANDLERS[int] {
handler();
}
}
IRQ 的注册可通过调用 register_irq()
函数进行,实现如下:
pub fn register_irq(int: Interrupt, handler: fn()) {
unsafe {
*(&IRQ_HANDLERS[int as usize] as *const _ as *mut Option<fn()>) = Some(handler);
}
Controller::new().enable(int);
}