Traps
本Lab主要熟悉在trap中系统调用的实现。
笔者用时约4h
概述
首先讲讲我对系统调用时trap的理解,用户程序通过系统调用进入内核态,在汇编代码中的体现就是ecall指令,会把系统调用参数保存在寄存器a7中。
在用户态中执行ecall指令之后,会将pc寄存器的内容复制到sepc中,并将模式转换为监督者模式,且会将stvec复制到pc中。
由于stvec的值为函数uservec(定义在kernel/trampoline.S:37)的地址,故在执行完ecall指令之后,会跳转到uservec函数中。由于ecall指令并没有切换页表的功能,于是在用户页表中必须要维护uservec函数虚拟地址的映射,这也就是trampoline页的功能啦。
在uservec函数中,首先需要将用户态中的所有寄存器内容保存到该用户进程的trapframe中,一开始trapframe的首地址保存在sscratch寄存器中,首先代码将a0寄存器与sscratch寄存器进行交换,然后开始保存所有其他寄存器的值(根据提前规定好的偏移量,定义在kernel/proc.h:44)。然后需要把当前进程trapframe中的一些字段保存到对应的寄存器中,比如kernel_sp保存到sp寄存器之类的。最后需要将该内核页表地址写入satp寄存器中,切换页表为内核页表。这里再一次体现了,内核页表和进程页表共同映射了trampoline页的原因。
uservec函数最后调用了usertrap函数(定义在kernel/trap.c:37中),在该函数中,首先向stvec寄存器写入kernelvec函数(定义在kernel/kernelvec.S中)的地址(因为这个时候在内核态,于是trap由kernelvec函数进行处理),并将当前sepc寄存器中的值保存到trapframe的epc字段中,然后将epc加4,即跳过了ecall指令,指向用户态的下一条指令。然后调用syscall函数进行系统调用的操作。
当syscall函数返回时,会调用usertrapret函数(定义在kernel/trap.c:103中),该函数将uservec重新写入stvec寄存器中,将trapframe中的一些字段设置好,并将模式设置为用户模式,以便处理下一次的用户态trap。最后,将trapframe的地址作为第一个参数(保存在a0中),将用户进程跟页表地址作为第二个参数(保存在a1中),调用userret函数(定义在kernel/trampoline.S:88中)。
在函数userret中,首先切换页表为用户进程页表,且将trapframe页的地址保存到寄存器a0中,然后恢复所有寄存器的值,将原始a0寄存器的值(在系统调用情况下,trapframe中的a0字段被设置为系统调用返回值)放在sscratch寄存器中,最后交换a0寄存器和sscratch寄存器的值,并使用sret指令(将模式变为用户模式,将sepc寄存器中的内容复制到pc寄存器中)回到用户态。
RISC-V assembly
回答一些问题
- Q: 哪些寄存器保存函数的参数?例如,在main对printf的调用中,哪个寄存器保存13?
A: 寄存器a0-a7保存函数参数,比如printf的调用中,第一个参数是字符串地址保存在a0中,f(8)+1保存在a1中,13保存在a2中。 - Q: main的汇编代码中对函数f的调用在哪里?对g的调用在哪里(提示:编译器可能会将函数内联)
A: 被编译器优化掉了,减少了函数的调用,从li a1, 12可以看出 - Q: printf函数位于哪个地址?
A: 0x630, 在call.asm中搜一下就可以看到 - Q: 在main中printf的jalr之后的寄存器ra中有什么值?
A: 在main中,printf的jalr指令会将下一条指令的地址(也就是0x38)保存在寄存器ra中,于是jalr之后的寄存器ra中的值为0x38 - Q: 运行以下代码。
程序的输出是什么?unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i);
A: HE110 World
Q: 输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i设置成什么?是否需要将57616更改为其他值?
如果是大端存储,那么将i设置为0x726c6400即可,不需要将57616改为其他值 - Q: 在下面的代码中,“y=”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?
A: y将打印成什么取决于上一次a2寄存器中的内容printf("x=%d y=%d", 3);
Backtrace
这一部分需要实现一个函数backtrace,在调用处打印当前栈中所有栈帧保存的返回地址信息。
那么首先需要获取当前栈帧的帧尾指针fp,根据文档提示,我们可以在文件kernel/riscv.h中添加一个函数进行获取。
// read the frame pointer
static inline uint64
r_fp()
{
uint64 x;
asm volatile(

本文介绍了RISC-V架构中系统调用的实现过程,包括用户态通过ecall指令进入内核态,trapframe的使用,页表切换以及uservec和kernelvec的处理。此外,还涉及到了栈回溯(backtrace)功能的实现以及alarm机制,包括sigalarm和sigreturn系统调用的细节,如何在CPU时钟中断后恢复中断前的代码执行。
最低0.47元/天 解锁文章
2775

被折叠的 条评论
为什么被折叠?



