MIT6.S081之实用程序

前言

第一个实验是在XV6系统上实现五个实用程序。虽然不难,但是我写了好久……

实验

sleep

sleep本身不需要实现,只需要运行即可。

源代码

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

int main(int argc,char* argv[]){
if(argc != 2){
printf("Sleep needs one argument!\n"); //检查参数数量是否正确
exit(-1);
}
int ticks = atoi(argv[1]); //将字符串参数转为整数
sleep(ticks); //使用系统调用sleep
printf("(nothing happens for a little while)\n");
exit(0); //确保进程退出
}

编译运行

  1. 修改Makefile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    UPROGS=\
    $U/_cat\
    $U/_echo\
    $U/_forktest\
    $U/_grep\
    $U/_init\
    $U/_kill\
    $U/_ln\
    $U/_ls\
    $U/_mkdir\
    $U/_rm\
    $U/_sh\
    $U/_stressfs\
    $U/_usertests\
    $U/_grind\
    $U/_wc\
    $U/_zombie\
    $U/_sleep\ # 添加sleep
  2. 运行

    1
    sudo make qume

sleep运行结果

pingpong

要求

实现两个进程在管道两侧来回通信。
父进程将”ping”写入管道,子进程从管道将其读出并打印。子进程从父进程收到字符串后,将”pong“写入另一个管道,然后由父进程从该管道读取并打印。

原理

通过定义两个管道fp和sp,实现父子进程传输消息

注意,进程通常只持有某个管道的读出端或者写入端,因此使用的时候需要将另一端关闭。

源代码

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
#include "kernel/types.h"
#include "user.h"
int main(int argc,char* argv[])
{
int fp[2],sp[2];
pipe(fp);//父进程写入,子进程读取
pipe(sp);
printf("program start!\n");
int pid = fork();
if(pid<0)
{
printf("error!");
}
else if(pid == 0)
{
/*子进程 */
char *buffer = " ";
close(fp[1]); // 关闭写端
read(fp[0], buffer, 4);//阻塞等待
printf("%d: received %s\n",getpid(),buffer);
close(fp[0]); // 读取完成,关闭读端
char *intput = "pong";
close(sp[0]); // 关闭读端
write(sp[1], intput, 4);
close(sp[1]); // 写入完成,关闭写端

}
else{
/*父进程*/
char *buffer = "ping";
close(fp[0]); // 关闭读端
write(fp[1], buffer, 4);
close(fp[1]); // 写入完成,关闭写端
close(sp[1]); // 关闭写端
read(sp[0], buffer, 4);
printf("%d: received %s\n",getpid(),buffer);
close(sp[0]); // 读取完成,关闭读端
}
exit(0);
}

运行结果

pingpong运行结果

primes

要求

使用管道实现“质数筛选”, 输出2~35之间的而所有质数。

原理

筛选思路:

  1. 主进程将所有数据输入到管道的左侧
  2. 子进程从管道读出并筛选出第一个数字x,排除掉x的倍数,其他数字再写入下一管道;
  3. 重复步骤二,直到管道中没有数字
    质数筛选模型

    源代码

    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/types.h"
    #include "user.h"
    #include <stddef.h>
    void mapping(int n, int fd[])
    {
    close(n);//关闭文件描述符n,令n映射到fd[n]
    dup(fd[n]);
    close(fd[0]);
    close(fd[1]);
    }
    void primes()
    {
    int fd[2];
    pipe(fd);
    int prime;//当前的质数
    int ref = read(0, &prime, sizeof(int));
    if(ref == 0)return;//没有质数了
    printf("prime %d\n", prime);
    int pid = fork();
    if(pid == 0)
    {
    int num;
    mapping(1, fd);//将管道映射到1上
    while(read(0,&num, sizeof(int)))
    {
    if(num%prime == 0)continue;
    write(1, &num, sizeof(int));
    }
    }
    else
    {
    wait(NULL);
    mapping(0, fd);//将管道映射到0上
    primes();
    }
    }
    int main(int argc,char* argv[])
    {
    int fd[2];
    pipe(fd);//父进程写入,子进程读取
    printf("program start!\n");
    int pid = fork();
    if(pid<0)
    {
    printf("error!");
    }
    else if(pid == 0)
    {
    /*子进程 */
    mapping(1,fd);
    for(int i = 2;i <= 35; i++)//将所有数字塞入管道
    write(1, &i, sizeof(int));

    }
    else{
    /*父进程*/
    wait(NULL);//等待子进程结束
    mapping(0, fd);
    primes();
    }
    exit(0);
    }

运行结果

primes的运行结果

find

要求

在目录树中查找名称与字符串匹配的所有文件,输出文件的相对路径。

原理

借鉴ls指令的写法,读取路径下的文件名,如果是文件,就与寻找的文件名比较,如果是文件夹,就递归继续寻找该文件夹。

源代码

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
#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;
}

void find(char *path, char *file_name)
{
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(st.type != T_DIR)
{
fprintf(2, "find: cannot find in %s\n", path);
close(fd);
return;
}
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){//路径太长了
return;
}

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("find: cannot stat %s\n", buf);
continue;
}
char *name = fmtname(buf);
switch (st.type)
{
case T_FILE:
if(strcmp(name, file_name) == 0)
{
printf("%s\n", buf);
}
break;
case T_DIR:
if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
continue;//不能是本层或者上层
find(buf, file_name);
break;
default:
break;
}
}
close(fd);
}

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

if(argc < 3){
printf("please input find [path] [file_name]\n");
exit(0);
}
find(argv[1], argv[2]);
exit(0);
}

运行结果

find的运行结果

xargs

要求

从标准输入中读取行并 为每行运行一次 指定的命令,且将该行作为命令的参数提供。

原理

这个小实验主要是对于实现效果的理解。

  1. |
    命令行中的|会将左边的命令的输出通过管道传递到右侧,可以直接从标准输入中读取字符串。
  2. exec函数
    exec接收的二维参数数组argv,argv[0]必须是该命令本身,最后一个参数argv[size-1]必须为0,否则将执行失败。
    1
    2
    char *argv[]={"echo","hello",0};
    exec(argv[0],argv);

源代码

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

int main(int argc, char* argv[]) {
if(argc < 2){
printf("error inputs");
exit(0);
}//参数不够
char *cmd = argv[1];
char *argvs[MAXARG];
for(int i=1;i<argc;i++)
{
argvs[i-1]=argv[i];
}
char buff[1024];
while(read(0,&buff,sizeof(buff)))//读取|右边的命令输出或者等待输出
{
int len = strlen(buff);
argvs[argc-1]=&buff[0];
for(int i=0;i<len;i++)
{
if(buff[i]=='\n')// 默认 -n1
{
if(fork()==0)
{
buff[i]=0;
argvs[argc]=0;//命令的最后一个参数为0
exec(cmd,argvs);//执行每一行命令
}
wait(0);//等待子进程结束
argvs[argc-1]=&buff[i+1];//下一行命令的首地址
}
}
}
exit(0);
}

运行结果

运行命令 xargstest.sh

1
2
3
4
5
6
mkdir a
echo hello > a/b
mkdir c
echo hello > c/b
echo hello > b
find . b | xargs grep hello

xargs的运行结果

结语

第一个实验就耗费了大量的时间,有几个实验还借鉴了别人的写法。考虑到我不是科班出身,之后的实验不如趁早观看别人的写法,能学到东西就好。