[xv6-mit-6.S081-2020]Lab1: util
Lab: Xv6 and Unix utilities
https://pdos.csail.mit.edu/6.S081/2020/labs/util.html
代码:https://github.com/xyfJASON/xv6-mit-6.S081-2020/tree/util
sleep
任务:暂停指定 ticks,1 个 tick 由 xv6 内核定义。
这个任务就是用来让我们熟悉 xv6 的运作方式的。user 目录下是用户程序,kernel 目录下是内核程序,本次实验只需要添加用户程序,不涉及内核。在写好一个用户程序(如 sleep.c)之后,修改 Makefile 文件,这样在编译之后 xv6 就能得到 sleep 可执行文件(类似于 linux 下 /bin/ 中的内容),从而可以通过 shell 运行它。
xv6 系统提供了系统调用 sleep(),因而我们的 sleep.c 用户程序只需调用它。为了避免混乱,在此说明一下:当我们给 shell 输入 sleep 命令时,实际上是在执行我们写的 sleep 用户程序,而这个用户程序通过 sleep 系统调用执行了内核的相关代码,完成了暂停的功能。至于系统调用的过程中发生了什么,就是下一次实验要考虑的内容了。
pingpong
任务:父进程将“ping”写入管道,子进程从管道将其读出并打印。子进程从父进程收到字符串后,将“pong”写入另一个管道,然后由父进程从该管道读取并打印。
这个任务主要是学习使用管道的方法。我认为重点是要正确认识到文件描述符是什么,在此基础上管道就不难理解了。关于文件描述符的资料很多,在此不赘述。
最后记住不用的管道一定要 close 掉,否则读完了还在读、或写满了还在写就会一直被阻塞。
primes
任务:使用管道实现质数筛选,输出2~35之间的所有质数。
筛选思路:主进程将所有数据输入到管道的左侧,第一个子进程从管道读出并筛选出2,排除掉2的倍数,其他数字再写入下一管道;第二个子进程读出并筛选出3,排除掉3的倍数,其他数字写入到下一管道;第三个子进程读出筛选出5,以此类推……
这是这次实验中最难的内容。不难想到可以用递归实现,怎样设计这个递归是一个难点。我的实现如下:
从原理图可以看见,整个过程可以分为若干个阶段:筛 2 阶段、筛 3 阶段、筛 5 阶段、……每一个阶段都会从上一个阶段读入若干待处理数字,认定第一个读入的数字为素数,并筛去它的倍数。我把每一个阶段都交给一个进程负责。因此,每一层递归我们要做的事情就有:创建向下一个阶段传输的管道 p,然后 fork,父进程从前一个管道(递归时将管道文件描述符作为参数)读入数字,如果当前数字不被筛去,则输出到 p;子进程递归这个过程。如果从前一个管道读入的时候发现写端(它的父进程)已经关闭了,那么 read 将返回 0,我们以此作为递归的终止条件。
需要注意的是,xv6 系统文件描述符有限,因此创建管道有数量的限制。素数筛到 31 已经是上限,如果发现程序筛到 29 还没问题,筛到 31 就出错,那需要好好考虑一下有没有不用的管道没关闭和递归的终止条件。
find
任务:在目录树中查找名称与字符串匹配的所有文件,输出文件的相对路径。
这个任务需要参考 ls.c 的实现,读懂 ls.c 的逻辑,不需要一路回看到源码,根据变量名和上下文猜就行了。然后在 ls.c 的基础上略作更改,在搜索到文件时改成判断是否与要找的文件名相同,相同则输出 path;在搜索到目录时改成递归 find 即可。
xargs
任务:从标准输入中读取一些行,对于每一行,将该行作为参数运行一次指定的命令。
这个任务最难的点在于搞懂 xargs 的工作方式。xargs 后接一个命令,然后从标准输入读入若干行参数,每读一行,就用这一行的参数运行一次命令。
xargs 一般用作管道的读端,这样在管道的写端运行一个命令,视命令的输出为参数,写入管道,然后 xargs 读入这些参数。例如 echo hello | xargs echo bye,管道的写端执行命令 echo hello,于是将 "hello" 写入管道,xargs 读入 "hello" 作为 echo bye 的参数,于是命令变成了 echo bye hello,因此这条命令的执行结果是 "bye hello"。
实现的重点主要是字符串的处理,要求把输入的字符串转化成 *argv[] 的形式,即一个字符串数组;然后创建一个子进程执行命令,注意 exec() 的第一个参数为执行的命令名称,第二个参数为参数列表且以 0 结尾;父进程等待子进程执行结束后继续。