MIT 6.828 - Lab - Traps
课程主页:https://pdos.csail.mit.edu/6.828/2023/index.html
RISC-V assembly
Q: Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
观察 call.asm 中 main 的汇编结果,可以看出 13 是由 a2 进行传递的。
printf("%d %d\n", f(8)+1, 13);
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7d850513 add a0,a0,2008 # 800 <malloc+0x108>
30: 00000097 auipc ra,0x0
34: 610080e7 jalr 1552(ra) # 640 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
RISC-V 中,a0-a7 用于传递整型,fa0-fa7 用于传递浮点型。这里 printf 的三个参数使用 a0-a2 来传递。
Q: 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.)
在 main 中没有对 f 函数的调用,因为 f 和 g 都是纯函数,这里参数也是字面量,所以 f(8) 的结果已经被计算出来了,其值为 12。
Q: At what address is the function printf located?
printf 的地址是 ra + 1552,ra 的值由 auipc ra,0x0 计算,ra 的值为 0x64,因此 printf 的地址为 0x30 + 1552 = 0x640。这里采用的是 PC 相对寻址,在运行时 printf 的地址是多少,这就说不定了。
Q: What value is in the register ra just after the jalr to printf in main?
jalr 1552(ra) # 640 <printf>
JALR (jump and link register) 会跳转到 printf 处,并将 PC + 4 的值写入到 ra 中,因此 ra 的值为 0x38。
Q: Run the following code.
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
What is the output?
57616 转为 16 进制为 0xe110。
这里把 0x00646c72 当做字符串来打印,因此 i 的四个字节需要被解释为字符,因为是小端存储,因此会被解释为 0x72 0x6c 0x64 0x00,对应字符串 “rld\0”,因此上面的 printf 会打印:
He110 World
Q: 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);
这里需要三个参数,但是只传了两个,printf 在获取第二个格式化参数时,会去 a2 中读取。因此具体打印什么,取决于此时 a2 中存储的是什么值。
Backtrace
这个问题是实现一个 backtrace 函数,backtrace 就是通过回溯调用栈,得到当前正在执行的函数的调用关系。
risc-v 的函数调用栈具有以下特征,每一个栈帧都在开头位置保存了函数返回地址和前一个栈帧的地址,可以通过 fp 拿到当前的栈帧,然后读取出 ra 和前一个栈帧的指针 fp,如此就可以向前回溯,直到到达栈底。
+============+ <------+
| ra | |
+ - - - - - -+ |
| revp fp | |
+ - - - - - -+ |
| ... | |
| ... | |
+============+ <------|---+
| ra | | |
+ - - - - - -+ | |
| prev fp |--------+ |
+ - - - - - -+ |
| ... | |
| ... | |
+============+ <-- fp |
| ra | |
+ - - - - - -+ |
| prev fp |------------+
+ - - - - - -+
| ... |
| ... |
+============+
ra: return address
fp: frame pointer
prev fp: previous fp
实现方式如下:
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
void backtrace() {
uint64 fp = r_fp();
uint64 base = PGROUNDDOWN(fp);
while (fp > base) {
uint64 ra = *(uint64*)(fp - 8);
fp = *(uint64*)(fp - 16);
printf("%p\n", (void*)ra);
}
}
r_fp 用于读取当前 fp,fp - 8 就是 ra 的地址,fp - 16 就是前一帧的 fp 的地址。
因为调用栈的大小只有一页,因此当 fp 小于当前页的起始位置时,可以退出回溯。
Alarm
实现一个定时器,用户可以使用这个定时器执行周期性任务。
void handler() {
printf("alarm\n");
sigreturn();
}
int main() {
sigalarm(10, handler)
}
sigalarm 用于设置周期性任务,当时间到了后,之前设置的回调就会被调用,在回调中 sigreturn 用于返回原来的执行流。
思路
将当前设置的 alarm 回调保存在当前进程的结构体中。在时钟中断中,判断是否到了执行的时机,如果到了,则切换到 alarm 的回调处执行。当执行完毕后,在 sigreturn 中再切换回来。
有两个关键点:
- 在
usertrap中处理时钟中断的地方,需要判断当前进程是否注册了定时器,以及定时器是否该触发。如果该触发,则跳到定时器回调中执行。 - 在
sigreturn中,跳转到触发定时器回调之前的地方执行。
问题:如何跳转到定时器回调,如何跳转回去?
在 trapframe 中保存了用户进程上下文,每次触发中断后,用户上下文都会保存在 proc->trapframe 里面。trapframe->epc 用来记录用户进程中断前该执行的下一条指令。
当希望跳转到定时器回调函数时,可以拷贝 proc->trapframe,并修改 trapframe->epc 为回调的地址。在 sigreturn 中可以恢复原来的 trapframe。
实现
在 struct proc 中新增字段保存在定时器信息:
struct proc {
// ...
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
+
+ uint64 alarm_handler; // 定时器回调
+ uint64 alarm_interval_ticks; // 回调周期
+ uint64 alarm_passed_ticks; // 已经经过的时钟数
+ struct trapframe alarm_saved_trapframe; // 用于保存原 trapframe
};
sigalarm 只需要保存用户传来的信息到 proc 结构体中:
uint64 sys_sigalarm(void) {
int ticks;
uint64 handler;
argint(0, &ticks);
argaddr(1, &handler);
struct proc * p = myproc();
p->alarm_interval_ticks = ticks;
p->alarm_passed_ticks = 0;
p->alarm_handler = handler;
return 0;
}
在 usertrap 中处理时钟中断的地方处理定时器:
void
usertrap(void) {
// ...
// give up the CPU if this is a timer interrupt.
if(which_dev == 2) {
if (p->alarm_interval_ticks) {
p->alarm_passed_ticks++;
if (p->alarm_passed_ticks == p->alarm_interval_ticks) {
memmove(&p->alarm_saved_trapframe, p->trapframe, sizeof(*p->trapframe));
p->trapframe->epc = p->alarm_handler;
}
}
yield();
}
// ...
}
如果 p->alarm_interval_ticks 不为 0,则说明该进程注册了定时器回调,可以累加计数器,当计数器达到设定定时周期后,可以将当前的 trapframe 保存下来,然后修改 trapframe->epc,指向定时器回调函数,当退出中断服务函数后,就会跳转到 trapframe->epc 处执行,即开始执行定时器的回调。
在 sys_sigreturn 中,将定时器的计数器清零,等待下一轮超时,并将在 usertrap 中保存下来的 trapframe 恢复回来,这样当 sigreturn 返回后,就会跳转到触发定时器之前的为止继续执行。
uint64 sys_sigreturn(void) {
struct proc * p = myproc();
p->alarm_passed_ticks = 0; // 将计数器清零
// 恢复原 trapframe
memmove(p->trapframe, &p->alarm_saved_trapframe, sizeof(*p->trapframe));
return p->trapframe->a0;
}
这里 sys_sigreturn 使用 p->trapframe->a0 作为返回值,起初我返回的是 0,但是通过不了下面这个测试:
void
test3()
{
uint64 a0;
sigalarm(1, dummy_handler);
printf("test3 start\n");
asm volatile("lui a5, 0");
asm volatile("addi a0, a5, 0xac" : : : "a0");
for(int i = 0; i < 500000000; i++)
;
asm volatile("mv %0, a0" : "=r" (a0) );
if(a0 != 0xac)
printf("test3 failed: register a0 changed\n");
else
printf("test3 passed\n");
}
这是说 a0 寄存器的值被定时器回调修改了,在定时器触发前后,a0 寄存器的值应该保持不变。
因为定时器的回调函数是在用户态调用的,最后调用 sigreturn 回到内核态, sigreturn 执行完以后,会再次回到用户态,系统调用执行后的结果是会通过 a0 传递给用户的,见下面 syscall 的实现:
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
但这里我们不希望定时器回调函数改变 a0 寄存器,但 sigreturn 的返回值确实会被赋值给 trapframe->a0,而此时的 trapframe 恰记录的是触发定时器回调之前的上下文。
因此,可以在 sigreturn 中返回 trapframe->a0 的值。
下面是上面介绍的几个重点函数的执行流程:
完整变更
diff --git a/Makefile b/Makefile
index 473a471..f6c327b 100644
--- a/Makefile
+++ b/Makefile
@@ -188,6 +188,7 @@ UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
+ $U/_alarmtest\
diff --git a/kernel/defs.h b/kernel/defs.h
index a3c962b..3f91f3b 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -80,6 +80,7 @@ int pipewrite(struct pipe*, uint64, int);
void printf(char*, ...);
void panic(char*) __attribute__((noreturn));
void printfinit(void);
+void backtrace(void);
// proc.c
int cpuid(void);
diff --git a/kernel/printf.c b/kernel/printf.c
index 1a50203..81a40ca 100644
--- a/kernel/printf.c
+++ b/kernel/printf.c
@@ -123,6 +123,7 @@ panic(char *s)
printf(s);
printf("\n");
panicked = 1; // freeze uart output from other CPUs
+ backtrace();
for(;;)
;
}
@@ -133,3 +134,21 @@ printfinit(void)
initlock(&pr.lock, "pr");
pr.locking = 1;
}
+
+static inline uint64
+r_fp()
+{
+ uint64 x;
+ asm volatile("mv %0, s0" : "=r" (x) );
+ return x;
+}
+
+void backtrace() {
+ uint64 fp = r_fp();
+ uint64 base = PGROUNDDOWN(fp);
+ while (fp > base) {
+ uint64 ra = *(uint64*)(fp - 8);
+ fp = *(uint64*)(fp - 16);
+ printf("%p\n", (void*)ra);
+ }
+}
\ No newline at end of file
diff --git a/kernel/proc.c b/kernel/proc.c
index 58a8a0b..00d1f32 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -124,6 +124,9 @@ allocproc(void)
found:
p->pid = allocpid();
p->state = USED;
+ p->alarm_handler = 0;
+ p->alarm_passed_ticks = 0;
+ p->alarm_interval_ticks = 0;
// Allocate a trapframe page.
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
diff --git a/kernel/proc.h b/kernel/proc.h
index d021857..e9b0e0c 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -104,4 +104,9 @@ struct proc {
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
+
+ uint64 alarm_handler;
+ uint64 alarm_interval_ticks;
+ uint64 alarm_passed_ticks;
+ struct trapframe alarm_saved_trapframe;
};
diff --git a/kernel/syscall.c b/kernel/syscall.c
index ed65409..91b69ac 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -101,6 +101,8 @@ extern uint64 sys_unlink(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_close(void);
+extern uint64 sys_sigalarm(void);
+extern uint64 sys_sigreturn(void);
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
@@ -126,6 +128,8 @@ static uint64 (*syscalls[])(void) = {
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
+[SYS_sigalarm] sys_sigalarm,
+[SYS_sigreturn] sys_sigreturn,
};
void
diff --git a/kernel/syscall.h b/kernel/syscall.h
index bc5f356..67ca3a4 100644
--- a/kernel/syscall.h
+++ b/kernel/syscall.h
@@ -20,3 +20,5 @@
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
+#define SYS_sigalarm 22
+#define SYS_sigreturn 23
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index 3b4d5bd..55ed65d 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -67,6 +67,9 @@ sys_sleep(void)
sleep(&ticks, &tickslock);
}
release(&tickslock);
+
+ backtrace();
+
return 0;
}
@@ -91,3 +94,23 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}
+
+uint64 sys_sigalarm(void) {
+ int ticks;
+ uint64 handler;
+ argint(0, &ticks);
+ argaddr(1, &handler);
+
+ struct proc * p = myproc();
+ p->alarm_interval_ticks = ticks;
+ p->alarm_passed_ticks = 0;
+ p->alarm_handler = handler;
+ return 0;
+}
+
+uint64 sys_sigreturn(void) {
+ struct proc * p = myproc();
+ p->alarm_passed_ticks = 0;
+ memmove(p->trapframe, &p->alarm_saved_trapframe, sizeof(*p->trapframe));
+ return p->trapframe->a0;
+}
\ No newline at end of file
diff --git a/kernel/trap.c b/kernel/trap.c
index 512c850..60f1180 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -77,8 +77,16 @@ usertrap(void)
exit(-1);
// give up the CPU if this is a timer interrupt.
- if(which_dev == 2)
+ if(which_dev == 2) {
+ if (p->alarm_interval_ticks) {
+ p->alarm_passed_ticks++;
+ if (p->alarm_passed_ticks == p->alarm_interval_ticks) {
+ memmove(&p->alarm_saved_trapframe, p->trapframe, sizeof(*p->trapframe));
+ p->trapframe->epc = p->alarm_handler;
+ }
+ }
yield();
+ }
usertrapret();
}
diff --git a/user/user.h b/user/user.h
index 4d398d5..9426153 100644
--- a/user/user.h
+++ b/user/user.h
@@ -22,6 +22,8 @@ int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
+int sigalarm(int ticks, void (*handler)());
+int sigreturn(void);
// ulib.c
int stat(const char*, struct stat*);
diff --git a/user/usys.pl b/user/usys.pl
index 01e426e..fa548b0 100755
--- a/user/usys.pl
+++ b/user/usys.pl
@@ -36,3 +36,5 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
+entry("sigalarm");
+entry("sigreturn");