4.5 从内核空间陷入

xv6根据执行的是用户代码还是内核代码,对CPU陷阱寄存器的配置有所不同。当在CPU上执行内核时,内核将stvec指向kernelvec(kernel/kernelvec.S:10)的汇编代码。由于xv6已经在内核中,kernelvec可以依赖于设置为内核页表的satp,以及指向有效内核栈的栈指针。kernelvec保存所有寄存器,以便被中断的代码最终可以不受干扰地恢复。

kernelvec将寄存器保存在被中断的内核线程的栈上,这是有意义的,因为寄存器值属于该线程。如果陷阱导致切换到不同的线程,那这一点就显得尤为重要——在这种情况下,陷阱将实际返回到新线程的栈上,将被中断线程保存的寄存器安全地保存在其栈上。

Kernelvec在保存寄存器后跳转到kerneltrap(kernel/trap.c:134)。kerneltrap为两种类型的陷阱做好了准备:设备中断和异常。它调用devintr(kernel/trap.c:177)来检查和处理前者。如果陷阱不是设备中断,则必定是一个异常,内核中的异常将是一个致命的错误;内核调用panic并停止执行。

如果由于计时器中断而调用了kerneltrap,并且一个进程的内核线程正在运行(而不是调度程序线程),kerneltrap会调用yield,给其他线程一个运行的机会。在某个时刻,其中一个线程会让步,让我们的线程和它的kerneltrap再次恢复。第7章解释了yield中发生的事情。

kerneltrap的工作完成后,它需要返回到任何被陷阱中断的代码。因为一个yield可能已经破坏了保存的sepc和在sstatus中保存的前一个状态模式,因此kerneltrap在启动时保存它们。它现在恢复这些控制寄存器并返回到kernelvec(kernel/kernelvec.S:48)。kernelvec从栈中弹出保存的寄存器并执行sret,将sepc复制到pc并恢复中断的内核代码。

值得思考的是,如果内核陷阱由于计时器中断而调用yield,陷阱返回是如何发生的。

当CPU从用户空间进入内核时,xv6将CPU的stvec设置为kernelvec;您可以在usertrap(kernel/trap.c:29)中看到这一点。内核执行时有一个时间窗口,但stvec设置为uservec,在该窗口中禁用设备中断至关重要。幸运的是,RISC-V总是在开始设置陷阱时禁用中断,xv6在设置stvec之前不会再次启用中断。