usertrap
xv6 中 usertrap 和 kerneltrap 是两种方式。对于 syscall 时的 usertrap,流程大概是 uSys.S -> ecall -> uservec -> usertrap -> syscall -> usertrapret -> userret -> sret。 感觉 xv6 里面的 usertrap 比较奇怪。
- riscv 的 ecall 只会修改 mode、保存 pc 到 sepc 以及跳到 stvec 三件事。
- 在这里,ecall 是 user mode 转 supervisor mode;
- stvec 指向的是 trampoline,也是 uservec。
- stvec 中的值是在第一个 hart 创建时在 kernel/main.c 中调用 trapinithart 时被存入的。
- uservec 中主要干四件事,
- 将一些 general reg 和 system reg 的值存到 trapframe 中;
- 将 trampoline 中的 kernel_sp、kernel_trap 等值存入 sp、t0 等寄存器中;
- 修改 satp 的值使内核使用 user pagetable 而非 kernel pagetable;
- 跳到 usertrap。
- usertrap 干的几件事。
- 修改 stvec 的值,从 user stvec 的 0x3ffffff000 到 0x80004f40 (kernelvec defined in kernelvec.S)。
- 【系统调用】xv6 中定义 syscall 的 scause 为 8。usertrap 开中断后会调用 syscall 函数。syscall 是系统调用的总入口。
- usertrap 结束后,会调用 usertrapret 结束陷入,会做一些回归 user mode 的工作。
- 关中断并恢复 user stvec 的值从 0x80004f40 到 0x3ffffff000。
- 将 sp、t0 等寄存器存入 trampoline 中的 kernel_sp、kernel_trap 中。
- 告知 cpu 下次执行 sret 后返回 user mode,并把 sret 执行需要的 pc。
- 跳转到 trampoline 中的 userret 中。
- userret 干的几件事:
- 切换到 user pagetable;
- 通过现有的 reg a0 (指向 trapframe,112(a0) 是 trapframe 中的 a0 的 address,这两个 a0 的概念不是一样的) 获取 syscall 的返回值并存入 reg a0;
- 恢复寄存器到陷入前状态,尤其是把 sscratch 重新指向 trapframe;
- sret 返回,从 supervisor mode 恢复到 user mode 并恢复 pc。
注
- sscratch 用于保存 kernel stack address。在 supervisor mode 下,sscratch 的值不重要。
- user 的 stvec 和 kernel 的 stvec 不是同一个。
- 一个非常简单的道理,xv6 被划分成了 kernel 目录与 user 目录,那自然 user/usys.S 中的 .global write 等系统调用 entry 执行时的 OS 在 user mode;而 ecall 后跳转到的位于 kernel/trampoline.S 的 uservec 在 supervisor mode。虽然 mode 变了,但 kernel 目录下的代码并非都在 “完全” 的内核态中运行,存在部分代码运行时还有 system reg 没修改过。
- 比如 uservec 修改页表之后再跳转到 usertrap 就是一个非常简单的道理。
在 userret 的最开始,a0 仍旧指向 trapframe (因为在 uservec 的最开始,
csrrw a0, sscratch, a0
就把 sscratch 中存的 trapframe 赋给 a0 了),为了获取 syscall 的返回值 (同样存在 a0,但是是 trapframe 中的 a0),所以使用 112(a0) 先获取返回值到某一寄存器中,然后再运作到真正的 a0 中。1
2
3
4
5
6
7
8
9
10struct trapframe {
/* 0 */ uint64 kernel_satp; // kernel page table
/* 8 */ uint64 kernel_sp; // top of process's kernel stack
/* 16 */ uint64 kernel_trap; // usertrap()
/* 24 */ uint64 epc; // saved user program counter
/* 32 */ uint64 kernel_hartid; // saved kernel tp
/* balabala */
/* 112 */ uint64 a0;
/* balabala */
};