traps
简介
此文档研究系统调用write()从用户空间进入内核空间,再从内核空间返回到用户空间的整个过程
用户空间进入内核空间
首先,查看user/sh.asm可知,系统调用write()的ecall指令所在地址为0x1410
$ vim user/sh.asm
000000000000140e <write>:
.global write
write:
li a7, SYS_write
140e: 48c1 li a7,16
ecall
1410: 00000073 ecall
ret
1414: 8082 ret然后,通过gdb在ecall指令所在地址设置断点,并且运行到断点处
(gdb) b *0x1410
Breakpoint 1 at 0x1410
(gdb) c
Continuing.
Breakpoint 1, 0x0000000000001410 in ?? ()
=> 0x0000000000001410: 73 00 00 00 ecall打印PC地址为起点的前2条指令
查看系统调用write()的参数buf内容
打印用户页表的地址
查看stvec、pc、sepc的值
执行ecall指令后,从用户空间进入内核空间,即 将User mode切换成Supervisor mode, 寄存器pc复制到 寄存器sepc,寄存器stvec复制到 寄存器pc,同时关闭全部中断
寄存器sscratch保存trapframe起始地址(在上一次返回到用户空间时配置),交换 寄存器sscratch与寄存器a0的值,然后通过sd指令保存用户空间使用过的32个通用寄存器
将trapframe存储的kernel stack pointer(在上一次返回到用户空间时配置)加载到寄存器sp中
将trapframe存储的hardid(在上一次返回到用户空间时配置)加载到寄存器tp中
将trapframe存储的usertrap()函数指针(在上一次返回到用户空间时配置)加载到寄存器t0中
将trapframe存储的kernel page table(在上一次返回到用户空间时配置)加载到寄存器t1中,同时写入到寄存器satp中(即 此时从用户页表切换到内核页表)
进入usertrap()函数
将寄存器stvec设置成kernelvec()函数指针,即 如果在系统调用write()在内核空间处理过程时,遇到中断或异常等,进去kernelvec()进行处理,而不是uservec()。
将寄存器sepc保存到trapframe的epc变量
读取寄存器scause,如果等于8进入,同时将trapframe的epc变量加4
进入syscall(),然后通过读取寄存器a7得到系统调用号,调用sys_write()
内核空间返回到用户空间
返回到usertrap(),继续调用usertrapret
将寄存器stvec重新设置为uservec()函数指针,同时保存trapframe的kernel_satp、kernel_sp、kernel_trap、kernel_hartid、epc变量(为下一次进入内核空间作准备)
同时将 寄存器sstatus设置为:下一次执行sret指令后,Risc-V进入 User mode 和 开启全部中断
准备内核页表地址,保存到satp变量中,然后进入userret()
从内核页表切换到用户页表,然后恢复用户空间的32个通用寄存器,最后 寄存器sscratch也保存trapframe的起始地址(为下一次进入内核空间作准备)
执行sret后,从内核空间切换到用户空间。即 将Supervisor mode切换成 User mode, 寄存器sepc复制到 寄存器pc,同时重新开启全部中断
页表
通过qemu可以打印 用户空间页表 和 内核空间页表
用户空间页表
内核空间页表
Last updated
Was this helpful?