2025-10-29
- 一生一芯
- 2025-10-29
- 37热度
- 0评论
riscv通过ecall指令发起自陷,ecall指令会将将pc跳转到mtvec寄存器中的地址。程序通过cte_init将异常处理函数注册到mtvec寄存器中,同时将应用程序传来的处理函数句柄保存下来。mtvec中保存的程序地址是在汇编中定义的__am_asm_trap函数,这个函数里面会调用句柄中的函数。现在我不明白的是mtvec这个寄存器要放到哪里呢?
->答案就是有物理寄存器,跟32个通用寄存器一样,所以在numu中添加定义,具体的地址在riscv特权架构中有定义,如下表所示

Context结构体需要重新组织,原因是trap.S中在调用异常之前会保存上下文,这一功能是通过汇编来实现的所以保存的顺序已经固定了,为了让__am_irq_handle能正确地找到参数在栈中的位置,需要重新排列Context结构体的顺序,对照trap.S汇编进行修改就可以。再在nemu中实现csrrw,csrrs,mret,trap这4条指令,这个case就可以出现想要的结果了。
__am_irq_handle()中上下文结构在哪里?
在栈中,trap.S的函数__am_asm_trap完成上下文的保存并调用__am_irq_handle函数
从yield test调用yield()开始,到从yield( )返回的期间,这一趟旅程具体经历了什么?
首先从AM的角度,调用yield( )后开始执行yield( )函数,yield( )函数是一段内联汇编,包含两条指令,第一条将-1放到寄存器$a7,这个-1实际上就是yield事件的编号;第二条指令是ecall触发内陷,ecall指令的执行内容为跳转到mtvec寄存器中保存的地址并记录一些csr信息,在nemu中这个功能是由isa_raise_intr函数实现的,这个函数的参数为事件的编号NO和触发事件的指令的pc,也就是ecall指令的地址,函数的功能是将NO保存在mcause寄存器,将epc保存在mepc,将0x1800保存在mstatus,返回mtvec,之后nemu中指令译码和执行单元将isa_raise_intr返回的地址作为下一条指令的地址并执行。在yield( )之前AM已经通过cte_init( )函数将自陷处理函数__am_asm_trap保存在了mvtec寄存器,所以执行ecall后就会跳转到__am_asm_trap函数。这个函数是在trap.S中通过汇编实现的,该函数首先会保存上下文,包括除寄存器0和sp外的30个通用寄存器和mcause,mstatus,mepc三个csr寄存器,保存完成后会将栈指针放入$a0寄存器并调用__am_irq_handle( )函数,这个函数的作用是根据保存的csr寄存器判断是什么事件导致的这次中断,并将这个信息保存在变量event中,然后调用应用程序定义的中断处理函数来处理这一中断。中断处理函数也是在cte_init( )中注册过的,yield test的处理函数比较简单,就是根据事件编号打印一些东西然后返回,返回到__am_irq_handle函数后简单判断中断处理函数的返回值没有问题就再返回到__am_asm_trap函数。至此中断已经处理完毕,__am_asm_trap会恢复上下文,也就是从栈中将数据再加载出来,然后调用mret返回,mret会将pc置为mepc的下一条指令
看了下课程主页,前方任务还非常的多,但是都很有价值