第一节 异常处理

按照 RiscV 规范,异常和中断被触发时,当前的执行流程被打断,跳转到异常向量表中相应的例程进行处理。其中,stvec 寄存器指向的是向量表的基地址,scause 寄存器记录的异常编号则作为例程入口的偏移,二者相加得到异常处理例程的最终地址。

本节中,我们将准备一个符合规范的异常向量表,在内核启动阶段设置到 stvec 寄存器,此后,就可以逐步扩展向量表中注册的例程,去支持各种异常。看一下异常向量表,它采用汇编方式定义:

// axhal/src/riscv64/trap.S
.macro SAVE_REGS
    addi    sp, sp, -{trapframe_size}
    PUSH_GENERAL_REGS

    csrr    t0, sepc
    csrr    t1, sstatus
    csrrw   t2, sscratch, zero          // save sscratch (sp) and zero it
    sd      t0, 31*8(sp)                // tf.sepc
    sd      t1, 32*8(sp)                // tf.sstatus
    sd      t2, 1*8(sp)                 // tf.regs.sp
.endm

.macro RESTORE_REGS
    ld     t0, 31*8(sp)
    ld     t1, 32*8(sp)
    csrw    sepc, t0
    csrw    sstatus, t1

    POP_GENERAL_REGS
    ld     sp, 1*8(sp)                  // load sp from tf.regs.sp
.endm

.section .text
.balign 4
.global trap_vector_base
trap_vector_base:
    SAVE_REGS
    mv      a0, sp
    call    riscv_trap_handler
    RESTORE_REGS
    sret

异常向量表实际是在 riscv_trap_handler 中处理,但在真正处理异常的前后,分别需要保存和恢复原始的上下文,我们称之为异常上下文,以与上一章提到的任务上下文进行区别。

在内核中,存在多种相互独立的运行环境,每一种环境都抽象为一种上下文,内核需要在必要时进行切换。

在前面一章 - 多任务支持中,重点说明了任务上下文,用于任务间切换;本节的上下文对应异常处理的场景,称之为异常上下文。

关于两种上下文的示意图:

任务上下文和异常上下文

注意:异常上下文实际是基于当前任务的栈进行保存和恢复的。

然后看一下异常处理的具体逻辑:

#[no_mangle]
fn riscv_trap_handler(tf: &mut TrapFrame) {
    let scause = scause::read();
    match scause.cause() {
        Trap::Exception(E::Breakpoint) => handle_breakpoint(&mut tf.sepc),
        _ => {
            panic!(
                "Unhandled trap {:?} @ {:#x}:\n{:#x?}",
                scause.cause(),
                tf.sepc,
                tf
            );
        }
    }
}

fn handle_breakpoint(sepc: &mut usize) {
    log::debug!("Exception(Breakpoint) @ {:#x} ", sepc);
    *sepc += 2
}

先处理 BreakPoint 异常作为一个示例,简单的输出触发指令的地址,然后地址加 2,跳到下一条指令。以后可以仿照该异常,扩展异常处理的范围。

最后,设置异常上下文向量表:

// axhal/src/riscv64.rs
unsafe extern "C" fn rust_entry(hartid: usize, dtb: usize) {
    extern "C" {
        fn trap_vector_base();
        fn rust_main(hartid: usize, dtb: usize);
    }

    trap::set_trap_vector_base(trap_vector_base as usize);
    rust_main(hartid, dtb);
}

测试异常机制,在 axorigin 中通过 ebreak 指令触发,make run LOG=debug 查看日志。验证成功。