MIT 6.S081的Lab1: Xv6 and Unix utilities题解
Boot xv6 (easy)
这个在环境配置里已经做过了,注意退出系统是按ctrl+a
后按x
,查看进程是ctrl+p
,根据后面报错和网上教程在这里先做一些调整:由于我的/usr/bin
下有python3,于是把grade-lab-util
文件的第一行修改为#!/usr/bin python3
sleep (easy)
要求
实现UNIX的sleep
程序,使得系统停止用户指定的时钟数,这里的时钟由xv6内核决定,程序放在user/sleep.c
分析
首先对用户传的参数进行判断,错误的话打印错误信息并退出;之后将参数用提示中说的atoi
转成整型再使用系统调用sleep
就可以了。
有兴趣的话可以去看内核代码kernal/sysproc.c
中的函数sys_sleep
实现
编辑user/sleep.c
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h"
int main(int agrc, char *agrv[]){ if(agrc < 2) { fprintf(2, "error: sleep time is required!\n"); exit(1); } int sleep_time = atoi(agrv[1]); sleep(sleep_time); exit(0); }
|
编辑Makefile
文件,在UPROGS=\
一栏添加如下内容:
测试
输入下面的命令,注意不是官网上的./grade-lab-util sleep
:
1
| sudo python3 grade-lab-util sleep
|
pingpong (easy)
要求
使用系统调用实现两个进程使用管道通信,父进程向子进程发送信息,等待子进程结束,从管道读取子进程发的信息,打印进程id: received pong
,结束进程;子进程打印进程id: received ping
,将信息发给父进程,结束子进程。程序放在user/pingpong.c
分析
创建两个管道,一个用于父进程到子进程通信,另一个用于子进程到父进程通信。创建子进程,分别对父子进程编程:一个先写后读,一个先读后写。这里没有提示要用close
,在官方文档对管道的解说里是有close的,写了下代码,关不关闭这里都是可以通过的。
实现
编辑user/pingpong.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
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h"
int main(int agrc, char *agrv[]) { int p_cp[2], p_pc[2]; pipe(p_cp); pipe(p_pc); char buf[2]; int pid = fork(); if(pid < 0) { fprintf(2, "error: fail to fork!\n"); exit(1); } else if(pid > 0) { close(p_pc[0]); close(p_cp[1]); if(write(p_pc[1], "p", 1) != 1) { fprintf(2, "error: parent fail to write!\n"); exit(1); } close(p_pc[1]); if(read(p_cp[0], buf, 1) != 1) { fprintf(2, "error: parent fail to read!\n"); exit(1); } close(p_cp[0]); if(buf[0] == 'c') { printf("%d: received pong\n", getpid()); } else { fprintf(2, "error: parent receives wrongly\n"); exit(1); } exit(0); } else { close(p_pc[1]); close(p_cp[0]); if(read(p_pc[0], buf, 1) != 1) { fprintf(2, "error: child fail to read!\n"); exit(1); } close(p_pc[0]); if(buf[0] == 'p') { printf("%d: received ping\n", getpid()); } else { fprintf(2, "error: child receives wrongly\n"); exit(1); } if(write(p_cp[1], "c", 1) != 1) { fprintf(2, "error: child fail to write!\n"); exit(1); } close(p_cp[1]); exit(0); } }
|
简略版(省去close和错误信息):
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
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h"
int main(int agrc, char *agrv[]) { int p_cp[2], p_pc[2]; pipe(p_cp); pipe(p_pc); char buf[2]; int pid = fork(); if(pid > 0) { write(p_pc[1], "p", 1); read(p_cp[0], buf, 1); if(buf[0] == 'c') { printf("%d: received pong\n", getpid()); } } else { read(p_pc[0], buf, 1); if(buf[0] == 'p') { printf("%d: received ping\n", getpid()); } write(p_cp[1], "c", 1); } exit(0); }
|
编辑Makefile
文件,在UPROGS=\
一栏添加如下内容:
测试
输入命令:
1
| sudo python3 grade-lab-util pingpong
|
上面是简略版代码的测试结果,下面是完整版代码的测试结果,均通过:
primes (moderate/hard)
要求
利用pipe
和fork
完成埃式筛素数,输出2-35的所有素数,程序放在user/primes.c
分析
给出了思路提示:
-
图片的意思是每个进程取出当前最小的素数输出,并利用埃式筛的原理去除该素数的倍数,将剩余的数传给下一个进程,直到 35
-
上述思路父子进程不断嵌套,考虑在一个无尽的循环里对父子进程分别操作
-
有一个数组用于保存当前的素数集,数组的第 0 位存放素数个数 n,第 1 至 n 位存放对应的每一个素数
-
每次循环里,父进程输出素数集的第 1 位即当前的第一个素数,并对剩下的素数进行筛选存入一个临时素数集(存放规则仍同上)中,筛完将其通过管道整体送给子进程,等待子进程完成后中止
-
每次循环里,子进程读出管道中的第一个整型数据(应为父进程给的素数个数),如果为 0 中止进程,否则将管道中剩余数据全部读入素数集,开新管道,开新进程
-
注意主进程要等所有进程结束才能结束,且注意关闭文件描述符防止越界
实现
编辑user/primes.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
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h"
int main(int agrc, char *agrv[]) { int p[2]; int prime[35], newprime[35]; pipe(p); prime[0] = 34; for(int i = 2; i <= 35; i++) { prime[i - 1] = i; } int pid = fork(); while(1) { if(pid < 0) { fprintf(2, "error: fail to fork!\n"); exit(1); } else if(pid > 0) { printf("prime %d\n", prime[1]); close(p[0]); newprime[0] = 0; for(int i = 2; i <= prime[0]; i++) { if(prime[i] % prime[1] != 0) { newprime[++newprime[0]] = prime[i]; } } write(p[1], newprime, sizeof(int) * (newprime[0] + 1)); close(p[1]); wait(0); exit(0); } else { close(p[1]); read(p[0], prime, sizeof(int)); if(prime[0] == 0) { exit(0); } read(p[0], prime + 1, sizeof(int) * prime[0]); close(p[0]); pipe(p); pid = fork(); } } exit(0); }
|
编辑Makefile
文件,在UPROGS=\
一栏添加如下内容:
测试
输入命令:
1
| sudo python3 grade-lab-util primes
|
输入命令:
find (moderate)
要求
写一个模仿UNIX中find
指令的简单程序,指令给出特定查找目录,和特定查找文件名,输出目录树中所有匹配的文件,程序放在user/find.c
分析
实现
编辑user/find.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
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/fs.h" char* newfmtname(char *path) { static char buf[DIRSIZ+1]; char *p; for(p = path + strlen(path); p >= path && *p != '/'; p--); p++; if(strlen(p) >= DIRSIZ) return p; memmove(buf, p, strlen(p)); buf[strlen(p)] = 0; return buf; } void findfile(char *path, char *name) { char buf[512], *p; int fd; struct dirent de; struct stat st; if((fd = open(path, 0)) < 0) { fprintf(2, "ls: cannot open %s\n", path); return; } if(fstat(fd, &st) < 0) { fprintf(2, "ls: cannot stat %s\n", path); close(fd); return; } switch(st.type) { case T_FILE: if(strcmp(newfmtname(path), name) == 0) printf("%s\n", path); break; case T_DIR: if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) { printf("ls: path too long\n"); break; } strcpy(buf, path); p = buf + strlen(buf); *p++ = '/'; 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; findfile(buf, name); } break; } close(fd); } int main(int agrc, char *agrv[]) { if(agrc != 3) { fprintf(2, "wrong path or filename!\n"); exit(1); } findfile(agrv[1], agrv[2]); exit(0); }
|
编辑Makefile
文件,在UPROGS=\
一栏添加如下内容:
测试
输入命令:
1
| sudo python3 grade-lab-util find
|
xargs (moderate)
要求
写一个模仿UNIX中xargs
指令的简单程序,从标准输入读取行并为每一行运行一个命令,将该行作为参数提供给命令,程序放在user/xargs.c
分析
-
题意:可能因为我没用过UNIX系统中的这个指令,看了半天也没太看明白,先写了个输出传入main函数的参数的程序,发现输出的是xargs
后面的部分,那这个指令大概是标准输入读取的是前面部分指令执行的结果(已经执行过了,直接读就可以),标准输入每行添加到原本的参数(argv)后面执行。
-
将原本的参数放进一个新的字符串数组,标记一下原参数结尾的位置,每从标准输入读一行(\n
结尾),将其添加到参数后面,fork()
一个子程序用来执行这个指令
-
kernal/exec.c
中可以查到exec
指令的代码,第一个参数传名字第二个参数传字符串数组
-
注意kernel/param.h
声明了MAXARG
,exec
的最大参数量,可以用于开数组
实现
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
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/fs.h" #include "kernel/param.h" int main(int argc, char* argv[]) { char* newargv[MAXARG]; char** lastpos = newargv; for (int i = 1; i < argc; i++, lastpos++) *lastpos = argv[i]; char** argv_cur = lastpos; char buf[MAXARG][100]; memset(buf, 0, MAXARG * 100); int i = 0, j = 0; while (read(0, buf[i] + j, 1) > 0) { if (buf[i][j] == ' ') { buf[i][j] = 0; *argv_cur = buf[i]; argv_cur++; i++, j = 0; } else if (buf[i][j] == '\n') { buf[i][j] = 0; *argv_cur = buf[i]; i = j = 0; int pid = fork(); if (pid == 0) { argv_cur++; **argv_cur = 0; exec(argv[1], newargv); exit(0); } else { wait(0); argv_cur = lastpos; } } else j++; } exit(0); }
|
编辑Makefile
文件,在UPROGS=\
一栏添加如下内容:
测试
在系统里输入命令:
以下是文档给的样例和测试的两个结果截图: