
首页

归档

关于

友链

存档点

xv6_2025

文章目录

  1. 1. Lab Utilities
    1. 1.1. Task 1: sleep
    2. 1.2. Task 2: sixfive
    3. 1.3. Task 3: memdump
    4. 1.4. Task 4: find
    5. 1.5. Task 5: exec
  2. 2. Lab System calls
z0z0r4
z0z0r4
文章
32
分类
21
标签
25

首页

归档

关于

友链

存档点
2026-08-24 2026-10-13

2026-07-31 至 2026-08-11 期间完成 2025 年的 MIT 6.1810 的 xv6 课程。

MIT 6.1810 之前叫做 MIT 6.S081,课程围绕 xv6 讲操作系统。

  • 课程网站:https://pdos.csail.mit.edu/6.828/2025/schedule.html
  • CSDIY: <csdiy.wiki/操作系统/MIT6.S081>
  • 我的实现:https://github.com/z0z0r4/xv6-labs-2025

xv6 是一个教学目的简化的 RISC-V 操作系统,提供完整的仓库和手册。

CSDIY 上面有许多参考实现和前人的笔记,但是由于记录都比较老,在 2025 年课程已经有部分更新,遂在此提供一份。

我没有看过课程的视频和官方笔记,在做 Lab 之前花了一周时间看 OSTEP (Operating Systems: Three Easy Pieces),这也是一本著名教材。

Lab 主要有以下:

  • Utilities
  • System calls
  • Page tables
  • Traps
  • Copy on-write
  • Network driver
  • Lock
  • File System
  • MMAP

按照 Tools 安装好必要的工具之后,记得检查 Guidance 中后续要用到的 RISCV GDB 调试时否可用。


Lab Utilities

首先克隆 xv6-labs-2025 仓库。

1
2
git clone git://g.csail.mit.edu/xv6-labs-2025
cd xv6-labs-2025

如果 Tools 安装没问题,使用 make qemu 试着编译运行,应该得到以下输出。

1
2
3
4
5
6
7
8
9
10
make qemu

...

xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
$

试着 ls 可以列出所有能用的程序。

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
$ ls
. 1 1 1024
.. 1 1 1024
README 2 2 2425
findtest.sh 2 3 91
sixfive.txt 2 4 19
cat 2 5 36424
echo 2 6 35288
forktest 2 7 17744
grep 2 8 43880
init 2 9 35672
kill 2 10 35208
ln 2 11 35008
ls 2 12 42576
mkdir 2 13 35264
rm 2 14 35256
sh 2 15 58008
stressfs 2 16 36128
usertests 2 17 194440
grind 2 18 51504
wc 2 19 37416
zombie 2 20 34536
logstress 2 21 37240
forphan 2 22 36032
dorphan 2 23 35488
memdump 2 24 36584
console 3 25 0

接下来可以正式进入正题,试着实现以下的用户程序。

  • sleep
  • sixfive
  • memdump
  • find
  • exec

基本上,我们在接下来的过程中,时不时就会去翻阅 xv6 的各处源代码。主要分为 kernel 和 user 两个部分。user 部分调用各种系统调用,然后再 kernel 里面实现这些系统调用。在当前 Util Lab 还不需要去实现,只需要调用就行了。

你可以在 user/user.h 中找到需要的调用:

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
#define SBRK_ERROR ((char *)-1)

struct stat;

// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(const char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sys_sbrk(int,int);
int pause(int);
int uptime(void);

// ulib.c
int stat(const char*, struct stat*);
char* strcpy(char*, const char*);
void *memmove(void*, const void*, int);
char* strchr(const char*, char c);
int strcmp(const char*, const char*);
char* gets(char*, int max);
uint strlen(const char*);
void* memset(void*, int, uint);
int atoi(const char*);
int memcmp(const void *, const void *, uint);
void *memcpy(void *, const void *, uint);
char* sbrk(int);
char* sbrklazy(int);

// printf.c
void fprintf(int, const char*, ...) __attribute__ ((format (printf, 2, 3)));
void printf(const char*, ...) __attribute__ ((format (printf, 1, 2)));

// umalloc.c
void* malloc(uint);
void free(void*);

除此之外,我们没有 libc。但好在 user/ulib.c 已经提供了不少有用的函数。如果你觉得自己缺少某个方法,很可能只是没注意到它在哪里藏着,去看看 user/user.h 里面 ulib.c 的部分!

每个 Task 提供的 Some hints 实际上一定包含了~~几乎~~所有你需要的提示,仔细看看这些提示(不要遗漏)能事半功倍的完成。虽然如此,但是 Hints 也不少,依旧需要你翻看源码!

小心注意什么情况下失败的返回值是 -1,什么情况下是 0。你最好仔细阅读注释(很可能不存在)和源码,并适当补充注释,以免写错。

Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.

如果没有在 ls 看到你的程序,很可能是你忘了添加到 Makefile 的 UPROGS 里面!


Task 1: sleep

Use the system call pause().

使用系统调用 pause() 。

所以只需要看看 pause 的声明是:

1
int pause(int);

实际上这是一个系统调用,你可以试着找 sys_pause 找到具体实现。关于 pause 如何变成 sys_pause,你将在下一个 Lab syscall 中了解到(或者自己去找)。

所以只需要按照如下获取参数、完成调用即可:

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

int main(int argc, char *argv[])
{
int duration;

if (argc <= 1)
{
fprintf(2, "sleep: missing duration\n");
exit(1);
}

duration = atoi(argv[1]);

if (duration < 0)
{
fprintf(2, "sleep: negative duration\n");
exit(1);
}

pause(duration);
exit(0);
}

Task 2: sixfive

sixfive 需要读取文件中的文本,输出 5 或 6 的倍数。这意味着我们需要找到用来读取文件的系统调用。

你需要 open、read、close,这些系统调用的声明都在 user/user.h 中。

但是实际上你很可能需要去看手册或者去翻源码,以确认如何传递参数。

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

char buf[512];

char *seps = " -\r\t\n./,";

void sixfive(int fd) {
char buf[512];
int n;
int in_num = 0;
long num = 0;

while ((n = read(fd, buf, sizeof(buf))) > 0) {
for (int i = 0; i < n; i++) {
char c = buf[i];

if (strchr(seps, c)) {
if (in_num) {
if (num % 5 == 0 || num % 6 == 0) {
printf("%ld\n", num);
}
in_num = 0;
num = 0;
}
} else if (c >= '0' && c <= '9') {
in_num = 1;
num = num * 10 + (c - '0');
} else {
in_num = 0;
num = 0;
}
}
}

// 处理文件末尾以数字结尾的情况
if (in_num) {
if (num % 5 == 0 || num % 6 == 0) {
printf("%ld\n", num);
}
}
}

int main(int argc, char *argv[])
{
int fd;

if (argc <= 1)
{
fprintf(2, "sixfive: missing file\n");
exit(1);
}

for (int i = 1; i < argc; i++)
{
if ((fd = open(argv[i], O_RDONLY)) < 0)
{
fprintf(2, "sixfive: cannot open %s\n", argv[i]);
exit(1);
}
sixfive(fd);
close(fd);
}

exit(0);
}

Task 3: memdump

memdump 需要输出内存中的内容。你需要使用 printf。

参考 xv6 中的 printf 实现,找到所需的格式化。(其实可能是我不知道如何使用 printf haha)

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

#include <stdarg.h>

static char digits[] = "0123456789ABCDEF";

static void
putc(int fd, char c)
{
write(fd, &c, 1);
}

static void
printint(int fd, long long xx, int base, int sgn)
{
char buf[20];
int i, neg;
uint x;

neg = 0;
if(sgn && xx < 0){
neg = 1;
x = -xx;
} else {
x = xx;
}

i = 0;
do{
buf[i++] = digits[x % base];
}while((x /= base) != 0);
if(neg)
buf[i++] = '-';

while(--i >= 0)
putc(fd, buf[i]);
}

static void
printptr(int fd, uint64 x) {
int i;
putc(fd, '0');
putc(fd, 'x');
for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4)
putc(fd, digits[x >> (sizeof(uint64) * 8 - 4)]);
}

