本文记录了 Labs:Xv6 and Unix utilities 的实验过程

实验链接

启动 xv6

拉取 xv6 的源码(如果已经 clone 了源码则跳过这一步)

1
git clone git://g.csail.mit.edu/xv6-labs-2020

进入该 Lab 的分支

1
2
cd xv6-labs-2020
git checkout util

build 以及 运行 xv6

1
make qemu

xv6 没有 ps(列出系统中当前运行的进程) 命令;但可以使用 Ctrl-p,内核将打印进程的信息。

退出 qemu

1
Ctrl-a x

Grading 和 hand-in 程序

  • 运行 make grade(该命令会测试该 Lab 下的所有程序) 将使用 grading 程序测试你的代码。如果只是想测试一个程序,可以使用如
1
./grade-lab-util sleep

注:需要安装 python3,sudo apt-get install python-is-python3

  • 使用 make handin 来提交你的 Lab。

1、Sleep (easy)

思路

读取命令行中 sleep 程序后面的参数,使用已提供的 atoi 函数将参数转化为整数,然后执行 sleep 系统调用。

实验步骤

  • user 目录下添加 sleep.h 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "kernel/tpes.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
int n;
if (argc != 2){ // forgets to pass an argument, sleep should print an error message.
fprintf(2, "please give sleep process a argument.\n");
exit(1);
}
n = atoi(argv[1]); // 将字符串转化为整数
sleep(n);
exit(0); // 结束进程
}
  • 然后在 xv6-labs-2020 目录的 Makefile 中找到 UPROGS,添加 $U/_sleep\。回到 xv6-labs-2020 目录,输入./grade-lab-util sleep 查看能否通过测试

2、pingpong(easy)

思路

一个进程可以通过系统调用 fork 来创建一个新的进程。fork 创建的新进程被称为子进程,子进程的内存内容同创建它的进程(父进程)一样。fork 函数在父进程、子进程中都返回(一次调用两次返回)。对于父进程它返回子进程的 pid,对于子进程它返回 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
#include "kernel/tpes.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(void) // int argc, char *argv[])
{
char buf[512];

// 由于管道为半双工通信,因为为了让父子进程互相通信,需要创建两个管道
int fd1[2], fd2[2]; // [0] is read, and [1] is write

// 创建两个管道,父进程在 fp1 中写,子进程在 fp2 中写
pipe(fd1);
pipe(fd2);

int pid = fork(); // 创建子进程

if (pid < 0) {
fprintf(2, "fork error.\n");
exit(1);
}

if (pid == 0) { // 如果当前 pid 为 0,说明正在执行子进程
read(fd1[1], buf, 1); // 等待父进程向 fd1 中写数据
fprintf(1, "%d: received ping\n", getpid());
write(fd2[0], buf, 1);
exit(0); // exit() 会释放子进程的文件资源
} else {
write(fd1[0], buf, 1);
read(fd2[1], buf, 1); // 父进程等待子进程向 fd2 中写数据
wait(0); // 等待子进程结束
fprintf(1, "%d: received pong\n", getpid());
}
exit(0);
}

3、primes(moderate)/(hard)

思路

首先在父进程中,我们向管道写入数 2~35,然后由子进程执行埃氏筛法。

实验过程

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

void solution(int fd[])
{
int prime;
// 当父进程的管道为空时,结束运行
if(read(fd[0], &prime, sizeof(prime)) == 0){
exit(0);
}
printf("prime %d\n", prime);

int child_fd[2];
pipe(child_fd);

if (fork() == 0){ // 创建子进程
close(child_fd[1]); // 关闭子进程管道的写端
solution(child_fd);
}else{
close(child_fd[0]);
int num;
// 当父进程的管道不为空时,从管道读取一个数,然后筛选它
while(read(fd[0], &num, sizeof(num)) != 0){
if(num % prime != 0){
write(child_fd[1], &num, sizeof(num));
}
}
close(child_fd[1]);
}
wait(0);
exit(0);
}

int
main(void)
{
int fd[2]; // fd[0] -> read; fd[1] -> write
pipe(fd);

int x = 2;

if (fork() == 0){ // 子进程
close(fd[1]); // 关闭子进程在管道的写端
solution(fd);
}else{
close(fd[0]); // 关闭父进程在管道的读端
for (; x <= 35; x++){
write(fd[1], &x, sizeof(x));
}
close(fd[1]); // 写入完毕,关闭父进程在管道的写端
}
wait(0); // 等待子进程结束
exit(0);
}

4、find (moderate)

思路

理解 user/ls.c 中的代码,然后修改一下即可。

实验过程

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

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

if((fd = open(path, 0)) < 0){
return;
}

if(fstat(fd, &st) < 0){
close(fd);
return;
}

switch(st.type){
case T_FILE:
break;

case T_DIR:
strcpy(buf, path);
p = buf + strlen(buf);
*p++ = '/'; // buf is path + "/"
while(read(fd, &de, sizeof(de)) == sizeof(de)){
if(de.inum == 0)
continue;
// 跳过 "." 和 ".." 目录,避免死循环
if (strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
// 比较判断是不是我们要找的 name
if (strcmp(name, de.name) == 0) {
printf("%s\n", buf);
}
// 递归搜索子目录
find(buf, name);
}
break;
}
close(fd);
}


int
main(int argc, char *argv[])
{
if(argc < 3){
exit(0);
}

find(argv[1], argv[2]);

exit(0);
}

5、xargs (moderate)

  • |:管道命令,是将左侧命令的标准输出转换为标准输入,提供给右侧命令作为参数。例如
1
echo hello too | xargs echo bye

将左侧的标准输入,转为命令行参数 hello too,传给第二个 echoxargs命令的作用,是将标准输入转为命令行参数。

思路

由于 | 左边的参数在标准输出中,因此我们需要按空格从标准输入中读取各个参数保存起来。拼接到 | 右边的参数中。然后调用 exec 执行 xargs 后面的命令。

实验过程

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

#define MAX 512

int
main(int argc, char *argv[])
{
char *args[MAXARG];
char buf[MAX];
int args_idx = 0;

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

int n;
// when meet the ctlr + d, namely n < 0, exit
while ((n = read(0, buf, MAX)) > 0){
if (fork() == 0){
// 申请一块内存来存放各个参数,用二维数组也可以
// each parameter in left part of '|', separate by ' '
char *para = (char*)malloc(sizeof(buf));
int idx = 0;
for (int i = 0; i < n; i++){
if (buf[i] == ' ' || buf[i] == '\n'){
para[idx] = 0; // 字符串结束符
args[args_idx++] = para; // 将当前参数保存到 args 中
idx = 0;
para = (char*)malloc(sizeof(buf));
}else{
para[idx++] = buf[i];
}
}
args[args_idx] = 0;
exec(args[0], args);
}else{ // 父进程
wait(0);
}
}
exit(0);
}

测试

1
make grade