xv6的内核态与用户态的切换 | Blurred code

xv6的内核态与用户态的切换

2020/11/17

Updated:2020/11/17

Categories: xv6 Linux

当一个进程需要调用kernel提供的服务的时候,他们调用一个system call, 在x86上一个system call大概类似于int 0x80的一条指令,而在RSIC-V的xv6调用syscall的方式是通过ecall指令, 并把进行的系统调用的编号放到a7寄存器。ecall会修改特权等级,并且进入到由内核控制的某个函数入口。

.global fork
fork:
 li a7, SYS_fork
 ecall
 ret

xv6kernel

从User进入Kernel

当一个ecall指令被调用,首先跳到uservec的函数。 这个函数具有两个特征

因此内核和每一个用户进程的页表都拥有一个叫做TRAMPOLINE的映射,他们的虚拟地址和物理地址是一样的,在这一页里包含了uservecuserret函数。 每一个进程的proc结构体内,有一个trapframe的页面,这一个页面的地址会被放置到sscratch寄存器,而这一个页面的主要用途是用于保存所有寄存器。 当uservec发生的时候,uservec先找到进程trapframe(此时还是用户态的页表),然后依次在trapframe保存所有的寄存器,保存完所有寄存器以后,切换到内核页表,跳转到usertrap函数,此时已经完全进入内核,在usertrap函数里面判断所有的trap来源。

trapframe

SIGNAL的实现

在xv6的一个lab实验中要求实现sigalarm,需要实现定时器,当定时器事件发生的时候需要进入到用户态去调用signal_handler,其实从内核返回到用户态的时候,需要设置epc的寄存器,确定回到进程以后从哪里执行,默认是从进入内核的指令的下一条指令。修改p->trapframe->epcsignal_handler的地址跳转到signal_handler。 还有更难的要实现sigreturn,就是signal_handler执行完成以后返回到执行前的指令,这需要在进程内部再开辟空间,在跳转到signal_handler之前保存所有的寄存器,这样从signal_handler返回以后可以恢复所有的寄存器,从而在下次回到用户态的时候,恢复到信号发生之前的状态。