xv6

xv6(6.828 2021)

lab1:util

1.sleep

在user目录下新创建一个sleep.c文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
if (argc < 2)
{
fprintf(2, "Usage: sleep [time]\n");
exit(1);
}
for (int i=1; i<argc; i++)
{
sleep (atoi(argv[i]));
}
exit(0);
}

其中主要是调用了sleep函数。sleep是一段汇编代码,定义如下:

1
2
3
4
5
6
# user/usys.S
.global sleep
sleep:
li a7, SYS_sleep
ecall
ret

在riscv架构下,CPU从a7寄存器中读取系统调用号,然后执行ecall指令,ecall指令会触发一个trap,处理程序会根据a7寄存器的值调用相应的系统调用。a0~a6用于存放系统调用的参数。user/usys.S里没传递参数的原因是在sleep.asm中可以看到,调用sleep的时候就已经把参数传递给a0寄存器了。随后调用ecall指令,这个指令会将PC设置为stvec寄存器的值,跳转到那里去运行。在xv6创建用户进程(shell)的时候,会调用usertrapret,这个函数内的w_stvec(TRAMPOLINE + (uservec - trampoline));这一行代码将stvec寄存器的值设置为trampoline + (uservec - trampoline),即将stvec的值设置为uservec。uservec的定义如下:

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
.globl uservec
uservec:
#
# trap.c sets stvec to point here, so
# traps from user space start here,
# in supervisor mode, but with a
# user page table.
#
# sscratch points to where the process's p->trapframe is
# mapped into user space, at TRAPFRAME.
#

# swap a0 and sscratch
# so that a0 is TRAPFRAME
csrrw a0, sscratch, a0

# save the user registers in TRAPFRAME
sd ra, 40(a0)
sd sp, 48(a0)
sd gp, 56(a0)
sd tp, 64(a0)
sd t0, 72(a0)
sd t1, 80(a0)
sd t2, 88(a0)
sd s0, 96(a0)
sd s1, 104(a0)
sd a1, 120(a0)
sd a2, 128(a0)
sd a3, 136(a0)
sd a4, 144(a0)
sd a5, 152(a0)
sd a6, 160(a0)
sd a7, 168(a0)
sd s2, 176(a0)
sd s3, 184(a0)
sd s4, 192(a0)
sd s5, 200(a0)
sd s6, 208(a0)
sd s7, 216(a0)
sd s8, 224(a0)
sd s9, 232(a0)
sd s10, 240(a0)
sd s11, 248(a0)
sd t3, 256(a0)
sd t4, 264(a0)
sd t5, 272(a0)
sd t6, 280(a0)

# save the user a0 in p->trapframe->a0
csrr t0, sscratch
sd t0, 112(a0)

# restore kernel stack pointer from p->trapframe->kernel_sp
ld sp, 8(a0)

# make tp hold the current hartid, from p->trapframe->kernel_hartid
ld tp, 32(a0)

# load the address of usertrap(), p->trapframe->kernel_trap
ld t0, 16(a0)

# restore kernel page table from p->trapframe->kernel_satp
ld t1, 0(a0)
csrw satp, t1
sfence.vma zero, zero

# a0 is no longer valid, since the kernel page
# table does not specially map p->tf.

# jump to usertrap(), which does not return
jr t0

可以看出uservec有这么几个作用。

1.交换a0和sscratch的值,让a0指向当前进程的trapframe。

2.根据trapframe的布局保存寄存器的值到首地址存放在a0的trapframe中。

3.从用户栈切换到内核栈。

4.取出usertrap的地址。

5.从用户页表切换到内核页表。(页表的首地址存放在satp寄存器中)

6.跳转到usertrap。

在usertrap中,调用syscall(),syscall会读取当前trapframe中a7的值,根据这个值调用对应的系统调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sys_sleep(void)
{
int n;
uint ticks0;

if(argint(0, &n) < 0) //a0->n
return -1;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){
if(myproc()->killed){
release(&tickslock);
return -1;
}
sleep(&ticks, &tickslock);
}
release(&tickslock);
return 0;
}

2.pingpong

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
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
int p1[2]; // child -> parent
int p2[2]; // parent -> child
char buf[5];
pipe(p1);
pipe(p2);
if(fork() == 0){
// child
close(p2[1]); // 不写
close(p1[0]); // 不读
read(p2[0], buf, 4);
printf("%d: received ping\n", getpid());
write(p1[1], "pong", 4);
close(p2[0]);
close(p1[1]);
exit(0);
}
else{
// parent
close(p2[0]); // 不读
close(p1[1]); // 不写
write(p2[1], "ping", 4);
read(p1[0], buf, 4);
printf("%d: received pong\n", getpid());
close(p2[1]);
close(p1[0]);
wait(0);
exit(0);
}
}

3.primes

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
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void sieve(int fd)
{
int prime;
int num;
// 读出第一个数,它一定是素数
if(read(fd, &prime, sizeof(int)) == 0)
{
close(fd);
exit(0);
}
printf("prime %d\n", prime);
int p[2];
pipe(p);
if(fork() == 0)
{
// 子进程负责下一层筛选
close(p[1]);
close(fd);
sieve(p[0]);
}
else
{
close(p[0]);
while(read(fd, &num, sizeof(int)) > 0)
{
if(num % prime != 0)
{
write(p[1], &num, sizeof(int));
}
}
close(fd);
close(p[1]);
wait(0);
exit(0);
}
}

int main(int argc, char *argv[])
{
int p[2];
pipe(p);
if(fork() == 0)
{
close(p[1]);
sieve(p[0]);
}
else
{
close(p[0]);
for(int i = 2; i <= 35; i++)
{
write(p[1], &i, sizeof(int));
}
close(p[1]);
wait(0);
}

exit(0);
}

