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)
实验要求
实现简易版UNIXfind
程序:查找指定目录下所有文件。保存于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)
实验要求
实现简易版UNIXxargs
程序:该程序在命令行输入参数描述一个命令作为其指定命令,然后从标准输入读取数据,将每行字符作为其指定命令附加的参数运行该命令。保存在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还是比较友好也挺有趣的,带我这个小菜鸡正式入了这个课的门。但即便如此,也在最后一题的坑上卡了好久,还需要再接再励!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。