xv6 Lab (traps)
Nowherechan 学徒

博客的进度总算是跟上了实验的进度,接下来就是博客催着实验往前跑了。

这章倒是花了我不少时间。

RISC-V assembly

回答一些问题,大致目的是帮助读者来熟悉 riscv 相关的知识,跟 csapp 以及计组中学到的知识大差不差,此处略过。

Backtrace

写一个内核函数,backtrace,打印出栈上每次函数调用的返回地址;然后为了测试,在 sleep 中调用 backtrace。实际上应该是在 panic 中调用 backtrace,这样就知道 panic 之前内核做了什么事情,方便调试。

题面中给出了获取寄存器数值的汇编调用,直接复制进 kernel/riscv.h 即可。

栈帧的结构在题面给出的lecture notes中给出,不难发现 s0 寄存器保存当前栈帧的基地址,sp 寄存器保存栈顶。s0 的值大于 sp,因为栈从高地址开始往低地址生长。s0-8 的地址上保存着 ra,s0-16 的地址上保存着上一个栈帧的 s0。因此依次往上迭代 s0,顺便打印 ra,即可完成要求。
但是要考虑从 kernel/proc.c/proc_mapstacks() 函数以及 kernel/memlayout.h 来看,每个 kstack 大小为 1 个页面,并且上下都有 guard page。既然大小为 1,那么可以直接用 PGROUNDUP 来确定 kstack 的最顶部(这里是指内存最高位地址而非栈顶),当 s0 超出该值之后就不再迭代。
代码

Alarm

实现两个系统调用,分别是 sigalarm(interval, handler)sigreturn(void),作用为:普通程序调用 sigalarm,输入间隔,每当运行到第 interval 个时钟中断时,就会修改 epc,从而中断返回到 handler 处,运行一次 handler 函数,其中 handler 为函数指针;handler 函数执行完函数体中的内容后,调用 sigreturn,进入内核,再将 epc 修改为原程序触发时钟中断时的值,再返回到原程序中。

那么问题来了:
为什么要这么复杂?因为 handler 函数是在用户态进行的。
handler 函数在执行的时候不会破坏栈吗?这正是需要读者自己实现的。handler 编译后跟普通函数一样,被调用时会自己减小 s0 和 sp 寄存器,将自己的局部变量放到栈上,只是执行结束后再将 s0 和 sp 加回去以及读取 ra 的操作没有执行,因为此前就会调用 sigreturn。但是即使是这样也无所谓,因为时钟中断触发 sigalarm 的设置之后,需要保存包括 s0 和 sp 在内的各个通用寄存器,在 sigreturn 的过程中再进行恢复。因此 sigreturn 返回后,原程序根本就不知道有一个 handler 执行了,只有检查全局变量的值的时候才会发现。

代码反反复复改了 7 个小时,gdb 调试时间不清楚,总之一整天就耗这上面了,乐。
代码

Traps

本来做完实验很有感触,想写写中断的控制流程,但是真的开始写,发现想写的内容几乎就是把 xv6 book 的原文进行一遍翻译,或者说,“用自己的话来讲一遍 xv6 book”。然后就觉得毫无意义,也没了兴致。

但是 xv6 的时钟中断很特殊,因为时钟中断运行在机器模式下,不能被 xv6 屏蔽,即关中断无用。那么如果系统正处于保存寄存器的阶段,就会干扰到这一过程,保存寄存器这个操作必须是原子的。xv6 似乎通过特殊方式把时钟中断通过某种方式引进了设备中断中。处理过程代码在 kernel/kernelvec.S/timervec 中,大致思路是触发后立即产生一个软中断,这样就可以交给普通的 trap 去处理。但是相关代码我还没有完全看懂,此处难以详述。

之后找机会再补上。

 Comments