2,3两个实验主要是用到了管道pipe。管道是一种进程间通信的方式,一个为读端,一个为写端。在读端和写端之间,数据是按顺序写入的,并且读端和写端是成对出现的,且数据只能单向流动。

4.find

5.xargs

lab2:syscall

关于syscall的大体流程,在lab1的sleep中已经介绍过了。

1.trace

这一节要改的代码比较多,直接用git diff给出。

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
diff --git a/Makefile b/Makefile
index c926b7e..6647da5 100644
--- a/Makefile
+++ b/Makefile
@@ -193,6 +193,7 @@ UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
+ $U/_trace\



diff --git a/kernel/proc.c b/kernel/proc.c
index 22e7ce4..af1adca 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -315,6 +315,11 @@ fork(void)
np->state = RUNNABLE;
release(&np->lock);

+ acquire(&np->lock);
+ np->mask = p->mask;
+ release(&np->lock);
+
+
return pid;
}

diff --git a/kernel/proc.h b/kernel/proc.h
index f6ca8b7..7dea72b 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -105,4 +105,5 @@ struct proc {
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
+ int mask;
};
diff --git a/kernel/syscall.c b/kernel/syscall.c
index c1b3670..cb78e11 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -104,6 +104,7 @@ extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
+extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
@@ -127,8 +128,34 @@ static uint64 (*syscalls[])(void) = {
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
+[SYS_trace] sys_trace,
};

+static char *syscall_names[] = {
+ [SYS_fork] = "fork",
+ [SYS_exit] = "exit",
+ [SYS_wait] = "wait",
+ [SYS_pipe] = "pipe",
+ [SYS_read] = "read",
+ [SYS_kill] = "kill",
+ [SYS_exec] = "exec",
+ [SYS_fstat] = "fstat",
+ [SYS_chdir] = "chdir",
+ [SYS_dup] = "dup",
+ [SYS_getpid] = "getpid",
+ [SYS_sbrk] = "sbrk",
+ [SYS_sleep] = "sleep",
+ [SYS_uptime] = "uptime",
+ [SYS_open] = "open",
+ [SYS_write] = "write",
+ [SYS_mknod] = "mknod",
+ [SYS_unlink] = "unlink",
+ [SYS_link] = "link",
+ [SYS_mkdir] = "mkdir",
+ [SYS_close] = "close",
+ [SYS_trace] = "trace",
+ };
+
void
syscall(void)
{
@@ -143,4 +170,11 @@ syscall(void)
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
+ if ((p->mask >> num) & 1)
+ {
+ printf("%d: syscall %s -> %d\n",
+ p->pid,
+ syscall_names[num],
+ p->trapframe->a0);
+ }
}
diff --git a/kernel/syscall.h b/kernel/syscall.h
index bc5f356..cc112b9 100644
--- a/kernel/syscall.h
+++ b/kernel/syscall.h
@@ -20,3 +20,4 @@
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
+#define SYS_trace 22
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index e8bcda9..db4ae47 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -6,6 +6,7 @@
#include "memlayout.h"
#include "spinlock.h"
#include "proc.h"
+#include "syscall.h"

uint64
sys_exit(void)
@@ -95,3 +96,15 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}
+
+// trace syscall
+uint64 sys_trace(void)
+{
+ int n;
+ struct proc *p = myproc();
+ if(argint(0, &n) < 0)
+ return -1;
+ int mask = n; //取出mask
+ p->mask = mask;
+ return 0;
+}
\ No newline at end of file
diff --git a/user/user.h b/user/user.h
index b71ecda..fdeeefc 100644
--- a/user/user.h
+++ b/user/user.h
@@ -23,6 +23,7 @@ int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
+int trace(int);

// ulib.c
int stat(const char*, struct stat*);
diff --git a/user/usys.pl b/user/usys.pl
index 01e426e..04fc322 100755
--- a/user/usys.pl
+++ b/user/usys.pl
@@ -36,3 +36,4 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
+entry("trace")

首先是按照要求,在proc结构体中添加mask字段,并在fork的时候让子进程继承父进程的mask字段。修改makefile,添加系统调用号和函数声明等。重点在于trace的编写和对syscall函数的修改。

trace系统调用的主要功能是给当前进程的mask字段设置为系统调用传递来的参数。打印工作在syscall函数中进行。

1
2
3
4
// syscall.c
// void syscall(void)
num = p->trapframe->a7;
p->trapframe->a0 = syscalls[num]();

通过这两行代码我们可以看出,系统调用的返回值被放在了当前进程trapframe的a0字段中,系统调用的调用号放在了a7字段中。在调用trace时,num,即a7存放的就是这个系统调用对应的系统调用号。 (p->mask >> num) & 1的目的就是将掩码的第num位取出来,如果为1,则打印,为0则不打印。一个进程可能会调用许多次系统调用,每次都会进入syscall函数进行打印。

2.sysinfo

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
diff --git a/Makefile b/Makefile
index 6647da5..8b66b46 100644
--- a/Makefile
+++ b/Makefile
@@ -20,6 +20,7 @@ OBJS = \
$K/trap.o \
$K/syscall.o \
$K/sysproc.o \
+ $K/sysinfo.o\
$K/bio.o \
$K/fs.o \
$K/log.o \
@@ -194,6 +195,7 @@ UPROGS=\
$U/_wc\
$U/_zombie\
$U/_trace\
+ $U/_sysinfotest\



