Xv6 and Unix utilities
Boot xv6 (easy)
使用 Ubuntu 20.04 ,安装需要的工具
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu部署 xv6
git clone git://g.csail.mit.edu/xv6-labs-2021
...
cd xv6-labs-2021
git checkout util这里使用 ls 就可以看到 xv6 的源码了。
接下来执行 make qemu 就会编译运行 xv6 了。
使用 Ctrl-a x 就可以退出 xv6 (按住 Ctrl 按下 a ,松开 Ctrl 和 a ,按下 x)。
sleep (easy)
实现一个 sleep 接口。
实验思路
- 参考 user 下的其他程序,将头文件引入,(kernel/types.h 声明类型的头文件, user/user.h 声明系统第哦啊用的头文件,ulib.c 声明工具的头文件)
- 写
int main(int argc, char* argv[])函数。其中,参数argc是所有参数的个数,argv[]是参数的内容,第 0 个参数是程序的全名,其他的是用户输入的其他参数 - 先判断用户输入的参数是否正确,只要参数不等于 2 个,就知道用户输入的参数是有错误的,就可以打印返回错误信息。参考 user/echo.c ,可以使用
fprintf()函数打印错误信息,最后需要调用exit(1),0 表示正常,1 表示异常。 - 剩下的就是参数没有问题的情况,由于接收的是字符类型,所以应该先用
atoi()函数将字符转换为整数,调用系统调用sleep()函数,最后调用系统调用exit(0)退出 - 在 Makefile 中添加配置,在
UPROGS的最后一行添加$U/_sleep\
pingpong (easy)
实现一个 pingpong 接口。
实验思路
- 按照实验目的的思路,先使用
pipe来创建管道,由于管道是半双工的,因此需要创建两个管道,一个是父进程向子进程发送字节,一个是子进程向父进程发送字节,其中 p[0] 是读端, p[1] 是写端 - 通过
fork建立子进程,通过阅读 xv6文档可知,对于父进程,fork会返回父进程的 pid ,同时创建并返回一个 pid 是 0 子进程;让父进程通过管道发送 "ping" 给子进程,然后使用read开始读取来自子进程的pong - 当子进程使用
read读取管道,读取到父进程发送的 "ping" 时,通过getpid获取 "pid" ,然后打印出 "<pid>: received ping" ,之后使用write发送 "pong" 给父进程,最后退出子进程 - 当父进程读取到子进程的发送的
pong时,打印出 "<pid>: received pong"
primes (moderate)/(hard)
实现一个多进程并发打印素数的接口。
实验思路
- 首先看链接中的图,可以看出,数字全部输入到第一条管道,然后打印出 2 ,然后将所有 2 的倍数都剔除,接着把被剔除的数字全部输入到下一条管道,第二个进程打印的管道传输来的第一个数字 3 ,然后将所有 3 的倍数剔除,以此类推
- 看图片上面的伪代码,首先 p 从左边的管道获取到一个数字,打印 p ,循环,n 从左边的的管道获取一个数字,如果 p 不能整除 n ,则将 n 发送到右边的管道
- 代码思路,
main函数中,使用fork生成父子进程,使用子进程调用一个primes函数,父进程利用管道将数字传输给子进程,子进程将读管道传输给primes函数 primes函数打印当前数字,生成一个用于父子传递的管道,对应提示网站的右边的传输,然后再使用fork()生成父子进程,然后子进程递归调用primes函数,父进程接口读取参数中传来的数据,筛选完后发送给子进程
踩到的坑
- 一定一定一定要记得把不需要还有用完的文件描述符关掉
对于管道读操作,如果没有数据写入,会出现阻塞,直到有数据写入
对于管道读操作,如果所有写端被关闭时,read()会返回0(EOF)
对于管道写操作,缓冲区满的时候,会出现阻塞,直到有空间写入
对于管道写操作,如果所有读端被关闭时,会触发SIGPIPE信号(默认中止进程)
find (moderate)
写一个简单的版本的 UNIX 的查找程序:找到目录中所有带特定名称的文件。
实验思路
- 根据提示,先看一下 user/ls.c 是如何读取目录的,使用
open()打开路径会返回一个文件描述符,使用fstat()获取路径信息;然后读取路径类型,如果路径是文件,直接打印,如果是文件夹,判断一下路径是否太长,太长直接返回错误,否则循环读取,每次读一个文件或文件夹,如果文件夹的 inodenum 是 0 ,说明这个文件失效了,直接跳过,接着将文件名复制到路径后面,再在路径末尾新增一个空字符串,最后格式化打印 - 先遍历当前目录的所有文件,查找出文件名带有目标字符的文件,如果找到直接打印,如果找到文件夹,就递归查找
xargs (moderate)
写一个简单版本的 UNIX 的 xargs 程序:从标准输入中读取行数,并为每一行运行一条命令,同时将行数作为参数提供给命令
实验思路
- 进行参数校验,如果大于
MAXARG,直接返回错误 - 通过
read()读取文件描述 0 ,可以读取到管道传递的数据。系统调用循环读取输入,直到遇到 EOF(文件结束符) ,需要开辟新的内存来存储读取到的参数,同时将所有的参数存储到一个指针数组中,但是不可以使用main()里的argv[],因为容量不是固定的 - 最后,使用
fork(),创建一个子进程,在子进程中调用exec(),父进程一定要使用wait()等待子进程执行结束再退出
扩展
至于为什么要使用子进程,我也很好奇,问了一下 ds ,原因如下
防止某个命令会修改环境变量,导致后续的操作逻辑出错
如果不使用子进程,不能实现并行执行
如果有多命令时,单个命令出错会导出整个 xargs 程序崩溃
踩到的坑
- 头文件顺序对编译会有影响,
user/user.h不能第一个引入,否则会编译出错
Lab1 所有实现测试
执行 make grade
== Test sleep, no arguments ==
$ make qemu-gdb
sleep, no arguments: OK (2.8s)
== Test sleep, returns ==
$ make qemu-gdb
sleep, returns: OK (0.7s)
== Test sleep, makes syscall ==
$ make qemu-gdb
sleep, makes syscall: OK (1.0s)
== Test pingpong ==
$ make qemu-gdb
pingpong: OK (1.0s)
== Test primes ==
$ make qemu-gdb
primes: OK (0.9s)
== Test find, in current directory ==
$ make qemu-gdb
find, in current directory: OK (1.0s)
== Test find, recursive ==
$ make qemu-gdb
find, recursive: OK (1.2s)
== Test xargs ==
$ make qemu-gdb
xargs: OK (1.1s)
== Test time ==
time: FAIL
Cannot read time.txt
Score: 99/100
make: *** [Makefile:237: grade] Error 1最后一个出错了,搜索了一下参考链接,只需要在 xv6-labs-2020 的目录下新建一个 time.txt 文件,然后在里面输入一个整数就可以了。应该是 mit 的老师用来了解学生的进度的
== Test xargs ==
$ make qemu-gdb
xargs: OK (1.2s)
== Test time ==
time: OK
Score: 100/100