MIT 6.s081 2020 Lab4 Trap
lab4 是 traps 相关
预备知识
系统调用的时候发生了什么?例如 write()?
- 保存 32 个用户寄存器以及 pc
- 切换到 supervisor 模式
- 切换到内核页表
- 切换到内核栈
- 跳到 C 代码处
不要在 supervisor 模式执行用户代码!我之前就是这个思路(如何在内核空间中执行用户代码),想了很久都没有答案,然后看了别人的解析才发现,原来思路错了!
在 supervisor 模式,多了哪些特权?
- supervisor 模式下能够使用 CPU 的控制状态寄存器:
- satp – 保存了页表物理地址
- stvec – ecall 会跳转到该寄存器保存的地址处,即 TRAMPOLINE
- sepc – ecall 会将用户 pc 保存到 sepc 中
- sscratch – 保存 trapframe 的地址
- supervisor 模式下能够使用没有 PTE_U 标志的 PTEs
除此之外,supervisor 模式没有什么特别的了!它也不能访问不在其页表中的内容!
Backtrace
添加一个 backtrace 函数,sys_sleep 调用这个函数后可以打印出函数调用栈
实现思路:
根据 RISCV ABI,ra 寄存器存放返回地址,sp 寄存器存放栈顶指针,s0 寄存器存放栈基指针。在 RISCV 上的汇编语言 ABI 规定,在开辟新的函数调用栈的时候,将上一个栈的 栈基指针 存到新的 栈基-16 处;将 ra 存放到新的 栈基-8 处。因此我们可以根据栈基指针遍历整个函数调用过程,直到 栈基指针 的值超过了内核栈。
实现:
这个实现还是很简单的。
首先我们要获取 栈基指针 s0:用 C 代码封装汇编指令。
1 | kernel/riscv.h: |
然后不断根据 栈基指针 来遍历内核栈即可,结束条件就是遍历超过一页时停止。
1 | kernel/printf.c: |
然后在 sys_sleep 中调用就可以了。
1 | kernel/sysproc.c: |
Alarm
这个实验要我们实现一个定时器中断处理。
定时器中断是软中断,每过一个 ticks 都会触发定时器中断,即会从用户态陷入内核态处理定时器中断相关逻辑。
通过 sigalarm 系统调用来注册一个定时器中断(每隔多久执行以此中断处理函数)。添加系统调用的方式和 lab2 一样。这个注册是对应于某个具体进程的,因此需要在 struct proc 结构体中新添加几个字段来保存 间隔时间 和 中断处理函数虚拟地址:
1 | // Per-process state |
这样,在 sys_sigalarm 中对当前申请注册中断处理的进程的结构体字段进行相应的填写,然后修改 trap.c/usertrap 中处理定时器中断的逻辑即可。
由于 系统调用、中断、异常都会将 TRAMPOLINE 作为内核的进入点,因此执行的是同一套逻辑,只是会根据 陷入内核的原因(scause 中的值)来区分到底是三类中的哪一类导致的 trap:
1 | if(r_scause() == 8){ |
如果 scause == 8 则是系统调用,其他的则为中断或异常,可以根据 devintr() 的返回值来判断:
1 | // 2 if timer device, |
我们只要添加一个处理定时器中断的逻辑即可。
这里这个逻辑该怎么实现呢?我刚开始一直琢磨着如何在内核态去执行用户空间的代码,想到了建立进程内核页表(即 lab3 的内容),但是一想这实验应该不会涉及到前面的内容呀。。。然后实在没办法才看了下解析,看到别人的思路后恍然大悟。
思路:用户陷入内核时把 pc 值存在了 sepc 中,然后在 TRAMPOLINE 中做了进程状态的保存(将 sepc 保存到 p->trapframe->sepc 中)进入 usertrap;随后必定要按原路返回,通过 usertrapret 进入 userret 恢复进程的寄存器状态,最后依赖 sepc 回到下一条地址处或原地址处。我们只要在定时器中断处理中,将 中断函数的地址赋值给 p->trapframe->sepc,然后一切就水到渠成了!
当然这样的话相当于打乱了原来用户空间栈中的执行顺序,因此在 中断处理函数 结尾处要调用相应的 sigreturn 系统调用来回到原来的状态。而这个 sigreturn 的功能也就明了了,无非就是在 定时器中断处理中 在修改 p->trapframe->sepc 之前对整个 trapframe 做个备份保存起来,在 sigreturn 中用备份恢复 trapframe 就可以了。
在 struct proc 中新增一个 trapframe 备份字段:
1 | // Per-process state |
在 usertrap 的定时器中断中保存 trapframe 备份:
1 | ··· |
在 sigreturn 中恢复 trapframe:
1 | kernel/sysproc.c: |