MIT6828 学习笔记 013 (lab traps)

MIT6828 学习笔记 013 (lab traps)

RayAlto OP
RISC-V 指令表
点击展开
  • rs: register source
  • rd: register destination
  • lc: lui constant, 20-bit value
  • l: label
  • c: constant
  • o: offset
  • s: shift amount, unsigned, 5-bit
语法描述执行
lui rd, lcLoad Upper Immediaterd=lc<<12
jal rd, lJump And Linkrd=pc+4;pc=label
jalr rd, o(rs1)Jump And Link Registerrd=pc+4;pc=rs1+o
beq rs1, rs2, lBranch if =pc=(rs1==rs2?l:pc+4)
bne rs1, rs2, lBranch if !=pc=(rs1!=rs2?l:pc+4)
blt rs1, rs2, lBranch if <pc=(rs1<rs2?l:pc+4)
bltu rs1, rs2, lUnsigned version
bge rs1, rs2, lBranch if >=pc=(rs1>=rs2?l:pc+4)
bgeu rs1, rs2, lUnsigned version
lb rd, o(rs1)Load Byterd=mem[rs1+o]
lbu rd, o(rs1)Unsigned version
lh rd, o(rs1)Load Half wordrd=mem[rs1+o+1:rs1+o]
lhu rd, o(rs1)Unsigned version
lw rd, o(rs1)Load Wordrd=mem[rs1+o+3:rs1+o]
sb rs2, o(rs1)Store Bytemem[rs1+o]=rs2[7:0]
sh rs2, o(rs1)Store Half Wordmem[rs1+o+1:rs1+o]=rs2[15:0]
sw rs2, o(rs1)Store Wordmem[rs1+o+3:rs1+o]=rs2
addi rd, rs1, cADD Immediaterd=rs1+c
slti rd, rs1, cCompare < Immediaterd=(rs1<c?1:0)
sltiu rd, rs1, cUnsigned version
xori rd, rs1, cXOR Immediaterd=rs1^c
ori rd, rs1, cOR Immediaterd=rs1|c
andi rd, rs1, cAND Immediaterd=rs1&c
slli rd, rs1, sShift Left Logical Immediaterd=rs1<<s
srli rd, rs1, sShift Right Logical Immediaterd=rs1>>s
srai rd, rs1, sShift Arithmetic Immediaterd=rs1>>s
add rd, rs1, rs2ADDrd=rs1+rs2
sub rd, rs1, rs2SUBtractrd=rs1-rs2
sll rd, rs1, rs2Shift Left Logicalrd=rs1<<rs2[4:0]
slt rd, rs1, rs2Set Less Thanrd=(rs1<rs2?1:0)
sltu rd, rs1, rs2Unsigned version
xor rd, rs1, rs2XORrd=rs1^rs2
srl rd, rs1, rs2Shift Right Logicalrd=rs1>>rs2[4:0]
sra rd, rs1, rs2Shift Right Arithmeticrd=rs1>>rs2[4:0]
or rd, rs1, rs2ORrd=rs1|rs2
and rd, rs1, rs2ANDrd=rs1&rs2
RISC-V 伪指令表
点击展开
语法描述执行
li rd, cLoad Immediaterd=c
mv rd, rs1MoVerd=rs1+0
not rd, rs1NOTrd=rs1^-1
neg rd, rs1Negationrd=0-rs1
j lJumppc=l
jal l, call lJump and linkra=pc+4;pc=l
jr rs1Jump Registerpc=rs1&~1
jalr rs1Jump And Link Registerra=pc+4;pc=rs1&~1
retRETurn from subroutinepc=ra
bgt rs1, rs2, lBranch >pc=(rs1>rs2?l:pc+4)
bgtu rs1, rs2, lUnsigned version
ble rs1, rs2, lBranch <=pc=(rs1<=rs2?l:pc+4)
bleu rs1, rs2, lUnsigned version
beqz rs1, lBranch == 0pc=(rs1==0?l:pc+4)
bnez rs1, lBranch != 0pc=(rs1!=0?l:pc+4)
bltz rs1, lBranch < 0pc=(rs1<0?l:pc+4)
bgez rs1, lBranch >= 0pc=(rs1>=0?l:pc+4)
bgtz rs1, lBranch > 0pc=(rs1>0?l:pc+4)
blez rs1, lBranch <= 0pc=(rs1<=0?l:pc+4)

记得 checkout 到 traps

1. RISC-V 汇编

user/call.c 生成的 user/call.asm 。问:函数参数放在了那个寄存器?比如 user/call.c:15 里给 printf 传的 13 放在了那个寄存器?

答:粗略的说先放进参数寄存器 a0-a7 ,放不下的放进栈里,具体在 RISC-V 调用约定里有讲;根据 user/call.asm:45 可知 13 放进了 a2 里。

问: main 调用 f 的汇编代码在哪? g 呢?

答:根据 user/call.asm:26 可知调用被优化了,直接在编译期得出了 f(8)+112 ,所以 main 没有调用 fg

问: printf 函数的地址?

答:根据 user/call.asm:1078 可知 printf 的地址为 0x0000000000000630 (0x630)

问: main jalrprintf 之后 ra 的值?

答: jalr 产生的变化是 ra = pc + 4; pc = rs1 & ~1 所以根据 user/call.asm:50 可知 jalr 之后 rapc + 4 = 34 + 4 = 38