diff --git a/kernel/kalloc.c b/kernel/kalloc.c
index fa6a0ac..a2f446a 100644
--- a/kernel/kalloc.c
+++ b/kernel/kalloc.c
@@ -8,6 +8,7 @@
#include "spinlock.h"
#include "riscv.h"
#include "defs.h"
+#include "sysinfo.h"

void freerange(void *pa_start, void *pa_end);

@@ -80,3 +81,18 @@ kalloc(void)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}
+
+uint64 count_free_memory(void)
+{
+ struct run *r;
+ uint64 bytes = 0;
+ acquire(&kmem.lock);
+ r = kmem.freelist;
+ while(r)
+ {
+ bytes += PGSIZE;
+ r = r->next;
+ }
+ release(&kmem.lock);
+ return bytes;
+}
\ No newline at end of file
diff --git a/kernel/proc.c b/kernel/proc.c
index af1adca..2e4d9d2 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -5,6 +5,7 @@
#include "spinlock.h"
#include "proc.h"
#include "defs.h"
+#include "sysinfo.h"

struct cpu cpus[NCPU];

@@ -659,3 +660,18 @@ procdump(void)
printf("\n");
}
}
+
+int count_proc(void)
+{
+ struct proc *p;
+ int count = 0;
+ for (p = proc; p < &proc[NPROC]; p++)
+ {
+ if(p->state != UNUSED)
+ {
+ count++;
+ }
+ }
+ return count;
+}
+
diff --git a/kernel/syscall.c b/kernel/syscall.c
index cb78e11..f35a0f5 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -105,6 +105,7 @@ extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void);
+extern uint64 sys_sysinfo(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
@@ -129,6 +130,7 @@ static uint64 (*syscalls[])(void) = {
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace,
+[SYS_sysinfo] sys_sysinfo,
};

static char *syscall_names[] = {
@@ -154,6 +156,7 @@ static char *syscall_names[] = {
[SYS_mkdir] = "mkdir",
[SYS_close] = "close",
[SYS_trace] = "trace",
+ [SYS_sysinfo] = "sysinfo",
};

void
diff --git a/kernel/syscall.h b/kernel/syscall.h
index cc112b9..2740484 100644
--- a/kernel/syscall.h
+++ b/kernel/syscall.h
@@ -21,3 +21,4 @@
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_trace 22
+#define SYS_sysinfo 23
\ No newline at end of file
diff --git a/kernel/sysinfo.c b/kernel/sysinfo.c
new file mode 100644
index 0000000..b7b9bd4
--- /dev/null
+++ b/kernel/sysinfo.c
@@ -0,0 +1,31 @@
+#include "types.h"
+#include "riscv.h"
+#include "defs.h"
+#include "param.h"
+#include "memlayout.h"
+#include "spinlock.h"
+#include "proc.h"
+#include "sysinfo.h"
+
+int sys_sysinfo(void)
+{
+ uint64 addr;
+ struct proc *p;
+ struct sysinfo info;
+
+ if(argaddr(0, &addr) < 0)
+ return -1;
+
+ info.freemem = count_free_memory();
+ info.nproc = count_proc();
+
+ p = myproc();
+
+ if(copyout(p->pagetable,
+ addr,
+ (char *)&info,
+ sizeof(info)) < 0)
+ return -1;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/kernel/sysinfo.h b/kernel/sysinfo.h
index fb878e6..8cbb0d1 100644
--- a/kernel/sysinfo.h
+++ b/kernel/sysinfo.h
@@ -1,4 +1,7 @@
+#include "types.h"
struct sysinfo {
uint64 freemem; // amount of free memory (bytes)
uint64 nproc; // number of process
};
+extern uint64 count_free_memory(void);
+extern int count_proc(void);
\ No newline at end of file
diff --git a/user/user.h b/user/user.h
index fdeeefc..67fef40 100644
--- a/user/user.h
+++ b/user/user.h
@@ -1,5 +1,6 @@
struct stat;
struct rtcdate;
+struct sysinfo;

// system calls
int fork(void);
@@ -24,6 +25,7 @@ char* sbrk(int);
int sleep(int);
int uptime(void);
int trace(int);
+int sysinfo(struct sysinfo *);

// ulib.c
int stat(const char*, struct stat*);
@@ -41,3 +43,4 @@ void free(void*);
int atoi(const char*);
int memcmp(const void *, const void *, uint);
void *memcpy(void *, const void *, uint);
+
diff --git a/user/usys.pl b/user/usys.pl
index 04fc322..bc109fd 100755
--- a/user/usys.pl
+++ b/user/usys.pl
@@ -36,4 +36,5 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
-entry("trace")
+entry("trace");
+entry("sysinfo");

我们需要关注的是三件事。

1.统计进程数

2.统计空闲内存

3.sysinfo如何工作,copyout怎么进行拷贝。

1
2
3
$ sysinfotest
sysinfotest: start
sysinfotest: OK

1.统计进程数

xv6有一个固定大小的进程表,只需要统计进程表中状态为 非UNUSED 的进程数量即可。

2.统计空闲内存

统计 kmem.freelist 有多少个空闲页面再转化为字节数即可。

3.sysinfo如何工作,copyout怎么进行拷贝。

在 sysinfotest.c 中,有如下语句:

1
2
struct sysinfo info;
sinfo(&info);

在用户空间中,向系统调用函数sysinfo传入了一个用户态的地址,当cpu处于内核态的时候,内核不能把用户虚拟地址当成普通指针直接解引用。因此需要通过 copyout 函数将内核态的数据拷贝到用户态,让用户程序访问内核数据的拷贝。

copyout的函数原型为:

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
///////copyout(p->pagetable,addr,(char *)&info,sizeof(info));
// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
uint64 n, va0, pa0;

while(len > 0){
va0 = PGROUNDDOWN(dstva);
pa0 = walkaddr(pagetable, va0);
if(pa0 == 0)
return -1;
n = PGSIZE - (dstva - va0);
if(n > len)
n = len;
memmove((void *)(pa0 + (dstva - va0)), src, n);

len -= n;
src += n;
dstva = va0 + PGSIZE;
}
return 0;
}

其参数含义如下:

1
2
3
4
5
6
// 将内核空间 src 指向的 len 字节数据,
// 复制到用户进程页表 pagetable 中的用户虚拟地址 dstva。
pagetable_t pagetable, // 用户进程页表
uint64 dstva, // 用户虚拟地址(destination virtual address)
char *src, // 内核源地址(source)
uint64 len // 复制长度(单位:Byte)

lab3:pgtbl

1.Speed up system calls

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
diff --git a/kernel/proc.c b/kernel/proc.c
index 22e7ce4..f21be13 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -127,6 +127,15 @@ found:
return 0;
}

+ //Allocate a user-kernel page
+ if((p->usyscall = (struct usyscall*)kalloc()) == 0){
+ freeproc(p);
+ release(&p->lock);
+ return 0;
+ }
+
+ p->usyscall->pid = p->pid;
+
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
@@ -155,6 +164,10 @@ freeproc(struct proc *p)
p->trapframe = 0;
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
+ if(p->usyscall) {
+ kfree((void*)p->usyscall);
+ p->usyscall = 0;
+ }
p->pagetable = 0;
p->sz = 0;
p->pid = 0;
@@ -196,6 +209,19 @@ proc_pagetable(struct proc *p)
return 0;
}

+ // map the user-kernel page
+ if(mappages(pagetable,
+ USYSCALL,
+ PGSIZE,
+ (uint64)p->usyscall,
+ PTE_R | PTE_U) < 0)
+ {
+ uvmunmap(pagetable, USYSCALL, 1, 0);
+ uvmfree(pagetable, 0);
+ return 0;
+ }
+
+
return pagetable;
}

@@ -206,6 +232,7 @@ proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
+ uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, sz);
}

diff --git a/kernel/proc.h b/kernel/proc.h
index f6ca8b7..08e379d 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -86,6 +86,8 @@ enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
struct proc {
struct spinlock lock;

+ struct usyscall *usyscall;
+
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan

这个实验的目的就是在内核和用户空间中开辟一个可以共享的内存页,这个内存页的虚拟地址位于USYSCALL中。用户程序通过ugetpid()函数直接访问这片内存页,从而获取当前进程的pid,而不是使用getpid系统调用。

proc_pagetalble这个函数会创建一个新的用户页表,并映射几个页面。分配这个内存页的代码如下:

1
2
3
4
5
6
7
8
9
10
11
if(mappages(pagetable,
USYSCALL,
PGSIZE,
(uint64)p->usyscall,
PTE_R | PTE_U) < 0)
{
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, 0);
return 0;
}

mappages的函数实现如下,它的功能是做va -> pa 的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
uint64 a, last;
pte_t *pte;

if(size == 0)
panic("mappages: size");

a = PGROUNDDOWN(va);
last = PGROUNDDOWN(va + size - 1);
for(;;){
if((pte = walk(pagetable, a, 1)) == 0)
return -1;
if(*pte & PTE_V)
panic("mappages: remap");
*pte = PA2PTE(pa) | perm | PTE_V; //向L0的pte中写入物理页面的地址
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}

walk函数定义为下,用于返回L0 页表中某一个页表项(PTE)的地址

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
// Return the address of the PTE in page table pagetable
// that corresponds to virtual address va. If alloc!=0,
// create any required page-table pages.
//
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
// 39..63 -- must be zero.
// 30..38 -- 9 bits of level-2 index.
// 21..29 -- 9 bits of level-1 index.
// 12..20 -- 9 bits of level-0 index.
// 0..11 -- 12 bits of byte offset within the page.
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");

for(int level = 2; level > 0; level--) {
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}

2.打印页表

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
diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..a0ce189 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -170,6 +170,7 @@ uint64 walkaddr(pagetable_t, uint64);
int copyout(pagetable_t, uint64, char *, uint64);
int copyin(pagetable_t, char *, uint64, uint64);
int copyinstr(pagetable_t, char *, uint64, uint64);
+void vmprint(pagetable_t pagetable);

// plic.c
void plicinit(void);
diff --git a/kernel/exec.c b/kernel/exec.c
index 36048d6..3a9b22b 100644
--- a/kernel/exec.c
+++ b/kernel/exec.c
@@ -115,6 +115,10 @@ exec(char *path, char **argv)
p->trapframe->epc = elf.entry; // initial program counter = main
p->trapframe->sp = sp; // initial stack pointer
proc_freepagetable(oldpagetable, oldsz);
+ if(p->pid==1)
+ {
+ vmprint(p->pagetable);
+ }
return argc; // this ends up in a0, the first argument to main(argc, argv)

