usertrap

xv6 中 usertrap 和 kerneltrap 是两种方式。对于 syscall 时的 usertrap,流程大概是 uSys.S -> ecall -> uservec -> usertrap -> syscall -> usertrapret -> userret -> sret。 感觉 xv6 里面的 usertrap 比较奇怪。

  1. riscv 的 ecall 只会修改 mode、保存 pc 到 sepc 以及跳到 stvec 三件事。
    1. 在这里,ecall 是 user mode 转 supervisor mode;
    2. stvec 指向的是 trampoline,也是 uservec。
    3. stvec 中的值是在第一个 hart 创建时在 kernel/main.c 中调用 trapinithart 时被存入的。
  2. uservec 中主要干四件事,
    1. 将一些 general reg 和 system reg 的值存到 trapframe 中;
    2. 将 trampoline 中的 kernel_sp、kernel_trap 等值存入 sp、t0 等寄存器中;
    3. 修改 satp 的值使内核使用 user pagetable 而非 kernel pagetable;
    4. 跳到 usertrap。
  3. usertrap 干的几件事。
    1. 修改 stvec 的值,从 user stvec 的 0x3ffffff000 到 0x80004f40 (kernelvec defined in kernelvec.S)。
    2. 【系统调用】xv6 中定义 syscall 的 scause 为 8。usertrap 开中断后会调用 syscall 函数。syscall 是系统调用的总入口。
  4. usertrap 结束后,会调用 usertrapret 结束陷入,会做一些回归 user mode 的工作。
    1. 关中断并恢复 user stvec 的值从 0x80004f40 到 0x3ffffff000。
    2. 将 sp、t0 等寄存器存入 trampoline 中的 kernel_sp、kernel_trap 中。
    3. 告知 cpu 下次执行 sret 后返回 user mode,并把 sret 执行需要的 pc。
    4. 跳转到 trampoline 中的 userret 中。
  5. userret 干的几件事:
    1. 切换到 user pagetable;
    2. 通过现有的 reg a0 (指向 trapframe,112(a0) 是 trapframe 中的 a0 的 address,这两个 a0 的概念不是一样的) 获取 syscall 的返回值并存入 reg a0;
    3. 恢复寄存器到陷入前状态,尤其是把 sscratch 重新指向 trapframe;
    4. sret 返回,从 supervisor mode 恢复到 user mode 并恢复 pc。

  1. sscratch 用于保存 kernel stack address。在 supervisor mode 下,sscratch 的值不重要。
  2. user 的 stvec 和 kernel 的 stvec 不是同一个。
  3. 一个非常简单的道理,xv6 被划分成了 kernel 目录与 user 目录,那自然 user/usys.S 中的 .global write 等系统调用 entry 执行时的 OS 在 user mode;而 ecall 后跳转到的位于 kernel/trampoline.S 的 uservec 在 supervisor mode。虽然 mode 变了,但 kernel 目录下的代码并非都在 “完全” 的内核态中运行,存在部分代码运行时还有 system reg 没修改过。
  4. 比如 uservec 修改页表之后再跳转到 usertrap 就是一个非常简单的道理。
  5. 在 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
    10
    struct 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 */
    };