MIT 6.s081 Lab4 Trap
RISC-V assembly
1 | int g(int x) { |
1 | 0000000000000000 <g>: |
RISC-V trap machinery
- stvec :内核在这里写入它的trap处理程序的地址;RISC-V跳转到stvec中的地址来处理trap。
- sepc:当一个trap发生时,RISC-V将程序计数器保存在这里(因为pc会被stvec中的值覆盖)。sret (return from trap)指令将sepc复制到pc上。内核可以编写sepc来控制sret的位置。
- scause:RISC-V在这里放了一个数字来描述trap的原因。
- sscratch: trap处理程序代码使用sscratch来避免在 保存用户寄存器之前覆盖用户寄存器。
- sstatus:sstatus中的SIE位控制是否启用设备中断。如果内核清除了SIE, RISC-V将延迟设备中断,直到内核设置了SIE。SPP位表示trap来自用户模式还是管理模式,并控制sret返回哪种模式。
RISC-V中断发生过程:
- 如果设备中断,且sstatus SIE位为清零,则无需执行以下操作
- 通过清除sstatus中的SIE位来禁用中断。
- Copy the pc to sepc.
- 将当前模式(user或supervisor)保存在sstatus的SPP位中。
- 设置原因以反映陷阱的原因。
- Set the mode to supervisor
- Copy stvec to the pc.
- 在新的pc位置开始执行
User Trap
用户中断当用户调用了ecall指令时发生(或发生了非法操作或硬件中断)。
用户发生中断:
step1: uservec
step2: usertrap
当中断返回:
step1: usertrapret
step2: userret
1. 发生中断
TRAMPOLINE page在程序初始化时放置,位于user虚拟地址的顶部,同时TRAMPOLINE在内核页表也被映射。且没有 PTE_U标志。因此trap handler在切换到内核page后可以继续执行。
为了保存用户状态,uservec会将用户寄存器状态保存在TRAPFRAME(一个结构体)。TRAPFRAME 在 TRAMPOLINE之下。
1 | struct trapframe { |
TRAPFRAME中保存了内核page的信息和cpu信息,uservec从这里获取信息。然后执行usertrap。
usertrap的工作是确定trap的原因, 运行trap并返回。usertrap首先会保存sepc(用户程序计数器)。如果该trap是一个系统调用,则usertrap调用sycall来处理它;如果设备中断,devintr;否则它是一个异常,内核会终止发生故障的进程。
系统调用路径在保存的用户程序计数器上增加了4,因为RISC-V在系统调用的情况下,用户代码需要在后续指令处恢复执行(不能反复执行sys call)。在退出过程中,usertrap检查进程是否已经被杀死或应该产生CPU(如果这个trap是一个定时器中断)。
2. 中断返回
返回第一步是运行usertrapret。然后执行userret。这俩恢复了一些寄存器状态,返回用户空间。
initcode.S(如何调用sys call)
initcode.S将exec的参数放在寄存器a0和a1中,并将系统调用号放在a7中。系统调用号匹配syscalls数组中的条目,syscalls数组是一个函数指针(kernel/syscall.c:107)。调用指令被捕获到内核中,并导致uservec、usertrap和sycall执行。
1 | la a0, init |
Syscall (kernel/ Syscall .c:132) 从trapframe中保存的a7中获取系统调用号,并使用它索引到系统调用中。对于第一个系统调用,a7包含SYS_exec (ker- nel/ sycall .h:8),导致调用系统调用实现函数SYS_exec。
当sys_exec返回时,系统调用将返回值记录在p->trapframe->a0中。这将导致对exec()的原始用户空间调用返回该值,因为RISC-V上的Ccall约定将返回值放在a0中。
系统调用通常返回负数表示错误,返回零或正数表示成功。如果系统调用号无效,系统调用将打印错误并返回−1。