一、实验内容:

Part A RISC-V assembly

见问题回答

Part B Backtrace
  • Add the prototype for your backtrace() to kernel/defs.h so that you can invoke backtrace in sys_sleep.

trap里的timestamp trap.s_算法

  • Add the following function to kernel/riscv.h, and call this function in backtrace to read the current frame pointer. r_fp() uses in-line assembly to read s0.

在backtrace中获取s0的值,并用于读取第一个所需的返回地址:

trap里的timestamp trap.s_c++_02

trap里的timestamp trap.s_c++_03

  • 循环,向上遍历帧指针
    实现思路:
    正如第一个地址的操作,每次都是打印fp-8所指向的内容。故可将打印写作循环里,并调整fp为上一个的帧指针,即fp-16所指向的内容。同时,通过 PGROUNDDOWN(fp)PGROUNDUP(fp)来作为循环终止的判断条件。
    具体实现:

测试:

trap里的timestamp trap.s_算法_04

trap里的timestamp trap.s_trap里的timestamp_05

Part C Alarm
test0: invoke handler
  • 准备工作
    Modify the Makefile to cause alarmtest.c to be compiled as an xv6 user program.

The right declarations to put in user/user.h

trap里的timestamp trap.s_c语言_06

Update user/usys.pl (which generates user/usys.S), kernel/syscall.h, and kernel/syscall.c to allow alarmtest to invoke the sigalarm and sigreturn system calls.

trap里的timestamp trap.s_trap里的timestamp_07

trap里的timestamp trap.s_经验分享_08

trap里的timestamp trap.s_c++_09

trap里的timestamp trap.s_算法_10

  • For now, sys_sigreturn should just return zero.
  • sys_sigalarm() should store the alarm interval and the pointer to the handler function in new fields in the proc structure (in kernel/proc.h) . And keep track of how many ticks have passed since the last call (or are left until the next call) to a process’s alarm handler; add a new field in struct proc for this too.

trap里的timestamp trap.s_c++_11

  • Initialize proc fields in allocproc() in proc.c.
  • Every tick, the hardware clock forces an interrupt, which is handled in usertrap() in kernel/trap.c in if(which_dev == 2) .... And modify usertrap() so that when a process’s alarm interval expires, the user process executes the handler function.
    实现思路:
    首先,时间中断处要进行ticks的++,如何判断ticks是否和interval相同,若是则需执行handler函数。执行该函数的具体做法便是将handler的地址赋给程序计数器,即寄存器epc。要注意前面给的sigalarm(0,0)是停止周期调用,其实n==0的意义不大,ticks会恒大于等于0,故要判断interval是否为0,不是才要做上述操作。
    具体实现:

测试:

trap里的timestamp trap.s_c语言_12

可见test0正常通过,但test1不行

test1/test2()/test3(): resume interrupted code
  • Save and restore registers
    实现思路:
    类似于trapframe的构造,再构造一个save_trapframe用于保存之前的所有寄存器。像trapframe,故需要在allocproc()/freeproc()中进行alloc和free。当需要做程序跳转时,将当前的trapframe的内容放到save_trapframe中。
    具体实现:

trap里的timestamp trap.s_经验分享_13

trap里的timestamp trap.s_c语言_14

  • sigreturn should:
  1. correctly return to the interrupted user code.
  2. prevent re-entrant calls to the handler----if a handler hasn’t returned yet, the kernel shouldn’t call it again.
  3. return a0.

实现思路:

借助于之前的save_trapframe,直接将其内容给trapframe就行了。因为epc也在其中,程序计数器会自动跟着做,同时a0也可以轻易的读到。至于保证handler执行时不被调用,只需要把置0这一步放到sigreturn中即可,因为执行的条件是要ticks==interval,只要不置0,handler就不会再被执行。

trap里的timestamp trap.s_c++_15

测试:

trap里的timestamp trap.s_c++_16

Make grade

添加answers-traps.txt和time.txt,运行make grade

trap里的timestamp trap.s_c语言_17

