Lab 4:Traps
课程地址:https://pdos.csail.mit.edu/6.828/2020/schedule.html Lab 地址:https://pdos.csail.mit.edu/6.828/2020/labs/traps.html 代码仓库:https://github.com/ajackchan/my-xv6-riscv/tree/traps
RISC-V assembly
阅读 call.asm,以及 RISC-V 指令集教程,回答问题
1 2 3 Q: 哪些寄存器存储了函数调用的参数?举个例子,main 调用 printf 的时候,13 被存在哪个寄存器? A: 参数存储在 `a0-a7` 寄存器中。调用 `printf` 时,`13` 被存储在 `a2` 中。
a0 寄存器保存的是格式化字符串的地址(即 printf
的第一个参数)。a1 寄存器保存的是 12
,即第二个参数。a2 寄存器保存的是 13
,即第三个参数。
1 2 3 Q: main 中调用 f 和 g 的汇编代码在哪? A: 没有显式调用。编译器将 `g(x)` 和 `f(x)` 都内联到了 `main()` 中。
编译器优化时将 g(x)
内联到 f(x)
中,进一步又将 f(x)
内联到 main()
中。
1 2 3 Q: printf 函数所在的地址是? A: `printf` 函数的地址是 `0x0000000000000628`。在 `main` 中使用的是 `pc` 相对寻址来计算这个地址。
1 2 3 Q: 在 main 中 jalr 跳转到 printf 之后,ra 的值是什么? A:0x0000000000000038
jalr
指令执行后,ra
(返回地址寄存器)会保存跳转指令的下一条指令的地址。在本例中,jalr
跳转到 printf
后,ra
寄存器的值是 0x0000000000000038
,即 jalr
指令之后的下一条指令的地址。
1 2 3 4 5 6 7 8 Q: 运行下面的代码 unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i); 输出是什么?如果 RISC-V 是大端序的,要实现同样的效果,需要将 i 设置为什么?需要将 57616 修改为别的值吗? A: 输出 `"He110 World"`。大端序时,`i` 应设置为 `0x726c6400`。`57616` 无需修改。
57616
的十六进制表示为 0xE110
,输出时的 %x
格式符将其输出为 e110
。而 &i
指向的值是 0x00646c72
,其字节序按小端格式解释为 "rld"
,因此输出结果是 "He110 World"
。
1 2 3 4 Q: 以下代码 'y=' 之后会打印什么?为什么? printf("x=%d y=%d", 3); A: 输出不确定,因为 `printf` 预期两个参数,但只传递了一个,导致读取了未初始化的寄存器值。
Backtrace
在 kernel/printf.c
中实现一个 backtrace()
函数,用于打印函数调用栈,并通过帧指针遍历栈帧,输出每个栈帧中的返回地址,用于调试.
1. 在 defs.h
中添加 backtrace()
的声明 1 2 3 4 void printf(char*, ...); void panic(char*) __attribute__((noreturn)); void printfinit(void); void backtrace(void); // 添加 backtrace 声明
2. 在 riscv.h
中添加获取当前帧指针的函数 r_fp()
1 2 3 4 5 static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x)); // 获取帧指针(frame pointer) return x; }
3. 实现 backtrace()
函数 1 2 3 4 5 6 7 8 void backtrace() { uint64 fp = r_fp(); // 获取当前帧指针 while (fp != PGROUNDUP(fp)) { // 判断是否到达栈底 uint64 ra = *(uint64*)(fp - 8); // 获取返回地址(fp-8) printf("%p\n", ra); // 输出返回地址 fp = *(uint64*)(fp - 16); // 获取上一层的帧指针(fp-16) } }
4. 在 sys_sleep()
中调用 backtrace()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 uint64 sys_sleep(void) { int n; uint ticks0; backtrace(); // 在 sys_sleep 开头调用 backtrace 输出栈回溯信息 if (argint(0, &n) < 0) return -1; acquire(&tickslock); ticks0 = ticks; while (ticks - ticks0 < n) { if (killed(myproc())) { release(&tickslock); return -1; } sleep(&ticks, &tickslock); } release(&tickslock); return 0; }
Alarm
添加一个功能,使系统能定期通知进程它所使用的CPU时间,通过实现一个新的系统调用sigalarm(interval, handler)
,允许进程设置周期性的警报来执行特定的函数。
1.修改 user/usys.pl
1 2 3 ... entry("sigalarm"); entry("sigreturn");
2.修改 user/user.h
1 2 3 ... int sigalarm(int ticks, void (*handler)()); int sigreturn(void);
3.修改 kernel/syscall.h
1 2 3 ... #define SYS_sigalarm 22 #define SYS_sigreturn 23
4.修改 kernel/syscall.c
1 2 3 4 5 6 7 8 9 ... extern uint64 sys_sigalarm(void); extern uint64 sys_sigreturn(void); static uint64 (*syscalls[])(void) = { ... [SYS_sigalarm] sys_sigalarm, [SYS_sigreturn] sys_sigreturn, };
5.修改 kernel/defs.h
1 2 3 4 // trap.c ... int sigalarm(int n, void(*fn)(void)); int sigreturn();
6.修改 proc
结构 更新 proc.h
中的 proc
结构,以包括与警报信号相关的字段:
1 2 3 4 5 6 7 8 9 // 第1步:与警报相关的字段 struct proc { // ...... int alarm_interval; // Alarm interval (0 for disabled) void(*alarm_handler)(); // Alarm handler int alarm_ticks; // How many ticks left before next alarm goes off struct trapframe *alarm_trapframe; // A copy of trapframe right before running alarm_handler int alarm_goingoff; // Is an alarm currently going off and hasn't not yet returned? (prevent re-entrance of alarm_handler) };
7.实现系统调用 在 sysproc.c
中添加 sigalarm
和 sigreturn
系统调用的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 第2步:系统调用实现 uint64 sys_sigalarm(void) { int interval; uint64 handler; if(argint(0, &interval) < 0 || argaddr(1, &handler) < 0) return -1; return sigalarm(interval, (void (*)())handler); } uint64 sys_sigreturn(void) { return sigreturn(); }
8.定义 sigalarm
和 sigreturn
函数 在 trap.c
或其他适当的文件中定义这些系统调用的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 第3步:为 sigalarm 和 sigreturn 定义函数 int sigalarm(int ticks, void (*handler)()) { struct proc *p = myproc(); p->alarm_interval = ticks; p->alarm_handler = handler; p->alarm_ticks = ticks; p->alarm_goingoff = 0; return 0; } int sigreturn() { struct proc *p = myproc(); *p->trapframe = *p->alarm_trapframe; p->alarm_goingoff = 0; return 0; }
9.分配和释放 alarm_trapframe
更新 proc.c
中的 allocproc
和 freeproc
以处理新的 alarm_trapframe
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // proc.c static struct proc* allocproc(void) { // ...... found: p->pid = allocpid(); // Allocate a trapframe page. if((p->trapframe = (struct trapframe *)kalloc()) == 0){ release(&p->lock); return 0; } // Allocate a trapframe page for alarm_trapframe. if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){ release(&p->lock); return 0; } p->alarm_interval = 0; p->alarm_handler = 0; p->alarm_ticks = 0; p->alarm_goingoff = 0; // ...... return p; } static void freeproc(struct proc *p) { // ...... if(p->alarm_trapframe) kfree((void*)p->alarm_trapframe); p->alarm_trapframe = 0; // ...... p->alarm_interval = 0; p->alarm_handler = 0; p->alarm_ticks = 0; p->alarm_goingoff = 0; p->state = UNUSED; }
10.在 usertrap()
函数中,实现时钟机制具体代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void usertrap(void) { int which_dev = 0; // ...... if(p->killed) exit(-1); // give up the CPU if this is a timer interrupt. // if(which_dev == 2) { // yield(); // } // give up the CPU if this is a timer interrupt. if(which_dev == 2) { if(p->alarm_interval != 0) { // 如果设定了时钟事件 if(--p->alarm_ticks <= 0) { // 时钟倒计时 -1 tick,如果已经到达或超过设定的 tick 数 if(!p->alarm_goingoff) { // 确保没有时钟正在运行 p->alarm_ticks = p->alarm_interval; // jump to execute alarm_handler *p->alarm_trapframe = *p->trapframe; // backup trapframe p->trapframe->epc = (uint64)p->alarm_handler; p->alarm_goingoff = 1; } // 如果一个时钟到期的时候已经有一个时钟处理函数正在运行,则会推迟到原处理函数运行完成后的下一个 tick 才触发这次时钟 } } yield(); } usertrapret(); }