比较容易,主要是看系统调用的掌握,注意main函数传入的参数如何处理
1
2
3
4
5
6
7
8
9
10
11
12
|
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: sleep time\n");
exit(1);
}
sleep(atoi(argv[1])); //直接调用sleep
exit(0);
}
|
这个考察管道和fork的应用,首先对于管道 pipe,它读入一个长度为2的数组,分别作为管道的读入端和写入端,p[0] 表示写入端,p[1]表示读入端,可以用一个宏来定义,避免01区分不清的情况
然后就是创建一个子进程,父进程向管道写入一个字节后,子进程打印输出<pid>: received ping
,然后子进程向父进程写入一个字节,父进程打印输出<pid>: received pong
,这里我用了 wait
,表示父进程等待子进程结束之后再打印输出
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
|
//user/pingpong.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define WRITE 0
#define READ 1
int main(int argc, char *argv[]) {
if (argc >= 2) {
printf("Usage: pingpong\n");
exit(1);
}
char buf[2]; //一个缓冲区原来读写
int p[2]; //文件描述符
pipe(p); //创建一个管道
int pid = fork(); //创建一个子进程
if (pid == 0) {
read(p[READ], buf, 1);//从管道读取一个字节
close(p[READ]); //关闭管道读端
printf("%d: received ping\n", getpid());
write(p[WRITE], buf, 1);//向管道写入一个字节
close(p[WRITE]); //关闭管道写端
} else {
write(p[WRITE], buf, 1);//向管道写入一个字节
close(p[WRITE]); //关闭管道写端
wait(0); //这里是关键,要到达子进程结束后父进程才继续执行下去
read(p[READ], buf, 1); //从管道读取一个字节
printf("%d: received pong\n", getpid());
close(p[READ]); //关闭管道写端
}
exit(0);
}
|
相比前两个稍微复杂一点,重点是理解给的这张图:
这张图就是先创建一个主进程,然后将数字通过管道依次写入子进程中,由子进程读取来进行处理
子进程再创建一个新的进程,子进程处理时先读到的第一个数字first,然后将后续读到的数字是first倍数的都舍去,将不是first倍数的数字都写入管道中,由子子进程进行递归处理
值得注意的是递归终止的情况,详细见代码,理解了上面的过程就比较好解决了
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
|
//user/primes.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define WRITE 1
#define READ 0
void primes(int p[]) {
close(p[WRITE]); //关闭原管道的写端
int np[2];
pipe(np); //创建一个新的管道
//两个数分别表示从管道读取的第一个数字,以及接下来的数字
int first = 1, next = 0;
//如果此时已经读取不到数字了,关闭管道并且退出
if (read(p[READ], &first, 4) == 0) {
close(p[READ]);
exit(0);
}
//如果能读取到数字,打印输出
printf("prime %d\n", first);
int pid = fork(); //创建一个子进程
if (pid == 0) {
close(np[WRITE]); //关闭新管道的写端
primes(np); //进入下一层递归调用
close(np[READ]); //关闭新管道的读端
} else {
close(np[READ]); //关闭新管道的读端
//从原管道中读取数字,read返回为0,表示已经读取完了
while (read(p[READ], &next, 4) != 0) {
//如果读取的数字不能整除读取第一个数字,写入下一层管道
if (next % first != 0) {
write(np[WRITE], &next, 4);
}
}
close(p[READ]); //关闭原管道的读端
close(np[WRITE]);//关闭新管道的写端
wait((int*)0); //等待子进程结束
}
exit(0);
}
int main(int argc, char *argv[]) {
//参数不符合情况时候返回报错
if (argc >= 2) {
printf("Usage: primes\n");
exit(1);
}
int p[2];
pipe(p); //创建一个管道
int pid = fork(); //创建一个子进程
if (pid == 0) {
close(p[WRITE]);//关闭管道的写端
primes(p); //进行递归调用
} else {
close(p[READ]);//关闭管道的读端
//依次将2-35写入到管道中,由子进程进行进一步处理
for (int i = 2; i <= 35; ++i) {
write(p[WRITE], &i, 4);
}
close(p[WRITE]);//关闭管道的写端
wait((int*)0); //等待子进程结束
}
exit(0);
}
|
基本上就是在 user/ls.c
基础上改过来的,要理解其中 char* fmtname(char *path)
函数的作用,它传入一个文件名(包括前面的目录),然后取最后一个 /
之后的文件名,例如a/b/c
最后取出来的是 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
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
|
//user/find.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
char*
fmtname(char *path)
{
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
return p; //直接返回/下一个字符,ls中在结尾添加了空格,这里不需要
}
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;
}
//st.type为此时文件的类型
switch(st.type){
//当fd指向的是文件类型,进行比较
case T_FILE:
//如果比较发现是要查找的文件名,打印输出
if (strcmp(fmtname(path), filename) == 0) {
printf("%s\n", path);
}
break;
//如果fd指向的是目录类型,需要进一步处理
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("find: path too long\n");
break;
}
strcpy(buf, path); //将此时路径拷贝到buf上
p = buf+strlen(buf);//p指向buf结尾的位置
*p++ = '/'; //在buf结尾加上/,并且p指向下一个位置,先*p = '/',再p++
//读取目录中的每一个文件
while(read(fd, &de, sizeof(de)) == sizeof(de)){
//de.inum是所有文件的根目录的情况,.是当前目录本身,..是当前目录的父目录,都需要特判
if(de.inum == 0 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
continue;
//将此时文件名拷贝到p指向的位置,也就是buf后面接上文件名
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0; //字符串加上末端的0
//打开此时的绝对路径,如果失败则跳过
if(stat(buf, &st) < 0){
printf("find: cannot stat %s\n", buf);
continue;
}
//将此时绝对路径buf,进入下一层递归查找
find(buf, filename);
// printf("%s\n", fmtname(buf));
}
break;
}
close(fd);
}
int
main(int argc, char *argv[])
{
if(argc == 2) {
find(".", argv[1]);
} else if (argc == 3) {
find(argv[1], argv[2]);
} else {
fprintf(2, "Usage: find path file\n");
exit(1);
}
exit(0);
}
|
这个命令在 Linux 详细用法参考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
|
//user/xargs.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(2, "Usage: xargs cmd...\n");
exit(1);
}
char buf[1024];
char *argvs[MAXARG];
// memcpy(&argvs[0], &argv[1], sizeof(argv[0]) * (argc - 1));
for (int i = 1; i < argc; ++i) {
argvs[i - 1] = argv[i];
}
while (1) {
int size = 0; //表示从标准输入中读取的字节数目
//从标准输入中一个字节一个字节读取
while (read(0, &buf[size], 1) != 0) {
//遇见/n 跳出
if (buf[size] == '\n') break;
++size;
}
//如果一个字节都没读取到,说明标准输入内容已经读取完了
if (size == 0) break;
buf[size] = 0; //buf结尾置为0
//将从标准输入读取的一行内容放入到argvs末尾中
argvs[argc - 1] = buf;
//创建一个子进程,利用exec执行
if (fork() == 0) {
exec(argv[1], argvs);
exit(0);
}
wait(0); //等待父进程结束
memset(buf, 0, 1024); //将buf清空
}
exit(0);
}
|
这应该是第三次做lab1了,前两次都止步于lab3,这次打算完成学(抄)一遍
完结证明:
参考资料:
MIT 6.S081 2020 LAB1记录 - 知乎