实验四
原网址 Lab: traps
RISCV-V assembly
-
Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?
call.asm中地址0x24的指令将13放在了a2
-
Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.)
可以看到都被编译器优化了,可以在编译期计算除结果,没有显示的调用。
-
At what address is the function printf located?
0x640
-
What value is in the register ra just after the jalr to printf in main?
盲猜一个30,因为
jalr 1552(ra)
,ra+1552 = 0x640,1552 = 0x610,所以ra = 30 -
Run the following code. unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i); What is the output? Here's an ASCII table that maps bytes to characters. 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 change 57616 to a different value?
打印的是He110 World。如果RISC-V是小端系统,那么i理论上应该是0x726c6400,57616不变
-
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);
寄存器a2的值是多少就打印多少。
Backtrace
添加一个功能,打印函数栈。
函数栈长这样,所以只需要获取sp和fp,由sp拿到栈底即可。
-
riscv.h中加入内联汇编函数
static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x) ); return x; }
-
printf.c中定义函数backtrace
void backtrace(void) { printf("backtrace:\n"); struct proc* p = myproc(); uint64 fp = r_fp(); while (fp > PGROUNDUP(p->kstack)) { printf("%p\n", *(uint64*)(fp - 8)); fp = *(uint64*)(fp - 16); } }
-
sysproc.c中调用函数backtrace
uint64 sys_sleep(void) { //... while(....) { if(...) { } backtrace(); sleep(); } //... }
-
defs.h中声明backtrace函数的原型
void backtrace(void);
和GDB的backtrace(bt)打印出来的一样
Alarm
添加一个系统调用sigalarm(interval,handler),interval是时间间隔,handler是你想要运行的函数的
函数名,每隔interval,就运行一次handler。
test0
-
添加两个系统调用,这里只给出用户的接口。
/* user/user.h */ int sigalarm(int ticks, void (*handler)()); int sigreturn(void);
-
结构体proc中加入新成员
struct proc { //... void (*handle)(void); //函数指针 int ttr; //time to run handle }
-
定义sys_sigalarm和sys_sigreturn函数
/* kernel/sysproc.c */ uint64 sys_sigreturn(void) { return 0; } uint64 sys_sigalarm(void) { int time; uint64 fn_addr; if (argint(0,&time) < 0 || argaddr(1,&fn_addr) < 0) return -1; if (0 == time) return -1; struct proc* p = myproc(); p->handle = (void (*)())fn_addr; p->ttr = time; return 0; }
-
修改usertrap()函数,使每隔一段时间运行handler。test0只需要修改epc寄存器就行
/* kernel/trap.c */ void usertrap(void) { //... if (which_dev == 2) { static uint64 tick_cnt = 0; if (0 < p->ttr) { if (++tick_cnt == p->ttr)) { tick_cnt = 0; p->trapframe->epc = (uint64)p->handle; } } else tick_cnt = 0; yield(); } //... }
-
初始化proc中的ttr和handle
/* kernel/proc.c */ static struct proc* allocproc(void) { //... p->ttr = 0; p->handle = 0; return p; }
test1 test2
test0只实现每隔一段时间运行handler,但是可能无法正确返回中断的地方继续运行。所以test1、test2就是实现sys_sigreturn,使之能正确返回。
要想正确返回的前提就是正确恢复中断前寄存器的值,但是具体是哪些寄存器,实验提示it will be many
,这里偷了个懒,把所有寄存器的值都保存,没有一个个得寄存器去试。
-
proc结构体添加新成员
struct proc { //.. struct trapframe prev; //之前寄存器的状态 int alarm_lock; //在上一次handler返回之前,使之不能再次进入 }
初始化alarm_lock
/* kernel/proc.c */ static struct proc* allocproc(void) { //... p->alarm_lock = 0; return p; }
-
修改sys_sigreturn,恢复进入handler之前寄存器的值,并使alarm_lock为0,即又可以进入handler了
uint64 sys_sigreturn(void) { struct proc* p = myproc(); *(p->trapframe) = p->prev; p->alarm_lock = 0; return 0; }
-
修改usertrap(),进入handler之前,保存所有寄存器,使alarm_lock = 1,即在调用sigreturn之前不能再次进入handler
/* kernel/trap.c */ void usertrap(void) { //... if (which_dev == 2) { static uint64 tick_cnt = 0; if (0 < p->ttr) { if (0 == p->alarm_lock && ++tick_cnt == p->ttr)) { tick_cnt = 0; p->alarm_lock = 1; p->prev = *(p->trapframe); p->trapframe->epc = (uint64)p->handle; } } else tick_cnt = 0; yield(); } }
在qemu中运行usertests能通过,不知道为什么运行grade-lab-traps就不行。