异常响应机制
发布时间:2026/10/4 22:29:24 作者:尧图编辑部 阅读量:1,286

【一生一芯 / PA】异常响应机制从ecall到mtvec再到mret的完整代码解析代码环境NEMU~/ysyx-workbench/nemuAM~/ysyx-workbench/abstract-machineISAriscv32一、异常响应机制到底在解决什么问题在 PA3 中我们要让程序之间能切换执行流。但讲义特别强调不能用 call / jal 随便跳转 必须提供一种“限制入口”的执行流切换方式这种机制就是异常响应机制它的核心流程是异常 / 中断发生 - 硬件保存返回地址 - 硬件保存异常原因 - 硬件跳转到固定入口 mtvec - 软件保存上下文 - 软件处理异常 - 软件通过 mret 返回在 RISC-V 中这三个关键 CSR 是mtvec - 异常入口 mepc - 异常返回地址 mcause - 异常原因二、NEMU 硬件侧isa_raise_intr()文件nemu/src/isa/riscv32/system/intr.cword_t isa_raise_intr(word_t NO, vaddr_t epc) { // 1. 保存返回地址 csr_write(MEPC_ADDR, epc); // 2. 保存异常原因 csr_write(MCAUSE_ADDR, NO); // 3. 特殊处理 ebreak if (NO 3) { nemu_state.state NEMU_END; nemu_state.halt_pc epc; nemu_state.halt_ret 0; return epc; } // 4. 读取异常入口 word_t mtvec csr_read(MTVEC_ADDR); // 5. 返回 mtvec并保证 4 字节对齐 return mtvec ~0x3; }逐行解释csr_write(MEPC_ADDR, epc);保存异常发生时的 PC将来mret要返回这里。csr_write(MCAUSE_ADDR, NO);保存异常原因。例如11 - ecall 3 - ebreak 0x80000007 - 机器定时器中断if (NO 3)你的代码把ebreak特殊处理为 NEMU 正常结束。return mtvec ~0x3;把 PC 修改为mtvec这就是异常入口跳转。三、AM 软件侧cte_init()设置异常入口文件abstract-machine/am/src/riscv/nemu/cte.cbool cte_init(Context*(*handler)(Event, Context*)) { asm volatile(csrw mtvec, %0 : : r(__am_asm_trap)); user_handler handler; return true; }解释csrw mtvec, __am_asm_trap把异常入口设置为汇编函数__am_asm_trap。这样无论用户程序如何触发异常CPU 都只能跳转到mtvec __am_asm_trap这就是“限制入口”的具体实现。四、汇编异常入口保存和恢复上下文文件abstract-machine/am/src/riscv/nemu/trap.S__am_asm_trap: addi sp, sp, -CONTEXT_SIZE MAP(REGS, PUSH) csrr t0, mcause csrr t1, mstatus csrr t2, mepc STORE t2, OFFSET_EPC(sp) STORE t0, OFFSET_CAUSE(sp) STORE t1, OFFSET_STATUS(sp) mv a0, sp call __am_irq_handle mv sp, a0 LOAD t1, OFFSET_STATUS(sp) LOAD t2, OFFSET_EPC(sp) csrw mstatus, t1 csrw mepc, t2 MAP(REGS, POP) addi sp, sp, CONTEXT_SIZE mret逐段解释1. 保存通用寄存器addi sp, sp, -CONTEXT_SIZE MAP(REGS, PUSH)在栈上开辟上下文空间保存所有通用寄存器。2. 读取 CSRcsrr t0, mcause csrr t1, mstatus csrr t2, mepc把异常原因、状态和返回地址读出来。3. 保存到 ContextSTORE t2, OFFSET_EPC(sp) STORE t0, OFFSET_CAUSE(sp) STORE t1, OFFSET_STATUS(sp)写入 C 结构体Context。4. 调用 C 处理函数mv a0, sp call __am_irq_handle把Context *传给 C 函数。5. 恢复上下文csrw mstatus, t1 csrw mepc, t2 MAP(REGS, POP) mret恢复寄存器和 CSR最后mret返回。五、C 语言异常分发文件abstract-machine/am/src/riscv/nemu/cte.cContext* __am_irq_handle(Context *c) { if (user_handler) { Event ev {0}; switch (c-mcause) { case 11: if (c-gpr[17] -1) { ev.event EVENT_YIELD; } else { ev.event EVENT_SYSCALL; } c-mepc 4; break; case 0x80000007: ev.event EVENT_IRQ_TIMER; break; default: ev.event EVENT_ERROR; break; } c user_handler(ev, c); assert(c ! NULL); } return c; }逐行解释case 11:mcause 11表示ecall。if (c-gpr[17] -1)如果a7 -1说明是yield()否则是系统调用。c-mepc 4;跳过ecall指令返回后从下一条指令继续。case 0x80000007:mcause 0x80000007表示机器定时器中断。ev.event EVENT_IRQ_TIMER;交给用户注册的 handler 处理。六、用户侧事件处理函数修改后的trm.cstatic Context *event_handler(Event ev, Context *ctx) { switch (ev.event) { case EVENT_YIELD: break; case EVENT_SYSCALL: if (ctx-GPRx 0) { halt(ctx-GPR1); } break; case EVENT_IRQ_TIMER: break; default: panic(Unhandled event); break; } return ctx; }这个event_handler就是未来操作系统的入口雏形。七、定时器中断如何触发文件nemu/src/device/intr.cvoid dev_raise_intr() { cpu.pc isa_raise_intr(0x80000007, cpu.pc); }解释isa_raise_intr(0x80000007, cpu.pc)表示异常原因 机器定时器中断 返回地址 当前 PC然后pc mtvec进入__am_asm_trap。八、完整异常响应链路1.yield()链路用户程序执行 ecall ↓ NEMU isa_raise_intr(11, pc) ↓ mcause 11 mepc pc pc mtvec ↓ __am_asm_trap ↓ 保存 Context ↓ __am_irq_handle ↓ mcause 11 a7 -1 - EVENT_YIELD ↓ event_handler ↓ 返回 Context ↓ mret ↓ pc mepc2. 定时器中断链路Linux 虚拟定时器 ↓ alarm_sig_handler ↓ timer_intr ↓ dev_raise_intr ↓ isa_raise_intr(0x80000007, pc) ↓ mcause 0x80000007 mepc pc pc mtvec ↓ __am_asm_trap ↓ __am_irq_handle ↓ EVENT_IRQ_TIMER ↓ event_handler ↓ mret九、测试验证1. 编译 NEMUcd ~/ysyx-workbench/nemu make -j$(nproc)2. 运行中断测试export NEMU_HOME~/ysyx-workbench/nemu export AM_HOME~/ysyx-workbench/abstract-machine cd ~/ysyx-workbench/am-kernels/tests/am-tests make ARCHriscv32-nemu run mainargsi3. 成功标志会看到异常处理链反复执行hello_intr - __am_asm_trap - __am_irq_handle - simple_trap - putch最终输出ttttttttttt其中t 定时器中断 d 设备中断 y yield因为hello_intr()是死循环所以测试会一直运行timeout是正常的。十、代码和讲义对照异常响应机制对应代码保存返回地址csr_write(MEPC_ADDR, epc)保存异常原因csr_write(MCAUSE_ADDR, NO)跳转异常入口return mtvec ~0x3设置异常入口cte_init - csrw mtvec保存上下文trap.S异常分发__am_irq_handle用户处理event_handler异常返回mret十一、总结异常响应机制就是一套标准流程硬件保存 mepc / mcause跳转 mtvec 软件保存上下文 软件根据 mcause 分发事件 软件处理事件 软件mret 返回你的代码现在已经具备这条完整链路。接下来 PA3 要做的就是在这个机制之上实现1. 操作系统入口 event_handler 2. 用户程序加载器 3. 批处理调度