MIT 6.s081 Lab1: Xv6 and Unix utilities

这个 lab 是用来熟悉 xv6 和它的系统调用的,就是用它的系统调用来实现一些小工具。在实现之后要将程序加入 Makefile 里的 UPROGS 中才能执行。

sleep (eazy)

就是调用已经实现的系统调用 sleep (具体实现在 sysproc.c#sys_sleep 中 ) 来实现即可,非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...

int
main(int argc, char *argv[])
{
if (argc < 2) {
// 没有指定参数提示错误信息
printf("must enter a sleep time!\n");
exit(0);
}
int time = atoi(argv[0]); // 使用工具函数 atoi 将字符串转化成数字
sleep(time); // 调用系统调用
exit(0);
}

pingpong (eazy)

使用 pipe 系统调用创建管道,用 fork 系统调用创建子进程,然后两个进程使用这个管道进行通信,使用 read/write 系统调用对 pipe 进行读写即可;

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
// ...

int
main(int argc, char *argv[])
{
// 创建两个管道
int send[2], recv[2];
if (pipe(send) < 0 || pipe(recv) < 0) {
printf("there is a problem when create a pipe!\n");
exit(0);
}

if (!fork()) {
// pid 为 0,即是子进程
char c;
read(send[0], &c, 1); // 阻塞在这里直到父进程向管道中写入
int pid = getpid();
printf("%d: received ping\n", pid);
write(recv[1], "a", 1); // 向父进程发送消息,阻塞直到父进程读取消息
} else {
write(send[1], "a", 1); // 向子进程发送消息
char c;
read(recv[0], &c, 1); // 阻塞,直到收到子进程的消息
int pid = getpid();
printf("%d: received pong\n", pid);
}
exit(0);
}

primes (moderate/hard)

这个是利用 fork 和 pipe 来实现并发的处理质数(除了 1 和 它本身,不能被整除的数)。一开始我还没有懂什么意思,看了这张图之后还是没有搞懂:

后来看到一个 golang 版本的实现才明白是什么意思,也就是 root 父进程向一个通道中发送 2-35,并且 fork 出一个子进程,将这个通道传递给子进程,子进程从管道中读取出一个数,比如当前情况下这个数就是 2,它是一个质数(通道中的第一个数一定是质数,因为它没有被任何比它小的数给过滤掉),那么就打印。

接着这个子进程,再创建一个通道并 fork 一个子进程,然后将父进程通道中剩下的 3-35 都对刚刚取出来的质数 2 取模,如果不等于 0,表示起码不能被 2 整除,就将它放到新创建的通道中给它的子进程处理(看看它能不能被剩下的数整除)。

循环往复直到有一个子进程在通道里读不出东西了,这个时候表示 2-35 的所有质数都被找到了,就返回。要注意的是,每个进程都要等待它的子进程退出之后才能退出,所以要调用 wait 等待子进程退出。并且对于不再使用的通道要及时将其关闭,否则程序算不到 35 就会使 xv6 资源不足。关闭通道分关闭读端和写端,父进程对自己创建的通道只需要写而不需要读,所以它可以将读端先关闭,写完之后再将写端关闭,而子进程对于收到的通道只需要读不需要写,所以在收到之后,它可以先关闭写端,读完后再关闭读端。父进程不能再创建完后就关闭,而要在 fork 之后再关闭,因为如果先关闭了,那么子进程再持有到这个通道的文件描述符后,会发现通道的读端是关闭,无法读取数据,会被阻塞(卡了我好久)。

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
// ...

void processor(int *pipeline)
{

close(pipeline[1]);
int status;
int prime;
int read_len = read(pipeline[0], &prime, sizeof(int));
if (read_len == 0)
{
close(pipeline[0]);
return;
}
printf("prime %d\n", prime);
int out[2];
pipe(out);
if (!fork())
{
processor(out);
exit(0);
}
else
{
close(out[0]);
int num;
while (read(pipeline[0], &num, sizeof(int)) != 0)
{
if (num % prime != 0)
{
write(out[1], &num, sizeof(num));
}
}
close(out[1]);
close(pipeline[0]);
wait(&status);
}
}

int main(int argc, char *argv[])
{
int status;
int pipeline[2];
pipe(pipeline);
if (!fork())
{
processor(pipeline);
exit(0);
}
else
{
close(pipeline[0]);
int num = 2;
while (1)
{
write(pipeline[1], &num, sizeof(num));
num++;
if (num > 35)
{
close(pipeline[1]);
break;
}
}
wait(&status);
}
exit(0);
}

find (moderate)

find 是用来找出在文件夹中所有的指定名称的文件。

可以参考 user/ls.c 来看如何读取文件夹,并且使用递归来读取子文件夹(不用递归 “.” 和 “..” ,否则会爆栈)。使用 strcmp() 来比较字符串。

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
// ...

// 从文件路径中将文件名解析出来,对 ls 中的版本做了修改,去掉了填充
char *
fmtname(char *path)
{
char *p;
for (p = path + strlen(path); p >= path && *p != '/'; p--)
;
p++;
return p;
}

void find(char *path, char *filename)
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;

if ((fd = open(path, 0)) < 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;
}
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf)
{
printf("ls: path too long\n");
}
strcpy(buf, path);
p = buf + strlen(buf);
*p++ = '/';
while (read(fd, &de, sizeof(de)) == sizeof(de))
{
if (de.inum == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if (stat(buf, &st) < 0)
{
printf("ls: cannot stat %s\n", buf);
continue;
}
// st.type 为 2 就是文件,使用 strcmp 作比较
if (st.type == 2)
{
if (!strcmp(fmtname(buf), filename))
{
printf("%s\n", buf);
}
}
// st.type 为 1 就是文件夹,判断是否需要递归
else if (st.type == 1)
{
if (!strcmp(de.name, ".") || !strcmp(de.name, ".."))
{
continue;
}
find(buf, filename);
}
}
close(fd);
}

int main(int argc, char *argv[])
{
if (argc < 3)
{
exit(0);
}
// 将路径和文件名传入
find(argv[1], argv[2]);
exit(0);
}

xargs (moderate)

xargs 从标准输入中读取数据,将每一行当作参数添加到 xargs 后面的程序中当作参数,比如:

echo hello too | xargs echo bye -> echo bye hello too.

| 符号表示通过管道将数据发送到程序中,所以我们读数据的时候只要使用 read 系统调用在标准输出中读取就可以了,标准输出的 fd 编号为 0。

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
// ...

#define MAXARGS 10

int main(int argc, char *argv[])
{
int buf_idx, read_len;
int status;
char buf[512];
char* args[MAXARGS];

int i;

for (i = 1; i < argc; i++) {
args[i-1] = argv[i];
}

// 比如 find . b | xargs grep hello 就有可能传入多行数据
while (1)
{
buf_idx = 0;
while (1)
{
// 从标准输出中读取数据到 buf 里,一次读取一个字符
read_len = read(0, &buf[buf_idx], sizeof(char));
// 如果读到了 '\n' 或者已经没有数据了,就停止
if (read_len <= 0 || buf[buf_idx] == '\n') {
break;
}
buf_idx++;
}


if (read_len == 0) {
break;
}

// 加入字符串结束的标识
buf[buf_idx] = '\0';
args[argc-1] = buf;
// fork 出一条子进程来执行
if (!fork()) {
exec(args[0], args);
exit(0);
} else {
wait(&status);
}
}

exit(0);
}

执行结果