前面实现了协作式多任务,但是现在的操作系统往往采用的是抢占式多任务,这意味着操作系统会剥夺某个任务对hart的使用权,这涉及到interrupt。在RISC-V中外部的interrupt和内部的exception总称为trap,两者在处理逻辑上区别不大,下面是大致过程。
在实验四的基础上,通过在任务里面查看mtvec寄存器的值,会发现为0,若添加*(int *)0x00000000 = 1;指令则会发生exception,系统会执行玩初始化和top half部分,但是当要跳转到由mtvec指向的地址时会出错,因为这里没有给mtvec赋值,即初始化部分为空,只执行了top half,故而在bottom half阶段会出错。
所以接下来就是实现初始化和bottom half,即定义trap_vector函数,并赋值给mtvec。
void trap_init()
{
asm volatile("csrw mtvec, %[trap_vector]" : : [trap_vector] "r"(trap_vector));
}
其实就是把tarp_vector的地址写给mtvec寄存器。
.balign 4
trap_vector:
csrrw t6, mscratch, t6
write_ctx_from_reg t6
csrr t5, mscratch
STORE t5, 30*REG_SIZE(t6)
csrw mscratch, t6
csrr a0, mepc
csrr a1, mcause
call trap_handler
csrw mepc, a0
csrr t6, mscratch
write_reg_from_ctx t6
mret
因为mtvec寄存器中的BASE字段是[31:2],故trap_vector函数的地址必须是4B对齐;mtvec寄存器的后两位记录了该trap_vector函数是Direct还是Vectored的mode,前者表示所有trap的处理都会将pc设置为BASE,后者表示发生interrupt时pc被设置为BASE+mcause*4。
.h文件既支持C语言中的#define宏定义,还支持汇编语言中的.macro.end宏定义,故而这里把上下文的保存和恢复放在了.h文件中,但是这个头文件只能被汇编文件include,不能被C语言文件include,否则编译器会报错。另外,s和S文件都支持.include,只有S支持#include。
trap_handler有两个参数,第一个是mepc,第二个是mcause;保存上下文即保存发生trap的任务的上下文,恢复上下文也是指恢复发生trap的任务的上下文,即同一个任务,故mscratch在trap_handler前后指向的是同一个内存地址中的上下文。
reg_t trap_handler(reg_t mepc, reg_t mcause)
{
reg_t return_pc = mepc;
reg_t cause_code = mcause & MCAUSE_MASK_ECODE;
if (mcause & MCAUSE_MASK_INTERRUPT)
{
printf("Interruption Happened, Exception Code : %ld\n", cause_code);
switch (cause_code)
{
case 3:
uart_puts("Software Interruption\n");
break;
case 7:
uart_puts("Timer Interruption\n");
break;
case 11:
uart_puts("External Interruption\n");
break;
default:
uart_puts("Unkonwn Interruption\n");
break;
}
}
else
{
printf("Exception Happened, Exception Code: %ld\n", cause_code);
panic("What can I do");
}
return return_pc;
}
一些说明: