这个实验探索使用 traps 来实现 system call。首先有一个热身训练,然后将要实现一个用户层级的 trap 处理示例
实验前置操作
git fetch
git checkout traps
make cleanRISC-V 汇编指令速记
数据传输
ld → load double word
lw → load word
sd → store double
sw → store word
算术运算
add
sub
addi → add immediate
li → load immediate
mul → multiply
div → divide
逻辑运算
and/or/xor
andi/ori → and/or imm
slt → set if less than
slti → set if less than imm
程序控制
beq → branch if equal
bne → branch if not equal
blt/bge → branch if less/greater-equal
jal → jump and link 跳转并链接(普通函数调用)
jalr → jump and link register 寄存器跳转并链接(间接/动态调用)
ret → return 返回(伪指令,jalr zero, 0(ra) 的简写)
32位立即数加载
lui → load upper imm 加载高位立即数
auipc → add upper imm to PC 程序计数器加载高位立即数
RISC-V assembly 汇编代码
easy
理解一些 RISC-V 汇编指令很重要,有一个
user/call.c文件在 xv6 仓库中.make fs.img可以编译并生成一个可阅读的程序user/call.asm。 阅读g,f,main函数,回答下面的几个问题
- 哪几个寄存器存储函数的参数?例如,哪个寄存器存了
printf的参数 13 - 函数
f的汇编调用在main的哪里?函数g?(提示:编译器可能会把函数内联) - 函数
printf的地址在哪 main函数在jalr到printf之后ra的值是什么- 运行下面的代码:
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);输出是什么
输出依赖于 RISC-V 的小端存储,如果是大端存储,会发生什么
在下面的代码中,y= 后面会打印什么?(答案不是一个确定值)为什么会这样?
printf("x=%d y=%d", 3);
解答 1
call.c 的原始代码
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int g(int x) {
return x+3;
}
int f(int x) {
return g(x);
}
void main(void) {
printf("%d %d\n", f(8)+1, 13);
exit(0);
}main 函数的汇编代码
void main(void) {
1c: 1141 addi sp,sp,-16 # 分配 16 字节空间
1e: e406 sd ra,8(sp) # 保存 main 的返回地址
20: e022 sd s0,0(sp)
22: 0800 addi s0,sp,16
printf("%d %d\n", f(8)+1, 13); # 编译器算出了 f(8) + 1 是 12
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7b050513 addi a0,a0,1968 # 7d8 <malloc+0xea> # linker 计算的字符串存储位置
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 27e080e7 jalr 638(ra) # 2b8 <exit>a0,a1,a2... 存储函数参数,13 存在了 a2
解答 2
f 和 g 的汇编调用都被内联了
解答 3
0x630
解答 4
0x38 也就是 li a0, 0 这条指令的地址
解答 5
57616 = 0xE110
0x00646c72 小端存储从低位到高位依次为 72-6c-64-00 = rld
大端存储则需要改为 0x726c6400
解答 6
原本需要传两个参数却只传了一个,y= 取决于 a2 保存的值
Backtrace 回溯
moderate
需要一个 debug 工作 backtrace:在错误发生点之上的栈上的一个函数调用列表
在
kernel/printf.c实现一个backtrace()函数。在sys_sleep函数中插入一个backtrace(),运行bttest,这会调用sys_sleep。输出应该是: backtrace: 0x0000000080002cda 0x0000000080002bb6 0x0000000080002898 在bttest结束之后退出 qemu。运行addr2line -e kernel/kernel或者riscv64-unknown-elf-addr2line -e kernel/kernel然后复制粘贴刚才得到的地址 addr2line -e kernel/kernel 0x0000000080002de2 0x0000000080002f4a 0x0000000080002bfc Ctrl-D 应该会发现下面类似的情况 kernel/sysproc.c:74 kernel/syscall.c:224 kernel/trap.c:85
编译器在每个栈帧放了一个帧指针存储了调用者的帧信息。backtrace 将使用这些帧指针来向上打印栈帧的返回地址
提示:
- 在
kernel/defs.h增加backtrace的原型函数这样就可以在sys_sleep里调用backtrace GCC编译器把当前正在执行的函数的帧指针存在寄存器s0。将下面的函数添加到kernel/riscv.h
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}调用这个函数来读取当前函数栈帧中的 s0 中的值(帧指针)

- xv6 在内核为每个栈分配一页”页对齐“地址。可以通过
PGROUNDUP(rp)和PGROUNDDOWN(rp)来计算栈的底部和顶部地址(这可以帮助backtrace结束无限循环) 当backtrace能够运行了, 在kernel/printf.c的panic中调用,这样就能在内核 panic 的时候看到调用栈信息
实验过程
核心代码
void
backtrace(void)
{
uint64 frame_pointer = r_fp();
printf("backtrace:\n");
while (PGROUNDDOWN(frame_pointer) != PGROUNDUP(frame_pointer)) {
uint64 ra = *(uint64*)(frame_pointer - 8);
printf("%p\n", ra);
frame_pointer = *(uint64*)(frame_pointer - 16);
}
}Alarm
hard
在这个练习中将要为 xv6 加入一个功能,周期性警告表示一个线程在使用 CPU 时间。对于一些边界计算线程可能适用于它们想限制吃掉时间片的情况,或者是想周期性做一些动作的情况。更一般的说,你将要实现一个用户级别的中断/错误处理器原型。可以使用一些类似 缺页中断 的应用。如果解决方法正确的话会通过
alarmtest和usertests。
你应该增加一个新的 sigalarm(intervel, handler) 系统调用。如果一个应用调用 sigalarm(n, fn) ,然后每 n 个 ticks内核会让应用函数 fn 调用。
实验过程
该实验涉及了 时钟中断 机制
核心代码
void
usertrap(void)
{
// ...
// Handle timer interrupt
if(which_dev == 2) {
// Check if periodic alarm should fire
if (p->alarm_interval > 0 && !p->alarm_in_progress) {
p->alarm_ticks_remaining--;
if (p->alarm_ticks_remaining <= 0) {
// Reset counter for next alarm
p->alarm_ticks_remaining = p->alarm_interval;
// Save current user context before calling handler
*p->alarm_saved_state = *p->trapframe;
// Redirect execution to alarm handler
p->trapframe->epc = (uint64)p->alarm_handler;
// Mark that alarm handler is running
p->alarm_in_progress = 1;
}
}
yield();
}
usertrapret();
}