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
之前不会再次启用中断。