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

然后,通过gdbecall指令所在地址设置断点,并且运行到断点处

(gdb) b *0x1410
Breakpoint 1 at 0x1410
(gdb) c
Continuing.
Breakpoint 1, 0x0000000000001410 in ?? ()
=> 0x0000000000001410:  73 00 00 00     ecall

打印PC地址为起点的前2条指令

查看系统调用write()的参数buf内容

打印用户页表的地址

查看stvecpcsepc的值

执行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保存到trapframeepc变量

读取寄存器scause,如果等于8进入,同时将trapframeepc变量加4

进入syscall(),然后通过读取寄存器a7得到系统调用号,调用sys_write()

内核空间返回到用户空间

返回到usertrap(),继续调用usertrapret

将寄存器stvec重新设置为uservec()函数指针,同时保存trapframekernel_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?