问:因为 RISC-V 采用 little-endian ,下面的代码会输出 Hello World

1
2
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

如果在 big-endian 情况下,想要这段代码输出 Hello Worldi 应该设为什么值? 57616 需要改吗?

答: i 应该设为 0x726c640057616 不用改

问:下面的代码中 y= 后面打印的值是什么?为什么?

1
printf("x=%d y=%d", 3);

答:打印的值应该是寄存器 a2 的值,因为调用约定规定参数先通过 a0-a7 传,传给 printfa0 应该指向 "x=%d y=%d" 字符串、 a1 值为 3 、剩下的寄存器是未定义的,然后实际打印出来的应该就是未定义的寄存器 a2 的值

2. Backtrace

通过 fp (s0) 寄存器保存的栈帧地址打印进程的 backtrace ,格式像这样:

1
2
3
4
backtrace:
0x0000000080002130
0x0000000080002022
0x0000000080001d18

通过 addr2line 可以获取对应 C 代码位置:

1
2
3
.../kernel/sysproc.c:58
.../kernel/syscall.c:141
.../kernel/trap.c:76
点击展开:我的答案
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
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..390653d 100644
--- a/kernel/printf.c
+++ b/kernel/printf.c
@@ -133,3 +133,14 @@ printfinit(void)
initlock(&pr.lock, "pr");
pr.locking = 1;
}
+
+void
+backtrace(void)
+{
+ printf("backtrace:\n");
+ for(uint64 fp = r_fp(), pgstart = PGROUNDDOWN(fp);
+ PGROUNDDOWN(fp) == pgstart;
+ fp = *(uint64*)(fp - 16)){
+ printf("%p\n", *(uint64*)(fp - 8));
+ }
+}
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 20a01db..5ccee95 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -327,6 +327,14 @@ sfence_vma()
asm volatile("sfence.vma zero, zero");
}

+static inline uint64
+r_fp()
+{
+ uint64 x;
+ asm volatile("mv %0, s0" : "=r" (x) );
+ return x;
+}
+
typedef uint64 pte_t;
typedef uint64 *pagetable_t; // 512 PTEs

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;

3. Alarm

设计两个系统调用,用户进程通过 int sigalarm(int ticks, void(*handler)()); 格式的系统调用每隔 ticks (设为 0 取消)使用户进程进入 handlerhandler 内通过系统调用 int sigreturn(void); 使用户进程返回进入 handler 之前的位置,换句话说就是实现一个类似 trap 的机制。

点击展开:我的答案
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
diff --git a/kernel/proc.c b/kernel/proc.c
index 959b778..ed04ffe 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -140,6 +140,14 @@ found:
return 0;
}

+ // init alarm context
+ if((p->alarm = (struct alarmctx *)kalloc()) == 0){
+ freeproc(p);
+ release(&p->lock);
+ return 0;
+ }
+ memset(p->alarm, 0, sizeof(*p->alarm));
+
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
@@ -160,6 +168,9 @@ freeproc(struct proc *p)
p->trapframe = 0;
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
+ if(p->alarm)
+ kfree((void*)p->alarm);
+ p->alarm = 0;
p->pagetable = 0;
p->sz = 0;
p->pid = 0;
diff --git a/kernel/proc.h b/kernel/proc.h
index d021857..a64836f 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -81,6 +81,15 @@ struct trapframe {

enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };

+// sigalarm context
+struct alarmctx {
+ uint64 handler;
+ int ticks;
+ int tick_count;
+ int handling;
+ struct trapframe trapframe;
+};
+
// Per-process state
struct proc {
struct spinlock lock;
@@ -95,6 +104,9 @@ struct proc {
// wait_lock must be held when using this:
struct proc *parent; // Parent process

+ // for sigalarm
+ struct alarmctx* alarm;
+
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
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..38a29e7 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -92,3 +92,22 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}
+
+uint64
+sys_sigalarm(void)
+{
+ struct proc* p = myproc();
+ argint(0, &p->alarm->ticks);
+ argaddr(1, &p->alarm->handler);
+ return 0;
+}
+
+uint64
+sys_sigreturn(void)
+{
+ struct proc* p = myproc();
+ memmove(p->trapframe, &p->alarm->trapframe, sizeof(*p->trapframe));
+ p->alarm->handling = 0;
+ p->alarm->tick_count = 0;
+ return p->trapframe->a0; // to restore a0
+}
diff --git a/kernel/trap.c b/kernel/trap.c
index 512c850..d1ea36a 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -80,6 +80,17 @@ usertrap(void)
if(which_dev == 2)
yield();

+ // keep track of ticks
+ if(which_dev == 2 && p->alarm->ticks && !p->alarm->handling){
+ p->alarm->tick_count += 1;
+ if(p->alarm->tick_count == p->alarm->ticks){
+ p->alarm->handling = 1;
+ memmove(&p->alarm->trapframe, p->trapframe, sizeof(*p->trapframe));
+ p->trapframe->epc = p->alarm->handler;
+ p->alarm->tick_count = 0;
+ }
+ }
+
usertrapret();
}

diff --git a/user/user.h b/user/user.h
index 4d398d5..93fe757 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");

题目让我思考 trap 进 handler 之前应该保存哪些寄存器来确保用户代码可以正确地返回之前的位置,我只想到 pc 肯定得存,但别的寄存器我实在不知道哪个不需要存,索性直接把所有寄存器都存了。

目录
MIT6828 学习笔记 013 (lab traps)