bad:
diff --git a/kernel/vm.c b/kernel/vm.c
index d5a12a0..b39bbf0 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c
@@ -432,3 +432,36 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
return -1;
}
}
+
+void depth_vmprint(pagetable_t pagetable,int depth)
+{
+ for(int i = 0; i < 512; i++){
+ pte_t pte = pagetable[i];
+ if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0) {
+ for (int j = 0; j < depth; j++)
+ {
+ printf(" ..");
+ }
+ uint64 child = PTE2PA(pte);
+ printf("%d: ",i);
+ printf("pte %p pa %p",pte,(pagetable_t)child);
+ printf("\n");
+ depth_vmprint((pagetable_t)child, depth + 1);
+ } else if(pte & PTE_V && (pte & (PTE_R | PTE_W | PTE_X)) != 0){
+ for (int j = 0; j < depth; j++)
+ {
+ printf(" ..");
+ }
+ uint64 child = PTE2PA(pte);
+ printf("%d: ",i);
+ printf("pte %p pa %p",pte,(pagetable_t)child);
+ printf("\n");
+ }
+ }
+}
+
+void vmprint(pagetable_t pagetable)
+{
+ printf("page table %p\n",pagetable);
+ depth_vmprint(pagetable,1);
+}

参考free_walk函数递归来写就好了。

3.Detecting which pages have been accessed

这道题的主要目的是看明白他在问什么。看懂之后个人觉得不算是一个hard的题。

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
diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..b8d651d 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -170,6 +170,8 @@ uint64 walkaddr(pagetable_t, uint64);
int copyout(pagetable_t, uint64, char *, uint64);
int copyin(pagetable_t, char *, uint64, uint64);
int copyinstr(pagetable_t, char *, uint64, uint64);
+pte_t *walk(pagetable_t pagetable, uint64 va, int alloc);

// plic.c
void plicinit(void);
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 1691faf..5b5e4a0 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -343,6 +343,7 @@ sfence_vma()
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access
+#define PTE_A (1L << 6)

// shift a physical address to the right place for a PTE.
#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index 3bd0007..1e37afe 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -81,6 +81,38 @@ int
sys_pgaccess(void)
{
// lab pgtbl: your code here.
+ // 参数1:获取要检查的第一个用户页面的起始虚拟地址
+ // 参数2:要检查的页面数量。
+ // 参数3:一个用户空间地址,用于存放检查结果(mask)。
+ uint64 base;
+ int len;
+ uint64 mask;
+ uint64 sum = 0;
+
+ argaddr(0, &base); // 第0个参数
+ argint(1, &len); // 第1个参数
+ argaddr(2, &mask); // 第2个参数
+ uint64 virtual_addr[len];
+ struct proc *p = myproc();
+ for (int i = 0; i < len; i++)
+ {
+ virtual_addr[i] = base + (i * PGSIZE); //将需要检查的虚拟页面的首地址填入数组中
+ }
+ for (int j = 0; j < len; j++)
+ {
+ pte_t *temp_pte = walk(p->pagetable, virtual_addr[j], 0);
+ if (*temp_pte & PTE_A)
+ {
+ sum += 1 << j;
+ *temp_pte &= ~PTE_A;
+ } else
+ {
+ sum += 0 << j;
+ }
+ }
+ copyout(p->pagetable, mask, (char *)&sum, sizeof(sum));
+ sum = 0;
return 0;
}
#endif

这道题就需要实现一个sys_pgaccess函数即可。首先是要接收从用户态传来的三个参数,直接调用实现好的argaddr和argint函数即可获取。我们要检查的是以base为起始的len个页面(对应的物理页面,所以要用walk函数找一下)是否有A位,理解了这些代码就很容易写了。

lab4:trap

1.riscv assembly

1
1.哪些寄存器包含函数的参数?例如,在main函数调用printf时,哪个寄存器存储了13?

查阅riscv手册,在函数调用规范章节中可以看到在函数调用过程中各个寄存器的作用,如下图所示。
riscv-calling-convention

可见函数参数保存在a0~a7寄存器中。在main函数调用printf时,a2寄存器存储了13。

1
2.在 main 函数的汇编代码中,对函数 f 的调用在哪里?对 g 的调用又在哪里?(提示:编译器可能会将函数内联。) 

main没有调用f,f也没有调用g。编译器将f内联进了main中,g内联进了f中。

1
3.函数 printf 位于哪个地址?

0x648。

1
2
4.在 main 函数中,printf 语句前的 jalr 指令之后,寄存器 ra 中存有什么值?

0x38。

1
2
3
4
5
6
7
8
5.运行以下代码。

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

输出结果是什么?

输出结果取决于 RISC-V 采用小端序这一事实。如果 RISC-V 采用大端序,为了得到相同的输出结果,你应该将 i 设置为多少?是否需要将 57616 改为其他数值?

输出结果是 HE110 World。

如果riscv是大端序,则要改成unsigned int i = 0x726c6400;。57616不需要改动。

1
2
3
6.在下面的代码中,'y=' 后面会输出什么?(注意:答案不是具体的数值。)为什么会出现这种情况?

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

y是一个不可知的值。因为这是一个未定义的行为。

2.Backtrace

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
diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..9224b0f 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 e1347de..155ebfc 100644
--- a/kernel/printf.c
+++ b/kernel/printf.c
@@ -121,6 +121,7 @@ panic(char *s)
printf("panic: ");
printf(s);
printf("\n");
+ backtrace();
panicked = 1; // freeze uart output from other CPUs
for(;;)
;
@@ -132,3 +133,16 @@ printfinit(void)
initlock(&pr.lock, "pr");
pr.locking = 1;
}
+
+void backtrace(void)
+{
+ uint64 fp = r_fp();
+ uint64 bottom = PGROUNDDOWN(fp);
+ uint64 top = PGROUNDUP(fp);
+
+ while (fp >= bottom && fp < top)
+ {
+ printf("%p\n",*(uint64 *)(fp - 8));
+ fp = *(uint64 *)(fp - 16);
+ }
+}
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 1691faf..631e013 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -364,3 +364,12 @@ sfence_vma()