// Print to the given fd. Only understands %d, %x, %p, %c, %s.
void
vprintf(int fd, const char *fmt, va_list ap)
{
char *s;
int c0, c1, c2, i, state;

state = 0;
for(i = 0; fmt[i]; i++){
c0 = fmt[i] & 0xff;
if(state == 0){
if(c0 == '%'){
state = '%';
} else {
putc(fd, c0);
}
} else if(state == '%'){
c1 = c2 = 0;
if(c0) c1 = fmt[i+1] & 0xff;
if(c1) c2 = fmt[i+2] & 0xff;
if(c0 == 'd'){
printint(fd, va_arg(ap, int), 10, 1);
} else if(c0 == 'l' && c1 == 'd'){
printint(fd, va_arg(ap, uint64), 10, 1);
i += 1;
} else if(c0 == 'l' && c1 == 'l' && c2 == 'd'){
printint(fd, va_arg(ap, uint64), 10, 1);
i += 2;
} else if(c0 == 'u'){
printint(fd, va_arg(ap, uint32), 10, 0);
} else if(c0 == 'l' && c1 == 'u'){
printint(fd, va_arg(ap, uint64), 10, 0);
i += 1;
} else if(c0 == 'l' && c1 == 'l' && c2 == 'u'){
printint(fd, va_arg(ap, uint64), 10, 0);
i += 2;
} else if(c0 == 'x'){
printint(fd, va_arg(ap, uint32), 16, 0);
} else if(c0 == 'l' && c1 == 'x'){
printint(fd, va_arg(ap, uint64), 16, 0);
i += 1;
} else if(c0 == 'l' && c1 == 'l' && c2 == 'x'){
printint(fd, va_arg(ap, uint64), 16, 0);
i += 2;
} else if(c0 == 'p'){
printptr(fd, va_arg(ap, uint64));
} else if(c0 == 'c'){
putc(fd, va_arg(ap, uint32));
} else if(c0 == 's'){
if((s = va_arg(ap, char*)) == 0)
s = "(null)";
for(; *s; s++)
putc(fd, *s);
} else if(c0 == '%'){
putc(fd, '%');
} else {
// Unknown % sequence. Print it to draw attention.
putc(fd, '%');
putc(fd, c0);
}

state = 0;
}
}
}

void
printf(const char *fmt, ...)
{
va_list ap;

va_start(ap, fmt);
vprintf(1, fmt, ap);
}

可见我们需要 %d、%lx、%c、%s,要注意转换类型。

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
void
memdump(char *fmt, char *data)
{
while(*fmt) {
switch (fmt[0]) {
case 'i':
printf("%d\n", *(int *)data);
data += sizeof(int);
break;

case 'p':
printf("%lx\n", *(uint64 *)data);
data += sizeof(void *);
break;

case 'h':
printf("%d\n", *(short *)data);
data += sizeof(short);
break;

case 'c':
printf("%c\n", *(char *)data);
data += sizeof(char);
break;

case 's':
printf("%s\n", *(char **)data);
data += sizeof(char *);
break;

case 'S':
printf("%s\n", data);
return; // Stop after printing the string
}
fmt++;
}
}

我一开始没看明白,p 当成打印指针了。得用 uint64 和 %lx 才能打印出正确的值。

Task 4: find

和 Linux 中的 find 类似,find 需要递归地遍历目录,找到指定的文件。

需要参考 ls 的实现学习如何遍历目录。

你应该使用 open、fstat、read、close 调用。

open 后需要通过 fstat 判断是目录、文件、还是设备(如果是设备则忽略了):

  • 如果是 T_DIR,应该递归调用 find。记得跳过 . 和 .. 目录。

  • 如果是 T_FILE,则判断文件名是否匹配,如果匹配则输出。

Task 5: exec

这部分指的是对于每找到一个匹配的文件,如果有 -exec 参数,则将文件名放在下一个参数,然后执行这个命令。你需要使用 fork、exec、wait 系统调用。

比如 find . wc -exec echo hi 的行为是直接执行 echo hi ./wc(注意不需要再输出文件名!)。

为了实现,当然你需要理解 fork 和 wait 的行为:fork 会创建一个子进程,子进程会从 fork 返回 0,而父进程会从 fork 返回子进程的 PID。wait 会阻塞父进程,直到子进程结束。

子进程会直接从 fork 的下一行开始执行,并不是从头开始执行程序。(因为它是父进程的一个拷贝)

你还需要在执行完 exec 后,让子进程 exit!

你还需要小心不要让参数太多了!

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

char *
fmtname(char *path)
{
static char buf[DIRSIZ + 1];
char *p;

// Find first character after last slash.
for (p = path + strlen(path); p >= path && *p != '/'; p--)
;
p++;

// Return blank-padded name.
if (strlen(p) >= DIRSIZ)
return p;
memmove(buf, p, strlen(p));
memset(buf + strlen(p), ' ', DIRSIZ - strlen(p));
buf[sizeof(buf) - 1] = '\0';
return buf;
}

void find(char *path, char *filename, int enable_exec, char *exec_cmd, int exec_argc, char *exec_argv[])
{
int fd;
struct dirent de;
struct stat st;

if ((fd = open(path, O_RDONLY)) < 0)
{
fprintf(2, "find: cannot open %s\n", path);
return;
}

if (fstat(fd, &st) < 0)
{
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}

switch (st.type)
{
case T_DEVICE:
case T_FILE:
{
char *p;
for (p = path + strlen(path); *p != '/' && p >= path; p--)
;
p++;

if (strcmp(p, filename) == 0)
{
if (enable_exec)
{
int pid = fork();
if (pid == 0)
{
char *new_exec_argv[MAXARG];
int idx;
for (idx = 0; idx < exec_argc; idx++)
{
new_exec_argv[idx] = exec_argv[idx];
}

new_exec_argv[idx] = path;
new_exec_argv[idx + 1] = 0;
exec(exec_cmd, new_exec_argv);

fprintf(2, "find: exec failed for %s\n", exec_cmd);
exit(1);
}
else if (pid > 0)
{
wait(0);
}
else
{
fprintf(2, "find: fork failed\n");
}
}
else
{
printf("%s\n", path);
}
}
break;
}
case T_DIR:
while (read(fd, &de, sizeof(de)) == sizeof(de))
{
if (de.inum == 0)
{
continue;
}

if (strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
{
continue;
}

char child_full_path[512];
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof(child_full_path))
{
fprintf(2, "find: path too long\n");
continue;
}
strcpy(child_full_path, path);
char *p = child_full_path + strlen(path);
*p++ = '/';
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = '\0';
find(child_full_path, filename, enable_exec, exec_cmd, exec_argc, exec_argv);
}
break;
}
close(fd);
}

int main(int argc, char *argv[])
{
if (argc < 2)
{
fprintf(2, "find: missing path\n");
exit(1);
}
else if (argc < 3)
{
fprintf(2, "find: missing filename\n");
exit(1);
}

int enable_exec = 0;
char *exec_cmd = "";
char *exec_argv[MAXARG];
int exec_argc = 0;

for (int i = 0; i < MAXARG; i++)
{
exec_argv[i] = 0;
}

if (argc >= 4 && strcmp(argv[3], "-exec") == 0)
{
enable_exec = 1;

if (argc >= 5)
{
char *cmd_name = argv[4];
static char cmd_path[512];
strcpy(cmd_path, "/");
memmove(cmd_path + strlen(cmd_path), cmd_name, strlen(cmd_name) + 1);
exec_cmd = cmd_path;

exec_argv[exec_argc++] = cmd_name;

for (int i = 5; i < argc; i++)
{
exec_argv[exec_argc++] = argv[i];

// MAXARG - 2 多为后续的 filename 留下空间
if (exec_argc >= MAXARG - 2)
{
break;
}
}

// NULL 终止
exec_argv[exec_argc] = 0;
}
}

find(argv[1], argv[2], enable_exec, exec_cmd, exec_argc, exec_argv);
exit(0);
}

至此已经完成了这个 Lab,当然除此之外你还可以完成哈如 Optional challenge exercises,例如实现 uptime 和让 find 支持基本的正则表达式,或者更进一步,完善 sh。

Lab System calls

从现在开始往后,会实现很多不同的系统调用。Linux 中的系统调用有将近快四百个系统调用。

To Be Continued…

study-notesCSxv6MIT 6.S081MIT 6.1810
  • MIT 6.1810
  • MIT 6.S081
  • study-notes
  • xv6
CS144 Check0
前一篇

CS144 Check0

CS300 Project 5 KVstore
后一篇

CS300 Project 5 KVstore

说些什么吧!

giscus
Creative Commons License All website licensed under CC BY 4.0
2025-2026 z0z0r4
基于 Hexo  Theme.Reimu
136.2k  |  11:03
粤ICP备2025511811号
粤公网安备44130302100361号
总访问量   |  总访客量 

文章目录

  1. 1. Lab Utilities
    1. 1.1. Task 1: sleep
    2. 1.2. Task 2: sixfive
    3. 1.3. Task 3: memdump
    4. 1.4. Task 4: find
    5. 1.5. Task 5: exec
  2. 2. Lab System calls
z0z0r4
z0z0r4
文章
32
分类
21
标签
25

首页

归档

关于

友链

存档点