用户态与内核态切换
用户态与内核态切换分为两个操作,一个是从内核态到用户态,另一个是从用户态到内核态。由于用户态不能任意跳转到内核态的内存(不然基于特权级权限保护就没用了),我们必须在切换到用户态前设置好回到内核态的地址。
另外,在用户态与内核态切换时,通常我们需要切换页表(也有一些实现是不需要的)。在切换页表的时候,我们必须保证切换前后 pc 所在的内存是合法的。通常我们会使用 trampoline(蹦床)机制来完成从内核态与用户态的切换。以内核态切换到用户态为例,我们会先从内核地址跳到 trampoline,然后再从 trampoline 跳到用户态。请注意,如果内核所在的地址可能会与应用的地址空间冲突,那么必须使用 trampoline 机制。
因此,内核态进入用户态前,我们需要设置好用户态的环境(如 spp 设置为用户态),在内核地址空间储存必要的信息,在用户内存空间 (trap context) 储存恢复到内核态的信息(如内核的页表地址、内核栈地址等)。完成这些操作后,我们需要跳转到 trampoline,通过 trampoline 设置好用户的页表,并通过 sret
返回用户态。
第一次从内核态进入用户态的过程和之后从内核态进入用户态的过程实际上是一样的,所以我们一般会将两者合到一起。
在进行一些核心操作时,你需要先暂时禁用 S mode 的中断,以免在切换过程中发生中断,导致内核的不一致。在发生中断时,你无需手动禁用中断,因为 RISCV 特权指令集规定了在发生中断时,中断会自动被禁用,知道手动设置了允许中断。