MIT 6.S081 | 0x04 Traps
Before you start coding, read Chapter 4 of the xv6 book, and related source files:
kernel/trampoline.S
: the assembly involved in changing from user space to kernel space and backkernel/trap.c
: code handling all interrupts
RISC-V assembly
这道题让我们通过阅读一个文件 user/call.c
的 asm
代码来学习 RISC-V 汇编的一些基础知识
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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
使用 printf
打印两个数字,并在其中通过 f
调用 g
。下面就来看看实验提了哪些问题吧
Which registers contain arguments to functions? For example, which register holds 13 in main’s call to
printf
?
直接看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
000000000000001c <main>:
void main(void) {
1c: 1141 addi sp,sp,-16
1e: e406 sd ra,8(sp)
20: e022 sd s0,0(sp)
22: 0800 addi s0,sp,16
printf("%d %d\n", f(8)+1, 13);
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7b850513 addi a0,a0,1976 # 7e0 <malloc+0xe6>
30: 00000097 auipc ra,0x0
34: 612080e7 jalr 1554(ra) # 642 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 28e080e7 jalr 654(ra) # 2c8 <exit>
在 24 和 26 这两行分别向 a2
和 a1
两个寄存器存了 13、12 这两个值。所以可以回答这个问题,函数的寄存器存在 a1
~a7
这些寄存器中(查查 RISC-V 寄存器也能知道)
Where is the call to function
f
in the assembly code formain
? Where is the call tog
? (Hint: the compiler may inline functions.)
还是上面那段代码,a1
和 a2
存了两个立即数,这个 12 其实就是 f(8)+1
的结果。所以说编译器在编译的过程中直接将 f
和 g
的调用进行内联,直接用它们的结果进行编译了
At what address is the function
printf
located?
1
0000000000000642 <printf>:
所以 printf
这个函数的地址位于 0x642。这个问题一出来,就会很自然地想到下面的问题:
What value is in the register
ra
just after thejalr
toprintf
inmain
?
在 main
中有一句 jalr 1554(ra)
,这应该是在 ra
的基础上加 1554,而且这是一个十进制立即数,那么 0x642 减去这个立即数得到的结果是 48,转换为十六进制就是 0x30,这正好是 jalr
跳转的上一句地址
我还查了一下
auipc ra, 0x0
的含义:这是一个 RISC-V 指令,用于将一个 20 位的立即数(偏移量)加到当前指令的地址的高 20 位上,结果存储在目标寄存器
ra
中具体来说,
auipc
指令将当前指令的地址的高 20 位(即PC[31:12]
)与一个 20 位的立即数相加,得到一个 32 位的地址,然后将这个地址存储在目标寄存器ra
中。这个指令通常用于计算全局地址,例如用于加载全局变量的地址例如,如果当前指令的地址是 0x1000,那么执行
auipc ra, 0x0
指令后,目标寄存器ra
将被设置为 0x1000
Run the following code.
1 2 unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i);What is the output?
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?
这个问题考察我们对大端小端的理解,下面先来进行一个分析。首先是 "H%x"
,这个 %x
打印的是 57616 的 16 进制值,跟大小端没关系,所以打印的第一个值必定是 HE110
。然后是这个 "Wo%s"
,将一个无符号整数转换为字符串,因为 RISC-V 是小端存储,所以低地址在前,高地址在后,所以打印的字符应该分别为 72
、6c
、64
以及 00
(字符串结束),ASCII 转码后打印得到的是 rld
。现在可以对这个问题进行一个解答:
- 因为 RISC-V 是小端存储,所以全部的输出为:
HE110 World
- 如果是大端存储,因为
57616
直接打印值,所以不需要改变;而i
需要改变,应改为0x726c6400
In the following code, what is going to be printed after
y=
? (note: the answer is not a specific value.) Why does this happen?
1 printf("x=%d y=%d", 3);
y=
后的数字不确定,其数值取决于调用 printf
时第二个寄存器 a2
上的值。发生这种情况的原因是 printf
内有两个变量需要输出,但在后面的列表中只给了一个值,第二个值未给定,导致了输出结果的不确定
Backtrace
Implement a
backtrace()
function inkernel/printf.c
. Insert a call to this function insys_sleep
, and then runbttest
, which callssys_sleep
. Your output should be as follows:
1 2 3 4 backtrace: 0x0000000080002cda 0x0000000080002bb6 0x0000000080002898After
bttest
exit qemu. In your terminal: the addresses may be slightly different but if you runaddr2line -e kernel/kernel
(orriscv64-unknown-elf-addr2line -e kernel/kernel
) and cut-and-paste the above addresses as follows:
1 2 3 4 5 $ addr2line -e kernel/kernel 0x0000000080002de2 0x0000000080002f4a 0x0000000080002bfc Ctrl-DYou should see something like this:
kernel/sysproc.c:74 kernel/syscall.c:224 kernel/trap.c:85
分析
实现一个 backtrace()
方便调试。GCC 编译器将当前调用函数的 frame pointer 存到寄存器 s0
中,下图是函数栈的结构,返回地址存到 fp - 8
的位置,fp - 16
是上一个 fp
指针的位置
代码
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
diff --git a/kernel/defs.h b/kernel/defs.h
index a3c962b..4b82fc2 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..1fffaa4 100644
--- a/kernel/printf.c
+++ b/kernel/printf.c
@@ -133,3 +133,19 @@ printfinit(void)
initlock(&pr.lock, "pr");
pr.locking = 1;
}
+
+void
+backtrace(void)
+{
+ uint64 fp, up, ra;
+
+ fp = r_fp();
+ up = PGROUNDUP(fp);
+
+ printf("backtrace:\n");
+ while (fp != up) {
+ ra = *(uint64*)(fp - 8);
+ printf("%p\n", ra);
+ fp = *(uint64*)(fp - 16);
+ }
+}
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 20a01db..85a08cc 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -319,6 +319,14 @@ r_ra()
return x;
}
+static inline uint64
+r_fp()
+{
+ uint64 x;
+ asm volatile("mv %0, s0" : "=r" (x) );
+ return x;
+}
+
// flush the TLB.
static inline void
sfence_vma()
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index 3b4d5bd..4ef466f 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -51,6 +51,7 @@ sys_sbrk(void)
uint64
sys_sleep(void)
{
+ backtrace();
int n;
uint ticks0;
🟥 Alarm
In this exercise you’ll add a feature to xv6 that periodically alerts a process as it uses CPU time. This might be useful for compute-bound processes that want to limit how much CPU time they chew up, or for processes that want to compute but also want to take some periodic action. More generally, you’ll be implementing a primitive form of user-level interrupt/fault handlers; you could use something similar to handle page faults in the application, for example. Your solution is correct if it passes alarmtest and usertests.
分析
这个任务要我们实现 sigalarm
和 sigreturn
。前者每隔若干个 ticks
执行一个 handler
函数,后者结束这个 sigalarm
分配的任务。在 RISC-V 中,用户空间中的 pc
存在 p->trapframe->epc
中,我们在发生时钟中断时更新 alarm_tick_count
,如果达到相应的 ticks 数则执行 alarm_handler
(即 p->trapframe->epc = p->alarm_handler
)
大体的思路不是很复杂,有一些细节需要注意:
- 在执行
handler
函数之前要保持一些必要的寄存器,我这里是直接将trapframe
进行复制。在sigreturn
的时候还需要将这些寄存器的值进行复原 - 为了防止重复对
handler
进行赋值并调用,我在proc
中设置了一个开关变量alarm_running
,在执行handler
后无法再次对handler
复制,直到调用sigreturn
代码
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
diff --git a/Makefile b/Makefile
index ded5bc2..d89ddfe 100644
--- a/Makefile
+++ b/Makefile
@@ -199,6 +199,7 @@ endif
ifeq ($(LAB),traps)
UPROGS += \
+ $U/_alarmtest\
$U/_call\
$U/_bttest
endif
diff --git a/kernel/proc.c b/kernel/proc.c
index 959b778..5586691 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -132,6 +132,12 @@ found:
return 0;
}
+ if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){
+ freeproc(p);
+ release(&p->lock);
+ return 0;
+ }
+
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
@@ -146,6 +152,11 @@ found:
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
+ p->alarm_handler = 0;
+ p->alarm_intervals = 0;
+ p->alarm_tick_count = 0;
+ p->alarm_running = 0;
+
return p;
}
@@ -157,6 +168,8 @@ freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
+ if(p->alarm_trapframe)
+ kfree((void*)p->alarm_trapframe);
p->trapframe = 0;
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
diff --git a/kernel/proc.h b/kernel/proc.h
index d021857..77339dc 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -104,4 +104,10 @@ struct proc {
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
+
+ void (*alarm_handler)();
+ struct trapframe *alarm_trapframe;
+ int alarm_tick_count;
+ int alarm_intervals;
+ int alarm_running;
};
diff --git a/kernel/syscall.c b/kernel/syscall.c
index ed65409..cf8a2fc 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..382d781 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 4ef466f..583cf80 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -92,3 +92,27 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}
+
+uint64
+sys_sigalarm(void)
+{
+ struct proc* p = myproc();
+
+ argint(0, &p->alarm_intervals);
+ argaddr(1, (uint64*)&p->alarm_handler);
+
+ p->alarm_tick_count = 0;
+
+ return 0;
+}
+
+uint64
+sys_sigreturn(void)
+{
+ struct proc* p = myproc();
+
+ *p->trapframe = *p->alarm_trapframe;
+ p->alarm_running = 0;
+
+ return p->trapframe->a0;
+}
diff --git a/kernel/trap.c b/kernel/trap.c
index 512c850..94af5c0 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -67,6 +67,17 @@ usertrap(void)
syscall();
} else if((which_dev = devintr()) != 0){
// ok
+ if(which_dev == 2) {
+ if (p->alarm_intervals > 0) {
+ p->alarm_tick_count++;
+ if (p->alarm_tick_count == p->alarm_intervals && p->alarm_running == 0) {
+ *p->alarm_trapframe = *p->trapframe;
+ p->trapframe->epc = (uint64)p->alarm_handler;
+ p->alarm_tick_count = 0;
+ p->alarm_running = 1;
+ }
+ }
+ }
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
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");