ARM架构(二)异常和中断处理

An exception is an event (other than branch or jump instructions) that causes the normal sequential execution of instructions to be modified. An interrupt is an exception that is not caused directly by program execution. Usually, hardware external to the processor core signals an interrupt, such as a button being pressed

本文主要介绍AArch64架构对异常的定义(ARM中将中断归为异常的一种),同时给出能够导致异常发生的动作。最后分别从处理器和内核开发的角度,解释内核态异常处理是如何实现的。

1. 异常的定义

Exception: 要求内核处理/提供服务的系统事件。ARM架构将异常分为以下两类

  • 异步:外部事件/信号,异步异常包含了三种类型:
    • IRQ
    • FIQ
    • SError(System Error): 最有可能由异步数据终止(asynchronous Data Aborts)导致,例如从cache line将脏数据写回外部内存。
  • 同步:直接执行某条指令导致,并且返回地址表明了导致异常的指令位置

1.1. 导致异常发生的动作

1.1.1. 终止(Aborts

终止可能由取指令错误(Instruction Aborts)或数据访问错误(Data Aborts)导致。

外部内存系统可能给出处理器对某个错误内存地址访问的响应。此外,MMU也可能生成终止。OS可以利用MMU终止为应用程序动态分配内存。

执行无法被取出的指令会导致终止。指令终止异常仅会发生在处理器尝试执行它的时候。数据终止异常发生在load或store指令尝试读/写数发生之后。

否则,终止被描述成异步的。异步终止导致SError中断异常。

1.1.2. 复位(Reset

复位向量总是对应最高异常级别。该向量使用一个由配置输入信号设置的IMPLEMENTATION DEFINED地址。

该地址能够从复位向量基地址寄存器RVBAR_ELn中读取,n是被实现的最高异常级别。

所有核都有复位输入并在复位后处理复位异常。复位是最高优先级的异常,因此不可被掩盖。该异常被用于在系统上电后,执行核中的代码以初始化它。

1.1.3. 异常生成指令(Exception generating instructions

执行这些指令会导致异常。通常用于请求高特权级程序的服务。

  • Supervisor Call(SVC)指令:user mode程序情况OS服务
  • Hypervisor Call(HVC)指令:guest OS请求hypervisor服务
  • Secure monitor Call(SMC)指令:normal world请求secure world服务

1.1.4. 中断(Interrupts

异步异常有三类,IRQ,FIQ和SError。IRQ和FIQ相比SError更加通用,SError通常指外部异步数据终止。所以,术语“中断”指的一般是IRQ和FIQ。

FIQ优先级比IRQ高。这两种中断都和每个核的单个输入引脚关联。当外部硬件断言一条中断请求线,在当前指令被执行完后(存在特殊指令可能会被打断)对应的异常类型就被触发。(当然,这里假设中断没有被禁止)

各种中断源和中断控制器相连。中断控制器对中断请求仲裁并赋予它们中断优先级,之后提供串行的单个信号给处理器核的FIQ或IRQ信号线。

2. 异常处理 - 处理器角度

当异常发生时,处理器会打断当前程序控制流的执行,保存异常发生前程序的状态PSTATE到SPSR寄存器,更新当前处理器状态到PSTATE,接着将异常返回地址存放于ELR寄存器中。根据异常类型的不同,处理器在ELR寄存器中存入不同的返回地址如下:

  1. 对于异步异常,处理器存入第一条未执行的指令的地址。
  2. 对于非系统调用的同步异常,处理器存入生成该异常的指令的地址。
  3. 对于异常生成指令例如系统调用、虚拟机监视器调用等,处理器存入生成该异常的指令下一条指令的地址。

最后,处理器会转而执行异常向量表中的异常向量。异常向量是操作系统开发人员给出的异常处理器的起始部分汇编,异常级别EL3,EL2和EL1均有自己的异常向量表,它们的地址存放于每一异常级独有的向量基址寄存器(Vector Based Address Registers,VBAR_ELx)中。异常向量表中共有16个向量,每个向量为固定的64字节,最多包含16条指令。这16个向量被分四类,每类包含4个向量,分别对应4种异常类型,即同步异常,中断请求异常,快速中断请求异常和系统错误异常。

此外,对于同步异常,处理器还通过异常特征寄存器(Exception Syndrome Register,ESR)给出同步异常发生原因并通过错误地址寄存器(Fault Address Register)给出导致同步异常的指令地址。

2.1. 处理同步异常

Exception Syndrome Register(ESR_ELn)和Fault Address Register(FAR_ELn)被用于向异常hanler提供异常发生原因的信息。ESR_ELn给出了异常原因的信息,FAR_ELn给出了错误的虚拟地址。

Exception Link Register中存有导致数据访问终止的指令地址。

如果一个异常被从使用AArch32的异常级别带入使用AArch64的异常级别,且异常写入的Fault Address Register和目标异常级别相关联,则FAR_ELn的前32位均被设置为0。

对于实现了EL2或EL3的系统,同步异常通常发生在当前或者更高的异常级别。如果需要的话,异步异常也可以被发给到更高的异常级别,由hypervisor或secure kernel处理。SCR_EL3寄存器明确了哪个异常会被发到EL3。HCR_EL2明确了哪个异常会被发到EL2。

2.2. Exception Syndrome Register

该寄存器包含了异常发生的原因,仅被同步异常和SError更新。不会被IRQ或FIQ更新,它们对应的中断处理器通常在在Generic Interrupt Controller的寄存器中获取信息。

arm-except1.png arm-except2.png

  • ESR_ELn.EC: 异常种类(例如,对齐,数据终止)

  • ESR_ELn.IL: 指令长度(例如:1 for 32bit)
  • ESR_ELn.ISS: 指令具体细节(例如:translation level)
    • IFSC[5:0], Instruction Fault Status Code
      • Address size fault
      • Translation fault
      • Access flag fault
      • Permission fault
      • TLB conflict abort

2.3. 未分配指令

导致同步abort。该异常类型发生在处理器:

  • 执行opcode未定义指令
  • 执行要求一个比当前更高的异常级别指令
  • 执行被禁用的指令
  • PSTATE.IL域被设置时的任意指令

2.4. 系统调用

SVC指令能够执行系统调用,例如malloc()的调用链如下:

arm-except3.png

2.5. EL2/EL3系统调用

SVC被EL0程序使用,调用EL1中内核的程序。HVC和SMC系统调用指令则分别移动到EL2和EL3.当处理器在EL0执行时,不能直接进入EL2或EL3.仅当处理器处于EL1以上时才行。因此,应用程序需要使用SVC进入内核,并由内核进入更高的异常级别。

在OS内核(EL1)中,软件能够使用HVC调用Hypervisor代码(EL2),或是使用SMC调用Secure monitor(EL3)代码。若处理器实现了EL3,则能够从EL2捕获EL1的SMC指令调用EL3.若不存在EL3,则SMC仅会在当前异常级别触发。

arm-except4.png

类似地,在EL2,程序能够通过SMC调用EL3中的secure monitor。

2.6. 中断路由

rpi的中断路由如下:

arm-except5.png

3. 异常处理 - 内核开发角度

3.1. 异常向量表

发生异常后,处理器首先会根据异常的来源和类型执行异常向量表中的代码。异常向量表中的代码主要是保存上下文,接着跳转到异常处理器,执行具体的异常处理函数。

  • vector:means to direct an interrupt to a certain entry in the table
  • 4个异常类型 x 4个执行模式 = 16项
    • 每项包含了0x80字节的实际的代码
    • PC会基于两个条件的组合进行更新
  • VBAR_EL1: 指向向量表
  • 异常类型:
    • 同步:例如:系统调用
    • IRQ:例如:处理定时器中断
    • FIQ:例如:处理USB中断
    • SError:不支持
  • 异常模式
    • current EL with SP0:不支持
    • current EL with SPx:当在内核中数据abort
    • lower EL using AArch64:来自于用户空间的syscall
    • lower EL using AArch32(不支持)
1
2
3
4
5
6
pub enum Kind {
    Synchronous = 0,
    Irq = 1,
    Fiq = 2,
    SError = 3,
}

arm-except7.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// vector.s
.macro HANDLER source, kind
    .align 7
    stp     lr, xzr, [SP, #-16]!
    stp     x28, x29, [SP, #-16]!
    
    mov     x29, \source
    movk    x29, \kind, LSL #16
    bl      context_save
    
    ldp     x28, x29, [SP], #16
    ldp     lr, xzr, [SP], #16
    eret
.endm

3.2. 异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#[no_mangle]
pub extern "C" fn handle_exception( ... ) {
    match info.kind {
        Kind::Synchronous => {
            let syndrome = Syndrome::from(esr);
            match syndrome {
                Syndrome::Brk(_) => { ... }
                Syndrome::Svc(n) => { ... }
        }
        Kind::Irq => {
            let ic = Controller::new();
            for v in Interrupt::iter() {
                if ic.is_pending(v) {
                    crate::IRQ.invoke(v, tf);
                }
            }
        }
        ...
}

pub enum Syndrome {
    Unknown,
    SimdFp,
    IllegalExecutionState,
    Svc(u16),
    Hvc(u16),
    Smc(u16),
    InstructionAbort { kind: Fault, level: u8 },
    PCAlignmentFault,
    DataAbort { kind: Fault, level: u8 },
    SpAlignmentFault,
    Breakpoint,
    Step,
    Watchpoint,
    Brk(u16),
    ...
}

例如,处理同步异常时,根据esr寄存器中的值,判断为系统调用/软中断异常,获取各自的系统调用/软中断号,进一步分发到具体的系统调用函数处理。

3.3. 从异常返回

  • 通过eret(降低异常级别)
    1. SPSR_ELn -> PSTATE
    2. ELR_ELn -> PC,注:不同异常保存的ELR_ELn不同,同时,某些异常的处理也需要手动修改ELR_ELn的值。
异常类型 返回地址
异步异常 第一条未执行的指令地址
非系统调用类同步异常 生成该异常的指令地址
系统调用 生成该异常的指令地址 + 4

4. 中断

4.1. 控制中断

下面给出了在内核态设置中断掩码的方法,对于用户态,通过在内核态设置spsr的值,这样返回用户态时,spsr的值会被处理器写入PSTATE中,这样就使能了用户态的中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MSR DAIFSet, #Imm4  // Used to set any or all of DAIF to 1
MSR DAIFClr, #Imm4  // Used to clear any or all of DAIF to 0
/// Enable (unmask) IRQ interrupts
pub fn enable_irq_interrupt() {
    unsafe {
        asm!("msr DAIFClr, 0b0010" :::: "volatile");
    }
}
/// Disable (mask) FIQ interrupt
pub fn disable_fiq_interrupt() {
    unsafe { 
        asm!("msr DAIFSet, 0b0001" :::: "volatile");
    }
}

arm-except8.png

4.2. 嵌套中断控制

暂时还不知道如何实现。。。

arm-except9.png

5. 注:x86中的定义

  • Interrupt:异步事件
  • Exception:同步事件
    • fault: 出错导致指令没有执行(例如,page fault)
    • trap: 执行陷阱指令(例如,break point)
    • abort: 无法恢复(例如,machine check,double fault)

6. 参考