typedef uint64 pte_t;
typedef uint64 *pagetable_t; // 512 PTEs
+
+// get the frame pointer
+static inline uint64
+r_fp()
+{
+ uint64 x;
+ asm volatile("mv %0, s0" : "=r" (x));
+ return x;
+}
\ No newline at end of file
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index e8bcda9..767c972 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -67,7 +67,9 @@ sys_sleep(void)
release(&tickslock);
return -1;
}
+ backtrace();
sleep(&ticks, &tickslock);
+
}
release(&tickslock);
return 0;

3.alarm

3.1 test0
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
diff --git a/Makefile b/Makefile
index 7a7e380..bc4d47a 100644
--- a/Makefile
+++ b/Makefile
@@ -188,6 +188,7 @@ UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
+ $U/_alarmtest\

diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..9224b0f 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 e1347de..155ebfc 100644
--- a/kernel/printf.c
+++ b/kernel/printf.c
@@ -121,6 +121,7 @@ panic(char *s)
printf("panic: ");
printf(s);
printf("\n");
+ backtrace();
panicked = 1; // freeze uart output from other CPUs
for(;;)
;
@@ -132,3 +133,16 @@ printfinit(void)
initlock(&pr.lock, "pr");
pr.locking = 1;
}
+
+void backtrace(void)
+{
+ uint64 fp = r_fp();
+ uint64 bottom = PGROUNDDOWN(fp);
+ uint64 top = PGROUNDUP(fp);
+
+ while (fp >= bottom && fp < top)
+ {
+ printf("%p\n",*(uint64 *)(fp - 8));
+ fp = *(uint64 *)(fp - 16);
+ }
+}
diff --git a/kernel/proc.c b/kernel/proc.c
index 22e7ce4..a22d72e 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -141,6 +141,10 @@ found:
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;

+ //alarm
+ p->alarm_interval = 0;
+ p->remain_alarm_interval = 0;
+
return p;
}

diff --git a/kernel/proc.h b/kernel/proc.h
index f6ca8b7..5fda67c 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -1,3 +1,5 @@
+#include "types.h"
+
// Saved registers for kernel context switches.
struct context {
uint64 ra;
@@ -105,4 +107,9 @@ struct proc {
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
+
+ //alarm
+ uint64 handler;
+ uint64 alarm_interval;
+ uint64 remain_alarm_interval;
};
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 1691faf..631e013 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -364,3 +364,12 @@ sfence_vma()

typedef uint64 pte_t;
typedef uint64 *pagetable_t; // 512 PTEs
+
+// get the frame pointer
+static inline uint64
+r_fp()
+{
+ uint64 x;
+ asm volatile("mv %0, s0" : "=r" (x));
+ return x;
+}
\ No newline at end of file
diff --git a/kernel/syscall.c b/kernel/syscall.c
index c1b3670..24bfccd 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -104,6 +104,8 @@ extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
+extern uint64 sys_sigalarm(void);
+extern uint64 sys_sigreturn(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
@@ -127,6 +129,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..c09f4bd 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
\ No newline at end of file
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index e8bcda9..00a580d 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -67,7 +67,9 @@ sys_sleep(void)
release(&tickslock);
return -1;
}
+ backtrace();
sleep(&ticks, &tickslock);
+
}
release(&tickslock);
return 0;
@@ -95,3 +97,24 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}
+
+uint64 sys_sigalarm(void)
+{
+ int tick;
+ argint(0, &tick);
+ uint64 func;
+ argaddr(1,&func);
+ struct proc *p = myproc();
+ p->alarm_interval = 0;
+ p->remain_alarm_interval = tick;
+ p->handler = func;
+ return 0;
+}
+
+
+
+uint64 sys_sigreturn(void)
+{
+ return 0;
+}
+
diff --git a/kernel/trap.c b/kernel/trap.c
index a63249e..c0fc325 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -78,7 +78,17 @@ usertrap(void)

// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
+ {
+ p->remain_alarm_interval--;
+ p->alarm_interval++;
+ if (p->remain_alarm_interval == 0)
+ {
+ //((void (*)(void))p->handler)(); p->handler存储用户空间程序地址,这么做是相当于内核直接执行用户态程序
+ p->trapframe->epc = p->handler;
+ }
yield();
+ }
+

usertrapret();
}
diff --git a/user/user.h b/user/user.h
index b71ecda..57404e0 100644
--- a/user/user.h
+++ b/user/user.h
@@ -23,6 +23,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");

test0主要是对整个题目搭建起一个框架。需要注意的一点是RISC-V 从 trap 返回用户态时会执行sret,将PC设置为trapframe->epc。trapframe是用来保存用户态程序在发生trap时的 CPU 状态的结构。

test1&test2

test1,test2两个测试需要注意的细节很多。test1要求正确恢复被 timer interrupt 打断时的用户程序现场,test2要求当 alarm handler 还没有执行完时,新的 timer interrupt 不能再次进入 handler。简单来说就是:

test1的恢复现场,我们需要在执行完alarm handler之后返回用户进程,所以要保存当前进程的trapframe,在sys_sigreturn的时候恢复。

test2要求不能打断 alarm handler 的执行,所以要添加一个变量去当锁。

lab5:cow

到目前为止做的最牢的一个实验,在gpt的辅助下都做吐了。要关注的事情太多。

先看diff:

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..626174d 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -63,6 +63,7 @@ void ramdiskrw(struct buf*);
void* kalloc(void);
void kfree(void *);
void kinit(void);
+void cow_ref_count(uint64 pa);

