0%

Xv6 Lab1 Note

xv6实验一请看这里

sleep

实现一个sleep命令,达到睡眠效果

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

int main(int argc, const char *argv[]) {
if (argc != 2) {
fprintf(2, "usage: sleep <time>\n");
exit(1);
}

fprintf(1, "%d - %s %s\n", argc, argv[0], argv[1]);

// atoi函数可以将字符转换成数字
sleep(atoi(argv[1]));
exit(0);
}

这里很简单,直接系统调用sleep()

pingpong

要求使用pipe管道传输一个字符,其实就是学习pipe管道的用法,如何创建一个管道
pipe(int* fd), 要求传入一个数组,其会返回两个file description
第一个fd代表read端,指你可以从这一端读取到数据
第二个fd代表write端,指你可以从这一端写入数据
write -> fd1(write端) ||||||||| fd0(read端) -> read
数据流向从fd1流向fd0

下面代码就是父子进程使用管道传递数据的样例,其使用了两个管道,这也是因为pipe是单向流动的

1
2
3
4
5
int main() {
int p[2];
pipe(p);
}

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

#define PIPE_OUT 0
#define PIPE_IN 1

int main() {
/**
* pipe()
* 可以搜索sys_pipe来看其具体实现
* 返回两个fd到传入的数组中
* 其中,fd0表示管道输出端,读取端
* fd1表示管道输入端,写入端
* fd1 -> IIIIIIIII -> fd0
*
* 如何使用管道呢,
* 1. 我们将数据msg写入到fd1中,write(fd1, msg):msg -> fd1
* 2. 此时数据便通过管道传到了fd0,所以我们需要对fd0的数据做接收
* 3. 子进程read(fd0, receive) :fd0 -> receive
* 4. 疑问,是什么时候read都可以吗,管道里的数据不及时接收有问题吗?
* 5. 答:执行read时可认为等待输入,会等待管道另一端输入数据。
*/
int pipe_p2c[2]; //父进程到子进程

pipe(pipe_p2c); // 父 -> 子

int pipe_c2p[2];

pipe(pipe_c2p); // 子 -> 父

int pid = fork();

char buf = 'P';

// 出错, 关闭管道退出
if (pid < 0) {
close(pipe_p2c[PIPE_IN]);
close(pipe_p2c[PIPE_OUT]);

close(pipe_c2p[PIPE_IN]);
close(pipe_c2p[PIPE_OUT]);

fprintf(2, "fork pid < 0, pid : %d\n", pid);

exit(1);
} else if (pid == 0) {
// 子进程
close(pipe_c2p[PIPE_OUT]);
close(pipe_p2c[PIPE_IN]);

int exit_status = 0;

if (read(pipe_p2c[PIPE_OUT], &buf, sizeof(char)) == sizeof(char)) {
fprintf(1, "%d: received ping\n", getpid());
} else {
exit_status = 1;
fprintf(2, "child read error\n");
}

if (write(pipe_c2p[PIPE_IN], &buf, sizeof(char)) != sizeof(char)) {
exit_status = 2;
fprintf(2, "child write error\n");
}

close(pipe_c2p[PIPE_IN]);
close(pipe_p2c[PIPE_OUT]);

exit(exit_status);
} else {
// 父进程
close(pipe_c2p[PIPE_IN]);
close(pipe_p2c[PIPE_OUT]);

int exit_status = 0;

int writelen = write(pipe_p2c[PIPE_IN], &buf, sizeof(char));

if (writelen != sizeof(char)) {
exit_status = 2;
fprintf(2, "parent write error, writelen: %d\n", writelen);
}

if (read(pipe_c2p[PIPE_OUT], &buf, sizeof(char)) == sizeof(char)) {
fprintf(1, "%d: received pong\n", getpid());
} else {
exit_status = 1;
fprintf(2, "parent read error\n");
}

close(pipe_c2p[PIPE_OUT]);
close(pipe_p2c[PIPE_IN]);

exit(exit_status);
}
}

primes (Hard)

这个实验最关键的是提到了rsc模型
rsc提出了一种新的并发模型的观点,即使用管道通信来实现同步的效果,Golang的并发实践了该观点,有很多讲解的文章可以去搜搜看。

对于这个实验而言,其实跟rsc没太大关系,大致实现思路是,对于单独的一根管道,这根管道有一个基质数,所有他整除的数都会被过滤掉,没被过滤的数进入下一根管道去过滤,以此来实现线性素数筛

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


#define RD 0
#define WR 1

