[MIT6.S081]Lab4: Traps
# [MIT6.S081]Lab4: Traps
Lab: Traps (mit.edu) (opens new window)
# 1、RISC-V assembly
这个 assignment 主要是用来熟悉一些简单的 RISCV 的汇编。
- Which registers contain arguments to functions? For example, which register holds 13 in main's call to
printf
?
一般用 a0 - a7
来存放参数,显然参数 13 放在 a2
寄存器中。
- Where is the call to function
f
in the assembly code for main? Where is the call tog
? (Hint: the compiler may inline functions.)
- 根据下面两行汇编,显然两个函数都被内联优化了
24: 4635 li a2,13
26: 45b1 li a1,12
- At what address is the function
printf
located?
- 由下面两行汇编可以看出,我们在修改完 pc 的值之后跳转到了
0x30 + 1536
的地方,1536 的十六进制是0x600
,所以我们最后会跳转到printf
所在的地方0x630
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
- What value is in the register
ra
just after thejalr
toprintf
inmain
?
- 由下图可以看出,我们会把
原 pc + 4
的值写入寄存器ra
,因此此时他的值是0x38
Run the following code.
unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i);
What is the output?
The output depends on that fact that the RISC-V is little-endian. If the RISC-V were instead big-endian what would you set
i
to in order to yield the same output? Would you need to change57616
to a different value?
- 输出是
He110 World
- 整体按字节逆转一下顺序就行,
0x726c6400
- 不用,大小端是存储的形式,与数值无关,无论
57616
如何被存储,其十六进制都是0xe110
In the following code, what is going to be printed after
'y='
? (note: the answer is not a specific value.) Why does this happen?printf("x=%d y=%d", 3);
- 从汇编我们可以很容易知道参数传递是通过寄存器实现的,在这里的两个参数分别由
a1
和a2
传递。由于我们没有给第二个参数,因此这里会由当前a2
中的值代替,这取决于这行代码之前的部分了。
# 2、Backtrace
在这个 assignment 中,我们调用系统调用 sys_sleep
后,要递归打印被调用的函数的 return address。
这张图展现了栈的地址空间,可以看出我们要打印的 return address
在 fp -8
的位置,而前一个栈的 fp,存在了 fp - 16
的位置,了解了这些后,这个 assigment 就非常好实现了,我们直接看代码:
void backtrace(void) {
uint64 fp = r_fp(); // 系统提供的,获取了当前执行函数的 fp
uint64 top = PGROUNDUP(fp); // 系统提供的
uint64 bottom = PGROUNDDOWN(fp);
printf("backtrace:\n");
while (fp > bottom && fp < top) {
uint64 ra = *(uint64 *)(fp - 8); // 转化成指针再解引用
fp = *(uint64 *)(fp - 16);
printf("%p\n", ra);
}
}
# 3.Alarm
这个 assignment 比较复杂,简单来说就是要我们自己实现两个系统调用,能够通过倒计时来陷入 timer interrupt,来执行相关函数。
我们先要添加两个系统调用 sys_sigalarm
、sys_sigreturn
,添加流程和之前一致,就不赘述了。
为了实现要求,我们先要在 struct proc
中添加一些新的变量
struct proc {
//..................
int interval; // 计时器间隔
uint64 handler; // 倒计时结束后执行的函数地址
int passed_ticks; // 当前过了多久时间
struct trapframe *saved_trapframe; // 执行 handler 之前要保存 trapframe
int in_trap; // 当前是否在 trap 中,如果在要避免再次遇到 trap
};
对应的要在 proc.c
中进行初始化,以及 free
static struct proc* allocproc(void) {
// ...................
// Allocate a saved_trapframe page.
if((p->saved_trapframe = (struct trapframe *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
p->interval = 0;
p->passed_ticks = 0;
p->in_trap = 0;
return p;
}
static void freeproc(struct proc *p) {
// ......
if(p->saved_trapframe)
kfree((void*)p->saved_trapframe);
p->handler = 0;
p->in_trap = 0;
p->interval = 0;
p->passed_ticks = 0;
// ......
}
接着我们在 trap.c
中实现倒计时和切换函数。
// give up the CPU if this is a timer interrupt.
if(which_dev == 2) {
if (p->in_trap == 0) { // 保证当前不在 trap 里
p->passed_ticks++; // 经过时间 + 1
if (p->passed_ticks == p->interval) { // 倒计时结束
p->passed_ticks = 0; // 重置时间
p->in_trap = 1; // 进入trap
*p->saved_trapframe = *p->trapframe; // 保存当前的 trapframe
p->trapframe->epc = p->handler; // 切换执行函数
}
}
yield();
}
具体的看注释就可以了,需要注意的是我们通过修改 p->trapframe->epc = p->handler
来切换到相关函数并执行
另外我们还需要实现一下两个系统调用
uint64 sys_sigalarm(void) { // 获取相关参数并赋值
int ticks;
uint64 handler;
if(argint(0, &ticks) < 0)
return -1;
if(argaddr(1, &handler) < 0)
return -1;
myproc()->interval = ticks;
myproc()->handler = handler;
return 0;
}
uint64 sys_sigreturn(void) {
myproc()->in_trap = 0; // 又恢复了不在 trap 的转台
*myproc()->trapframe = *myproc()->saved_trapframe; // 恢复现场,将之前保存下来的 trapframe 恢复
return 0;
}
# 实验结果
# 存在问题
不知道是我的问题还是有 bug,我特地跑了 origin/traps 的原代码最后还是没有通过 usertests 🥲,后面的 labs 就