Lec 6: Isolation & system call entry/exit
- Ref: https://github.com/huihongxiao/MIT6.S081/tree/master/lec06-isolation-and-system-call-entry-exit-robert
- Preparation: xv6 book Chapter 4 except 4.6
Trap
- trap: 用户空间和内核空间的切换
- 切换时机:
- 系统调用
- 异常 Exception, 如缺页错误(page fault), 除零错误
- 设备触发的中断
- 重要的寄存器:
- STVEC(Supervisor Trap Vector Base Address Register): 指向内核中处理 trap 的指令首地址(根据来自用户或内核, 代码会有不同)
- SEPC(Supervisor Exception Program Counter): trap 过程中保存程序计数器的值
- SSRATCH(Supervisor Scratch Register): 用于存放 trapframe 页面虚拟地址
- 切换时需要完成的操作:
- 保存 32 个用户寄存器
- 保存程序计数器 PC
- 将模式 MODE 由用户态切换为内核态
- 将 SATP 寄存器由用户页表指向内核页表
- 需要一个栈来调用内核的 C 函数, 并使 SP 指向
- 跳入内核 C 代码
- 内核模式(supervisor mode)的特权:
- 读写 SATP 寄存器, 对 STVEC, SEPC, SSCRATCH 等寄存器进行操作
- 可以使用 PTE_U 标志位为 0 的 PTE
Trap 代码执行流程
- 系统调用通过 ECALL 指令切换到内核中(该指令不会切换页表, 此时仍未用户页表)
- 内核执行汇编函数
uservec
, 是trampoline.S
中trampoline
的一部分. 该函数会切换 SSCRATCH 和 a0 寄存器的值, 之后 a0 的值即为 trapframe 的地址, 然后将 32 个用户寄存器等寄存器的值存于p->trapframe
中; 然后会加载内核栈帧指针(kernel_sp), 并加载内核页表地址到 SATP 寄存器完成内核页表的切换. 最后跳转至 C 代码trap.c
的usertrap()
usertrap()
首先将 STVEC 寄存器由usertrap
更改为kerneltrap
(因为此时再发送中断就属于在有来自内核的 trap). 然后若为系统调用则调用syscall()
函数从系统调用表中查找相应系统功能的函数sys_xxx()
进行执行, 执行后返回syscall()
; 若为来自设备的中断则执行devintr
. 最后会调用usertrapret()
.- 执行
trap.c
中的usertrapret()
. 首先会将p->trapframe
中和内核相关的字段的值设置好, 用于下次 trap. 设置 SSTATUS 的用户模式标志位. 最后调用汇编函数userret
. - 执行
trampoline.S
中的userret
函数. 首先切换回用户页表, 然后从 trapframe 中加载值到寄存器, 最后将 trapframe 地址存于寄存器 SSCRATCH 中. - 返回用户空间.
- ECALL 作用:
- 将代码从用户模式转为内核模式
- 将 PC 保存与 SEPC
- 跳转到 STVEC 寄存器指向的(处理 trap的)指令
- 如何保存用户寄存器:
- 内核将 trapframe 映射到了每个用户页表
- 在进入到用户空间前(操作系统启动时是在内核模式), 内核会将 trapframe 地址保存在 SSCRATCH 寄存器, 配合
csrrw
指令进行寄存器a0
和sscratch
内容的互换.