// log.c
void initlog(int, struct superblock*);
@@ -170,6 +171,7 @@ uint64 walkaddr(pagetable_t, uint64);
int copyout(pagetable_t, uint64, char *, uint64);
int copyin(pagetable_t, char *, uint64, uint64);
int copyinstr(pagetable_t, char *, uint64, uint64);
+pte_t *walk(pagetable_t pagetable, uint64 va, int alloc);

// plic.c
void plicinit(void);
diff --git a/kernel/kalloc.c b/kernel/kalloc.c
index fa6a0ac..8f98c5a 100644
--- a/kernel/kalloc.c
+++ b/kernel/kalloc.c
@@ -13,6 +13,12 @@ void freerange(void *pa_start, void *pa_end);

extern char end[]; // first address after kernel.
// defined by kernel.ld.
+#define NPAGES ((PHYSTOP - KERNBASE) / PGSIZE)
+
+struct {
+ struct spinlock lock;
+ int count[NPAGES];
+} ref; //页面引用计数

struct run {
struct run *next;
@@ -23,10 +29,17 @@ struct {
struct run *freelist;
} kmem;

+int
+refindex(uint64 pa)
+{
+ return (pa - KERNBASE) / PGSIZE;
+}
+
void
kinit()
{
initlock(&kmem.lock, "kmem");
+ initlock(&ref.lock, "ref");
freerange(end, (void*)PHYSTOP);
}

@@ -36,22 +49,77 @@ freerange(void *pa_start, void *pa_end)
char *p;
p = (char*)PGROUNDUP((uint64)pa_start);
for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
+ {
+ acquire(&ref.lock);
+ ref.count[refindex((uint64)p)] = 1;
+ release(&ref.lock);
kfree(p);
+ }
}

// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc(). (The exception is when
// initializing the allocator; see kinit above.)
+// void
+// kfree(void *pa)
+// {
+// struct run *r;
+//
+// if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
+// panic("kfree");
+//
+// acquire(&ref.lock);
+// ref.count[refindex((uint64)pa)]--;
+// if(ref.count[refindex((uint64)pa)] > 0)
+// {
+// release(&ref.lock);
+// return;
+// }
+// release(&ref.lock);
+//
+//
+// // Fill with junk to catch dangling refs.
+// memset(pa, 1, PGSIZE);
+//
+// r = (struct run*)pa;
+//
+// acquire(&kmem.lock);
+// r->next = kmem.freelist;
+// kmem.freelist = r;
+// release(&kmem.lock);
+// }
+
+
void
kfree(void *pa)
{
struct run *r;
+ int idx;

- if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
+ if(((uint64)pa % PGSIZE) != 0 ||
+ (char*)pa < end ||
+ (uint64)pa >= PHYSTOP)
panic("kfree");

- // Fill with junk to catch dangling refs.
+ idx = refindex((uint64)pa);
+
+ acquire(&ref.lock);
+
+ if(ref.count[idx] <= 0){
+ release(&ref.lock);
+ panic("kfree: bad ref");
+ }
+
+ ref.count[idx]--;
+
+ if(ref.count[idx] > 0){
+ release(&ref.lock);
+ return;
+ }
+
+ release(&ref.lock);
+
memset(pa, 1, PGSIZE);

r = (struct run*)pa;
@@ -77,6 +145,19 @@ kalloc(void)
release(&kmem.lock);

if(r)
+ {
memset((char*)r, 5, PGSIZE); // fill with junk
+ acquire(&ref.lock);
+ ref.count[refindex((uint64)r)] = 1;
+ release(&ref.lock);
+ }
+
return (void*)r;
}
+
+void cow_ref_count(uint64 pa)
+{
+ acquire(&ref.lock);
+ ref.count[refindex(pa)]++;
+ release(&ref.lock);
+}
\ No newline at end of file
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 1691faf..271c7c6 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -343,6 +343,7 @@ sfence_vma()
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access
+#define PTE_COW (1L << 8) //reserved for software

// shift a physical address to the right place for a PTE.
#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
diff --git a/kernel/trap.c b/kernel/trap.c
index a63249e..1590f0e 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -67,7 +67,51 @@ usertrap(void)
syscall();
} else if((which_dev = devintr()) != 0){
// ok
- } else {
+ } else if (r_scause() == 15) // page fault
+ {
+
+ uint64 pa;
+ uint flags;
+ char *mem;
+ uint64 va = r_stval();
+ if(va >= MAXVA)
+ {
+ p->killed = 1;
+ } else
+ {
+ pa = walkaddr(p->pagetable, va);
+ pte_t *pte = walk(p->pagetable, va, 0);
+
+ if(pte == 0 ||
+ (*pte & PTE_V) == 0 ||
+ (*pte & PTE_U) == 0 ||
+ (*pte & PTE_COW) == 0)
+ {
+ // 不是合法的 COW fault,杀掉进程
+ p->killed = 1;
+ } else
+ {
+ if (*pte & PTE_COW) //cow page fault
+ {
+ if((mem = kalloc()) == 0)
+ {
+ printf("page fault. alloc page fault.");
+ p->killed = 1;
+ } else
+ {
+ memmove(mem, (char*)pa, PGSIZE); //复制旧页面到新页面中
+ flags = PTE_FLAGS(*pte);
+ flags |= PTE_W;
+ flags &= ~PTE_COW;
+ *pte = PA2PTE((uint64)mem) | flags;
+
+ kfree((void *)pa); //释放一个父进程的物理页面的引用,因为子进程的pte已经不指向这个物理页面
+ }
+ }
+ }
+ }
+ }
+ else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
@@ -81,6 +125,10 @@ usertrap(void)
yield();

usertrapret();
+
+// err:
+// printf("page fault. alloc page fault.");
+// p->killed = 1;
}