二、问题回答:

(1) 函数的参数包含在哪些寄存器中?例如在main对printf的调用中,哪个寄存器保存13?

trap里的timestamp trap.s_c++_18

从图中可以看出,函数的参数包含在a0~a7寄存器中。在main对printf的调用中,保存13的是a2寄存器。

printf("%d %d\n", f(8)+1, 13);
  24:	4635                	li	a2,13

(2) Main的汇编代码中对函数f的调用在哪里?对g的调用在哪里?(Hint:编译器可能内联函数)

编译器进行了函数内联,直接将f(8)+1的值计算了出来,即12

26:	45b1                	li	a1,12

(3) 函数printf位于哪个地址?

0x65a

34:	62a080e7          	jalr	1578(ra) # 65a <printf>

trap里的timestamp trap.s_c++_19

(4) 在jalr到main中的printf之后,寄存器ra中存储的值是?

ra寄存器保存的是发生函数调用后应该返回的地址值,调用printf后应该返回执行的地址为0x38,故ra存储的即是0x38。

(5) 运行以下代码:

unsigned int i = 0x00646c72;printf("H%x Wo%s", 57616, &i);

输出是什么?输出取决于 RISC-V是 little-endian的。如果 RISC-V 是 big-endian,怎样设置来产生相同的输出?是否需要更改i和57616为不同的值?

trap里的timestamp trap.s_经验分享_20

如果 RISC-V 是 big-endian:

57616不需要更改,因为它是作为一个整体的进制转换进行输出的

但i需要更改为726c6400

(6) 在下面的代码中,会打印出什么?(注意:答案不是特定值)为什么会发生这种情况?
printf("x=%d y=%d", 3);

trap里的timestamp trap.s_trap里的timestamp_21

先看正常的代码,printf("x=%d y=%d",3,3);,具体是将两个3分别放在a1和a2寄存器中,printf再去读寄存器里的值。但如果后面一个3缺省,但a2寄存器仍然可读取,所以y读的便是a2寄存器里的值,因为a2寄存器没有给定值,所以会打印一个不确定的值,当时a2寄存器是什么就打印什么。

三、问题解决:

【问题1】在Backtrace实验中,最开始对于提示中“指向**返回地址加上8的地址”理解有误,导致输出的保存地址不正确

【解决】画了一张简图(图太丑,就不放了)来理解,指针与指向具体内容的关系。文字说明就是,s0放的是fp的地址,该地址-8即是返回地址(打印内容)的地址,-16就是上一个fp的地址。

【问题2】完成实验进行make grade后,发现usertests失败,显示丢失了一些free page,应该是有些东西在结束后未被free掉。

trap里的timestamp trap.s_经验分享_22

【解决】经过漫长的排查,发现是save_trapframe与trapframe之间的拷贝造成的。最开始,是直接将两个进行复制来拷贝。后来才想起来这两个是指针,只拷贝指针的话,确实指针之前指向的内容没被释放掉。

trap里的timestamp trap.s_c语言_14

此处改之前是p->save_trapframe = p->trapframe

四、实验感想:

  1. 这个实验因为第一周事情比较多,导致拖到第二周已经忘了之前是咋理解的。alarm实验不亏是hard,虽然做完了回想一下也没有那么多复杂的代码,但写的时候就有种拆东墙补西墙的感觉。先写test0的时候很顺手,而且看着后面的提示也没有很多。结果后面的测试大概就是test1能过了test3不能过,test3能过了test1又出问题,最后以为成功了,usertests出问题。
  2. 这次实验的提示相比于之前的保姆式提示,略过了一些具体实现的介绍,比如添加一个新的.c文件到makefile里以及后面的一系列操作,还比如要保存和恢复寄存器并没有告诉你使用什么样的数据结构,这正是实验逐步深入的体现。
  3. 通过具体的函数跳转并返回这一机制的实验,我捡回了很多关于计算机组成的知识,也对这些知识有了更深刻的理解,比如程序计数器的执行原理、寄存器的使用等等。