PA3.1
- 特殊的原因? (建议二周目思考)
这些程序状态(x86的eflags, cs, eip; mips32的epc, status, cause; riscv32的mepc, mstatus, mcause)必须由硬件来保存吗? 能否通过软件来保存? 为什么?
还不知道
-
异常号的保存
x86通过软件来保存异常号, 没有类似cause的寄存器. mips32和riscv32也可以这样吗? 为什么?
还不知道
-
对比异常处理与函数调用
我们知道进行函数调用的时候也需要保存调用者的状态: 返回地址, 以及calling convention中需要调用者保存的寄存器. 而CTE在保存上下文的时候却要保存更多的信息. 尝试对比它们, 并思考两者保存信息不同是什么原因造成的.
保存上下文除了通用寄存器信息和PC等,还要CSR寄存器的信息,包括异常原因、异常状态等
-
理解上下文结构体的前世今生
你会在
__am_irq_handle()
中看到有一个上下文结构指针c
,c
指向的上下文结构究竟在哪里? 这个上下文结构又是怎么来的? 具体地, 这个上下文结构有很多成员, 每一个成员究竟在哪里赋值的?$ISA-nemu.h
,trap.S
, 上述讲义文字, 以及你刚刚在NEMU中实现的新指令, 这四部分内容又有什么联系?-
c
指向的上下文结构在trap.S
的__am_asm_trap
-
开辟
CONTEXT_SIZE
大小的栈空间,把通用寄存器压入栈,然后读出mcause、mstatus、mepc
到通用寄存器暂存,然后压入一定偏移的内存中。然后设置mstatus.MPRV to pass difftest,然后跳转到__am_irq_handle
,然后再取出CSR和通用寄存器恢复。
-
-
理解穿越时空的旅程
-
主函数如下,进入之前通过
asm volatile("csrw mtvec, %0" : : "r"(__am_asm_trap));
定义好异常入口。并把simple_trap
作为异常后执行的函数。 -
首先进入
yield()
,该函数由内敛汇编指令组成,并调用ecall
指令.
asm volatile("li a5, -1; ecall");
,在RV32E中,把a5置为-1(RV32是a7),记录原因,也就是mcause;ecall
会发起自陷,即调用isa_raise_intr
,这里有一点需要注意,ecall的异常码固定为11(0xb),因此这里直接传入11而不是保存原因的寄存器。
-
进入
isa_raise_intr
,记录mcause
和mepc
,跳转到trap.s
定义的异常入口__am_asm_trap
; -
保存现场后,执行回调函数
__am_irq_handle
,借助mcause
识别异常的类型并处理(这里是yield,0x0b),另外用软件的方式把mepc+4;指向自陷指令PC的下一条;然后调起user_handler
,测试中为simple_trap
,会执行相应的命令,这里是输出字符"y"。 -
执行完成后,回到
trap.s
进行现场复原,即把之前保存到栈里的csr和通用寄存器弹出,通过mret返回,也就是yield的下一条指令。