int createSieveProgress(int parentPipe[2]) {
close(parentPipe[WR]); //关闭父输入端
int baseprime = 0;
if (read(parentPipe[RD], &baseprime, sizeof(int)) != sizeof(int)) {
return 0;
}

printf("prime %d\n", baseprime);

int data = 0;

int p[2];

pipe(p);

while (read(parentPipe[RD], &data, sizeof(int) == sizeof(int))) {
if (data % baseprime) {
write(p[WR], &data, sizeof(int));
}
}

if (fork()) {
close(p[WR]);
close(p[RD]);
wait(0);
} else {
createSieveProgress(p);
}
return 0;
}

/**
* @brief 线性素数筛 通道版本
*
* 这里要求使用通道来对素数筛选,每个通道仅筛选其中一项素数
*
* 1. 先来个生成2-35数的进程
*
* 2. 创建管道过滤
*
* 1. 每个通道筛选其遇到的第一个素数,当通道无法从上一个通道获得质数时,便关闭
*
*
* 问题1:进程的孙子进程关闭时,进程的wait会受到影响吗?
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {

int p[2];

pipe(p);

// 父进程,用于生成源数据 2-35
for (int i = 2; i < 36; i++) { write(p[WR], &i, sizeof(int)); }

if (fork()) {
close(p[RD]);
close(p[WR]);
wait(0);
} else {
createSieveProgress(p);
}
exit(0);
}

find

实现find命令,类似这样find . a,即在当前目录递归寻找与a同名的文件。

find命令的编写还是有趣的,实验指导提示可以参照ls是如何遍历目录的。

这里会学习到一些函数例如fstat、stat,以及目录项dirent

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

void find(const char *path, const 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 fstat: %s\n", path);
close(fd);
return;
}

if (st.type != T_DIR) {
fprintf(2, "param1 not a dir: %s\n", path);
}

strcpy(buf, path);

p = buf + strlen(path);

*p++ = '/'; //此时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) {
fprintf(2, "error");
continue;
}

// 是目录,同时排除在.与..中递归
if (st.type == T_DIR && strcmp(p, ".") != 0 && strcmp(p, "..") != 0) {
find(buf, filename);
} else if (strcmp(filename, p) == 0) {
printf("%s\n", buf);
}
}
close(fd);
}

/**
* @brief Write a simple version of the UNIX find program:
* find all the files in a directory tree with a specific name.
*
* 1. 看下ls.c是怎么遍历文件的
* 1. open 指定目录,从指定目录开始遍历
* 2. 系统调用fstat获取当前fd的信息,打印出结果
*
* 2. 参照ls.c的做法
* 1. open 指定目录,从该目录开始遍历
* 2. 如何遍历,调用read读取目录项,dirent,拼接dirent名称,再用open的形式获取fd,再获取fstat
* 2. 系统调用fstat获取当前fd的信息,若为文件,则字符串比较,若为目录,则执行递归的寻找操作
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {

if (argc != 3) {
fprintf(2, "usage: find <directory> <filename>");
exit(1);
}

find(argv[1], argv[2]);
return 0;
}

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


void xargs(char *argv[]) {
int pid, status;
if ((pid=fork()) == 0) {
exec(argv[0], argv);
exit(1);
}
wait(&status);
return;
}

/**
* @brief Write a simple version of the UNIX xargs program:
* read lines from the standard input and run a command for each line,
* supplying the line as arguments to the command.
*
* 1. 调用fork与exec来执行每个command,并使用wait来等待子进程完成输出
* 2. 子进程可能输出多行,父进程在读入时需要注意,多行对应需要父进程执行多条命令
* 3. kernel/param.h定义了MAXARG,或许对你定义argv array有帮助
* 4. 记得修改Makefile中的UPROGS,记得make clean
*
* 1. 当 command line中输出xargs时,这时我们会用标准输入中的每一行去执行xargs后的命令行
* 忽略标准输入这点,xargs后面跟随的就是一条命令,一条待补充参数的命令,待补充参数就来自xargs前执行得到的结果
*
* 就相当于是fork子进程去执行n条命令
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char *argv[]) {
int i, j;
char c, buf[512], *xargv[MAXARG] = {0};
if (argc < 2 || argc - 1 > MAXARG) {
fprintf(2, "usage: xargs <cmd> {args}, # of args should be less than 32\n");
exit(1);
}

memset(buf, 0, sizeof(buf));

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

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

j = 0;

while (read(0, &c, 1) > 0) {
if (c != '\n')
buf[j++] = c;
else {
if (j != 0) {
buf[j] = '\0';
xargv[argc - 1] = buf;
xargs(xargv);
j = 0;
}
}
}
if (j != 0) {
buf[j] = '\0';
xargv[argc - 1] = buf;
xargs(xargv);
}
exit(0);
}
-------- 本文结束 感谢阅读 --------