Lab1 Xv6 and Unix utilities
Boot xv6 (easy)
从指定仓库 clone 代码,而后编译 - 运行,尝试一部分命令,没问题之后就能够正式开始了!
sleep (easy)
试验要求
实现 Unix 程序 sleep,使程序暂定指定数量的 ticks。并将解决方案放在文件
user/sleep.c
中。
一些提醒:
- 参考其余代码如果获取用户输出参数,并对用户输出参数做出肯定判断(未指定工夫报错
exit(1)
) - 能够应用零碎调用
sleep
- main 函数最初退出应调用
exit(0)
- 将程序增加到 Makefile 中
具体实现
留神参数判断和工夫小于 0 的状况。主函数实现如下:
int main(int argc, char* argv[]) {if (argc < 2) {printf("Usage: sleep [time]\n");
exit(1);
} else {int time = atoi(argv[1]);
if (time < 0) exit(1);
sleep(time);
exit(0);
}
}
试验后果
- 程序成果:
- 测试后果:
pingpong (easy)
试验要求
实现父子过程之间传输数据并打印过程 ID 和后果。具体为:父过程发一字节,子过程收到打印 ”<pid>: receive ping”,再把数据发回父过程,退出;父过程收到数据打印 ”<pid>: receive pong” 并退出。文件保留在
user/pingpong.c
中。
一些提醒:
- 创立管道
pipe
- 创立子线程
fork
read
从管道读取,write
写入管道getpid
获取过程 ID
具体实现
注意事项:
- 管道读写的方向(0 读 1 写)
- 留神敞开文件描述符
- 父过程
wait
期待子过程完结,这样可保障输入程序
具体实现:
int main() {int fd1[2], fd2[2], pid;
char buf;
pipe(fd1);
pipe(fd2);
pid = fork();
if (pid < 0) {fprintf(2, "fork() error\n");
exit(1);
} else if (pid > 0) { // father
close(fd1[0]);
close(fd2[1]);
write(fd1[1], "a", 1);
read(fd2[0], &buf, 1);
close(fd1[1]);
close(fd2[0]);
wait(0);
printf("%d: received pong\n", getpid());
} else { // child
close(fd2[0]);
close(fd1[1]);
read(fd1[0], &buf, 1);
write(fd2[1], &buf, 1);
close(fd2[1]);
close(fd1[0]);
printf("%d: received ping\n", getpid());
}
exit(0);
}
试验后果
- 失败后果
其实最开始我没有加wait
期待子过程完结,这样的后果就会造成最根本的并发编程谬误,导致父子过程输入的后果可能是交错在一起的,比方这样:当然也能够通过
sleep
或其余形式解决 - 失常后果
- 测试后果
primes (moderate)/(hard)
试验要求
实现一个并发的素数筛选程序。具体思路参考:
Hins:
- 因为 XV6 资源有余,应及时敞开文件描述符
- 当写入端敞开时
read
返回 0 - 间接将 int 类型数据写入管道
具体实现
试验思路:
- 首先能够明确父过程的工作:创立管道、开启子过程、写入 2~35 数据、而后期待就完了,因而父过程绝对简略。
-
依据图片,子过程的工作:
- 创立管道、创立子过程
- 从管道接收数据,第一个肯定为素数
- 而后利用该素数淘汰后续数据中是该素数整数倍的数据
- 未淘汰的数据写入管道传给本人的子过程
- 不难发现,所有的子过程行为十分类似,因而可用递归实现。
- 若用递归,则首先须要思考终止条件:子过程一个数据都收不到时。即其父过程找不到任何符合条件的数据要发送,就敞开了管道的写入端,子过程
read
时一个数据都没收到就返回了 0。 - 因而,在子过程中可先
read
一个 int 数据,若后果大于 0 则将收到的数作为判断后续数据的基准素数,若后果等于 0,则满足递归终止条件间接完结过程。
注意事项:
- 及时敞开文件描述符,的确是不够
- 及时回收子过程
试验代码:
-
子过程递归函数:
void processSon(int* dad_fd) {close(dad_fd[1]); int first_n, fd[2], pid, ret; ret = read(dad_fd[0], &first_n, sizeof(int)); if (ret == 0) exit(0); printf("prime %d\n", first_n); pipe(fd); pid = fork(); if (pid < 0) {fprintf(2, "fork() error\n"); exit(1); } else if (pid > 0) { int n; close(fd[0]); while (1) {ret = read(dad_fd[0], &n, sizeof(int)); if (ret > 0) {if (n % first_n != 0) {write(fd[1], &n, sizeof(int)); } } else {break;} } close(fd[1]); close(dad_fd[0]); wait(0); } else {processSon(fd); } }
-
主函数
int main() {int fd[2], pid; pipe(fd); pid = fork(); if (pid < 0) {fprintf(2, "fork() error\n"); exit(1); } else if (pid > 0) { // father close(fd[0]); for (int i = 2; i < 36; ++i) {write(fd[1], &i, sizeof(int)); } close(fd[1]); wait(0); } else { // son processSon(fd); } exit(0); }
测试后果
- 程序成果
- 测试后果
find (moderate)
试验要求
实现简易版 UNIX
find
程序:查找指定目录下所有文件。保留于user/find.c
。
hints:
- 可参考
ls.c
- 可通过递归降落到子目录,但不要递归“.”和“..”
- 应用
strcmp()
判断字符串是否雷同
具体实现
从 ls.c
中失去的提醒:
- 可在
main
实现缺省参数,在本体函数值专一于有参数失常状况 - 利用
struct stat
和int fstat(int, struct stat*)
获取文件状态,其中type
字段可判断文件类型(XV6 有文件、目录、设施三种类型) - 目录文件自身蕴含的文本信息,可利用
struct dirent
提取,该构造体中的name
字段保留目录中文件名
实现思路:
- 根本与
ls.c
思路一样。main
函数实现缺省参数。而后调用find
函数 find
函数每次关上一个文件,而后判断该文件类型:- 若是文件或设施,就间接打印(递归终止条件)
- 若是目录,就利用
struct dirent
读取目录下所有文件名,而后每个文件都递归调用find
注意事项:
- 不要递归“.”和“..”
- 提前结束时留神敞开关上的文件描述符
试验代码
-
find
函数——实现局部,不思考缺省参数void find(const char* dir) { int fd; struct stat st; if ((fd = open(dir, 0)) < 0) {fprintf(2, "find: cannot open %s\n", dir); return; } if (fstat(fd, &st) < 0) {fprintf(2, "find: cannot stat %s\n", dir); close(fd); return; } switch (st.type) { case T_DEVICE: case T_FILE: {printf("%s\n", dir); break; } case T_DIR: { struct dirent de; char newpath[512]; char* p; if (strlen(dir) + 1 + DIRSIZ + 1 > sizeof(newpath)) {printf("find: path too long\n"); break; } strcpy(newpath, dir); p = newpath + strlen(newpath); *p++ = '/'; while (read(fd, &de, sizeof(de)) > 0) {if (de.inum == 0 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) continue; memmove(p, de.name, DIRSIZ); p[DIRSIZ] = '\0'; find(newpath); } break; } default: break; } close(fd); }
-
主函数——思考缺省参数,调整后调用
find
int main(int argc, char* argv[]) { int i; if (argc < 2) {find("."); } else {for (i = 1; i < argc; i++) find(argv[i]); } exit(0); }
测试后果
- 程序成果
- 测试后果
xargs (moderate)
试验要求
实现简易版 UNIX
xargs
程序:该程序在命令行输出参数形容一个命令作为其指定命令,而后从规范输出读取数据,将每行字符作为其指定命令附加的参数运行该命令。保留在user/xargs.c
中。
hits:
- 应用
fork
和exec
,并在父过程中期待子过程完结 - 每次读取一个字符直到呈现换行符(’\n’)
kernel/param.h
中申明了MAXARG
示意最大参数数量
具体实现
实现代码:
-
次要逻辑函数
xargs
void xargs(int argc, char* argv[]) {char buf[BUF_SIZE], *start, *cur; int len, pid, read_idx = 0; while ((len = read(0, buf + read_idx, BUF_SIZE - read_idx - 1)) > 0) { read_idx += len; start = cur = buf; while (1) {while (cur - buf < read_idx && *cur != '\n') ++cur; if (*cur == '\n') *cur = '\0'; else break; argv[argc] = start; pid = fork(); if (pid > 0) {wait((int*)0); // printf("%d end\n", pid); } else {// for (int i = 0; i <= argc + 1; ++i) {// printf("argv[%d]=%s,", i, argv[i]); // } // printf("begin %s by %d ...", argv[0], getpid()); // 我也不晓得为什么我的这个文件打不开,利用 cat 或者 vi 命令都会卡住不动 if (strcmp(argv[2], "./console") == 0) exit(0); if (exec(argv[0], argv) < 0) {fprintf(2, "exec() error\n"); } exit(0); } start = cur + 1; } memmove(buf, start, cur - start); read_idx = cur - start; } }
-
仍然利用主函数
main
实现缺省参数int main(int argc, char* argv[]) {char* xargv[MAXARG]; if (argc == 1) {xargv[0] = "echo"; xargs(argc, xargv); } else {for (int i = 0; i < argc - 1; ++i) {xargv[i] = argv[i + 1]; } xargs(argc - 1, xargv); } exit(0); }
碰到的坑:
./console
文件打不开问题
在过了find | xargs echo ...
的命令之后感觉应该没问题了(事实上的确没问题了),而后去尝试find | xargs grep hello
,而后就会卡住不动,只能退出虚拟机。贴出来的代码也能够看到,我在exec
前打印了要执行的命令、参数和过程号,完结过程也打印一下。加上打印的find | xargs grep hello
运行后果如图:能够发现这个 29 号过程怎么都完结不了,其对应的文件就是
./console
。我始终认为是我程序自身的问题(也可能的确是我的问题),就始终 debug,一行一行去试,发现的确是这个子过程不完结,父过程始终wait
。(当初想想,其实能够在grep
中打个断点再持续 debug,之后再去尝试吧,但大概率会在read
时阻塞)
而后我明确了,的确是这个文件的问题。重启虚拟机后用其余命令比方cat/vi
试了一试,发现也会卡住:遂作罢,只能特定地跳过这个文件了(还是我太菜)
通过ls
能够查看这个文件的信息。发现这个文件是个设施文件,大小为 0。据此,我也只能揣测所有试图从该文件中读取数据的程序都读不到数据,也就是read
会阻塞,也没方法返回 0。如果我的揣测是对的,那么对于
grep
命令,或者须要先判断要搜寻的文件大小是否大于 0,而后再去读其中的内容会好一些。- 第二个坑——测试时规范输入的 ’hello’ 数量
测试文件中对于xargs
的一部分:被正文掉的是本来的测试语句,通过判断 ”hello” 在规范输入中呈现的数量是否为 3 来确定正确与否。其中
xargstest.sh
的内容通过echo
和mkdir
创立一些文件用于检测,也就是运行find | xargs grep hello
时呈现的头几行:它创立了三个有 ”hello” 的文件。然而
find
命令还另外输出了b
,也就是说文件 b 要被查两次,这么说后果应该是 4 个 hello,而且命令也在规范输入里啊!于是我每次测试它就讲它须要 3 个而我出了 8 个。为了过关,我只能把测试的 ”hello” 数量改了。
测试后果
- 程序成果
- 测试后果
总结
Lab1 还是比拟敌对也挺乏味的,带我这个小菜鸡正式入了这个课的门。但即便如此,也在最初一题的坑上卡了良久,还须要马不停蹄!