0%

xv6-2020-lab1解析

This lab will familiarize you with xv6 and its system calls.

这个实验能让你熟悉xv6和它的系统调用。

Boot xv6(简单)

直接从GitHub上克隆下对应的仓库,切换到指定分支就可以了。该实验还贴心地提供了git的用户手册和面向CS人员的简单手册

然后就可以直接在该分支上面进行编译,然后运行qemu来运行xv6这个虚拟机了。

确保能成功启动xv6。

sleep(简单)

编写一个程序叫做sleep,它接受一个数字作为输入,系统会延迟指定的时间。这个实验的主要目的是为了让你体验一下,所以本质上其实直接去调用封装好的sleep函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* impl sleep(int n)
*/
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

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

// make sure the argc == 2 which means the input is like sleep 10
if (argc != 2)
{
fprintf(2, "Usage:sleep <n>");
exit(-1);
}

// get the sleep time
sleep_time = atoi(argv[1]);
sleep(sleep_time);
exit(0);
}

可以看到,由于已经做好了函数的封装,所以这个程序实现起来非常简单,在user下创建好sleep.c,并且把它加入到MakeFile中即可。

pingpong(简单)

用一对管道来实现父子进程之间的双向通信。

这个程序比sleep稍微复杂那么一点点,需要用到多个系统调用,包括readpipefork这些,不过基本上和平时写的管道是一模一样的。

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

#define MAX 100
#define READEND 0
#define WRITEEND 1

int main(int argc, char *argv[])
{
int p1[2]; // child send msg to parent
int p2[2]; // parent send msg to child
int pid;
int parent_pid = getpid();
char buf[MAX];
pipe(p1);
pipe(p2);

pid = fork();
if (pid > 0)
{
// parent process
close(p1[WRITEEND]);
close(p2[READEND]);
write(p2[WRITEEND], "ping", strlen("ping"));
read(p1[READEND], buf, 4);
printf("%d: received pong\n", pid);
close(p1[READEND]);
close(p2[WRITEEND]);
}
else
{
// child process
close(p1[READEND]);
close(p2[WRITEEND]);
read(p2[READEND], buf, 4);
printf("%d: received ping\n", parent_pid);
write(p1[WRITEEND], "pong", strlen("pong"));
close(p1[WRITEEND]);
close(p2[READEND]);
}
exit(0);
}

primes(困难)

利用fork来实现素数筛。

素数筛的概念很简单,对从2(包括2)开始所有的数字,首先打印出第一个数字(这个数字必然是素数,因为它已经经历过前面所有的轮次了),然后以记录下这个数字,开始去除剩下的所有数字,如果能整除,那么这个数字就去掉,否则就保留到下一轮。

这里推荐一篇写得非常好的文章:https://blog.eastonman.com/blog/2020/11/xv6-primes/

find(中等)

首先我们肯定需要能够遍历所有的文件,这点可以参考ls.c是怎么实现的。

其次是必然会用到递归。

这里我觉得最最重要的还是,学到了文件系统的皮毛:

1
2
3
4
struct dirent {
ushort inum;
char name[DIRSIZ];
};

这个就是文件夹中每一条目录的抽象,本质上就是一个inode+文件名字而已。所以文件夹其实就是一个文件,只不过里面的内容是一条一条的dirent而已。

xargs(中等)

实现一个xargs,它能够从标准输入中获取数据,然后将标准输入中获取到的数据作为后面紧跟着的命令的命令行参数。

从标准输入中读取数据很简单,那么其实就变成了把从标准输入中得到的参数加上原来就有的参数,将这两部分组合而成一个命令行参数列表,并且利用exec来执行就可以了。

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

// xarg从标准输入中最多接受的字节
#define MAX 512

int main(int argc, char *argv[])
{
int count;
char buf[MAXARG * MAXARG];
// 为了避免重名
char args[MAXARG][MAXARG];
char *cmd[MAXARG];
char *p = buf;
char *start = p;
int i = 0;
int j = 0;
int k = 0;
int index = 0;
// read from standard input
if ((count = read(0, buf, 512)) > 0)
{
buf[count] = 0;
// 每个空格或者是回车就是一个参数,这里忽略了什么制表符之类的
while (*p != 0)
{
while (*p != ' ' && *p != '\n')
{
p++;
}
// 为了复制
for (j = 0; start != p; start++, j++)
{
args[i][j] = *start;
}
args[i][++j] = 0;
p++;
i++;
start = p;
}
}

// 首先把原来自带的那些参数加入
for (j = 1; j < argc; j++)
{
cmd[index++] = argv[j];
}

// 然后把从标准输入中获取到的、处理好的参数也加入其中
for (k = 0; k < i; k++)
{
cmd[index++] = args[k];
}

if (fork())
{
// parent
wait(0);
}
else
{
exec(argv[1], cmd);
}

exit(0);
}

总结

第一部分就是简单让你理解一下xv6这个系统,然后帮助你复习一下C语言,能够从现有的一些实现中变化出更多的实现。