//
diff --git a/kernel/vm.c b/kernel/vm.c
index d5a12a0..98fdd5a 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c
@@ -303,27 +303,57 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
pte_t *pte;
uint64 pa, i;
uint flags;
- char *mem;
-
- for(i = 0; i < sz; i += PGSIZE){
- if((pte = walk(old, i, 0)) == 0)
+ // for(i = 0; i < sz; i += PGSIZE)
+ // {
+ // if((pte = walk(old, i, 0)) == 0)
+ // panic("uvmcopy: pte should exist");
+ // if((*pte & PTE_V) == 0)
+ // panic("uvmcopy: page not present");
+ // pa = PTE2PA(*pte);
+ // flags = PTE_FLAGS(*pte);
+ // if((mem = kalloc()) == 0)
+ // goto err;
+ // memmove(mem, (char*)pa, PGSIZE);
+ // if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
+ // kfree(mem);
+ // goto err;
+ // }
+ // }
+
+ /*cow*/
+ for (i = 0; i < sz; i+=PGSIZE)
+ {
+ if((pte = walk(old, i, 0)) == 0) //父进程页表项
panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
panic("uvmcopy: page not present");
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
- if((mem = kalloc()) == 0)
- goto err;
- memmove(mem, (char*)pa, PGSIZE);
- if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
- kfree(mem);
+
+ if(flags & PTE_W) //清除PTE_W防止写行为并设置PTE_COW
+ {
+ flags &= ~PTE_W;
+ flags |= PTE_COW;
+ // 修改父进程 PTE
+ *pte = PA2PTE(pa) | flags;
+ }
+
+ // mappages(pagetable, va, PGSIZE, pa, flags);
+ if (mappages(new, i, PGSIZE, pa, flags) != 0)
+ {
+ printf("uvmcopy: cow mapping failed");
goto err;
}
+ // 增加物理页面引用计数
+ cow_ref_count(pa);
}
+
+
+
return 0;

- err:
- uvmunmap(new, 0, i / PGSIZE, 1);
+ err:
+ uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}

@@ -343,14 +373,87 @@ uvmclear(pagetable_t pagetable, uint64 va)
// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
+
+/*copyout准备写用户地址 dstva
+ ↓
+找到 dstva 对应的 PTE
+ ↓
+ 是不是 COW?
+ / \
+ 不是 是
+ ↓ ↓
+ 直接写 kalloc
+ ↓
+ 复制旧页
+ ↓
+ PTE → 新页
+ ↓
+ W=1 COW=0
+ ↓
+ kfree(oldpa)
+ ↓
+ 再写数据
+*/
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
uint64 n, va0, pa0;
+ uint64 oldpa;
+ uint flags;
+ pte_t *pte;
+ char *mem;

while(len > 0){
+ if(dstva >= MAXVA)
+ return -1;
va0 = PGROUNDDOWN(dstva);
- pa0 = walkaddr(pagetable, va0);
+ //pa0 = walkaddr(pagetable, va0);
+ pte = walk(pagetable, va0, 0);
+
+ if(pte == 0)
+ return -1;
+
+ if((*pte & PTE_V) == 0)
+ return -1;
+
+ if((*pte & PTE_U) == 0)
+ return -1;
+
+
+
+ if (*pte & PTE_COW)
+ {
+ // 保存旧物理页
+ oldpa = PTE2PA(*pte);
+
+ // 保存原来的 flags
+ flags = PTE_FLAGS(*pte);
+
+ // 分配新的物理页
+ mem = kalloc();
+ if(mem == 0)
+ return -1;
+
+ // 复制旧页内容
+ memmove(mem, (char *)oldpa, PGSIZE);
+
+ // 新页面允许写,并取消 COW
+ flags |= PTE_W;
+ flags &= ~PTE_COW;
+
+ // 当前进程的 PTE 改为指向新页
+ *pte = PA2PTE((uint64)mem) | flags;
+
+ // 当前 PTE 不再引用旧页
+ kfree((void *)oldpa);
+ }
+
+ if ((*pte & PTE_W) == 0)
+ return -1;
+
+ // 在 COW 处理之后获取 pa
+ pa0 = PTE2PA(*pte);
+
if(pa0 == 0)
return -1;
n = PGSIZE - (dstva - va0);

总体上来说是跟着reasonable plan of attack和hints来写。

1.修改uvmcopy

我们要对cow页面单独设置一个PTE的标志位进行标记进行一些判断处理。核心的函数功能就是要用mappages将子进程的pte映射到父进程的物理页,这个也是cow的核心功能之一。
我们还要对相应的物理页面进行引用计数的操作,方便kfree等函数对页面的处理。

2.修改usertrap

在usertrap中,我们可以使用r_scause() == 15来判定trap是否为page fault。如果*pte & PTE_COW条件成立,则说明是cow页面,需要根据写时复制的规则新分配一个物理页面,然后将子进程的pte映射到新页面中。

3.维护物理页面的引用计数

主要是对ref结构体的一些操作,以及对kfree,kalloc等函数做一些额外的判断和操作。

4.修改copyout

在之前我们知道了copyout的作用是让用户程序访问内核数据的拷贝。copyout写到dstva时我们要判断这个地址所在的物理页面是否为cow页面,如果不是则直接写,如果是的话就要按照cow的规则进行处理。

5.hints

主要是usertests里的一系列测试要求对地址空间的限定条件比较严格,所以要加很多的条件分支来限定函数的行为,确保不会地址越界。


xv6
http://example.com/2026/06/11/xv6/
作者
myslqyr
发布于
2026年6月11日
许可协议