0%

lab4-Traps

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 中添加 sigalarmsigreturn 系统调用的实现:

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.定义 sigalarmsigreturn 函数

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 中的 allocprocfreeproc 以处理新的 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();
}

-------------本文结束感谢您的阅读-------------