MIT6.S081 Lab util
环境准备
不得不说,在 M1 的 Macbook Air 上写这个实验还挺费劲……
我尝试根据课程的官方指引安装了 qemu
和 riscv-tools
,结果总是遇到各种奇怪的问题……
首先,根据官方的指导,我应该这样安装 riscv-tools
:
brew tap riscv/riscv
brew install riscv-tools
但是,可能是 M1 的原因,我直接这样是无法安装的,总是会显示各种错误。
所以我又尝试下载源码来编译:
brew install python3 gawk gnu-sed gmp mpfr libmpc isl zlib expat texinfo flock
git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
git submodule update --init --recursive
make
./configure --prefix=/opt/riscv-gnu-toolchain --with-cmodel=medany --enable-multilib
sudo make
前面都很顺利,除了下载子模块的遇到了亿点点网络问题。
但是到最后一步 make 的时候,我又总是会遇到提示找不到 libgmp
库的报错……但是我明明已经在第一步安装过 gmp
了……
最后的解决方法是下载了这位大佬的博客中提供的预编译版本,这次终于可以正确运行了。
下面是下载实验需要的 xv6 环境。
git clone git://g.csail.mit.edu/xv6-labs-2020
git checkout util
make qemu
一切都很顺利,只是……在 make qemu
这一步卡住了。
在网上搜索了一下,才知道是我安装的 qemu
版本太高了,2020 年的实验环境不支持。
没办法,干脆下载 2023 年的最新版本吧!
git clone git://g.csail.mit.edu/xv6-labs-2023
git checkout util
make qemu
终于可以进入命令界面了。
sleep (easy)
Implement a user-level
sleep
program for xv6, along the lines of the UNIX sleep command. Yoursleep
should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the fileuser/sleep.c
.
这个任务的要求大概是实现 sleep
命令。这里不是要实现 sleep
系统调用,只是这个终端命令而已。(我一开始以为是要实现系统调用,感觉第一个实验就做这个还有点难度)
那么这就是一个简单的练手题了。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
if(argc < 2){
fprintf(2, "Usage: sleep seconds...\n");
exit(1);
}
sleep(atoi(argv[1]));
exit(0);
}
记得按照题目中所说,修改一下 Makefile
(Add your sleep
program to UPROGS
in Makefile; once you've done that, make qemu
will compile your program and you'll be able to run it from the xv6 shell.)
UPROGS=\
$U/_cat\
............
$U/_sleep\
然后就可以运行啦:
评分:
pingpong (easy)
Write a user-level program that uses xv6 system calls to ''ping-pong'' a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print "
: received ping", where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print " : received pong", and exit. Your solution should be in the file user/pingpong.c
.
大概意思就是说要用在父子进程之间建立一对管道,父进程要先向子进程发一个字节,子进程接收以后打印 <pid>: received ping
然后再发回父进程并退出,父进程接受以后再打印 <pid>: received pong
。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
int pipe1[2], pipe2[2];
// pipe1: parent to child
// pipe2: child to parent
pipe(pipe1);
pipe(pipe2);
if (fork() == 0) {
close(pipe1[1]);
close(pipe2[0]);
char buf[1];
read(pipe1[0], buf, 1);
printf("%d: received ping\n", getpid());
write(pipe2[1], buf, 1);
exit(0);
} else {
close(pipe1[0]);
close(pipe2[1]);
char buf[1];
write(pipe1[1], "a", 1);
read(pipe2[0], buf, 1);
printf("%d: received pong\n", getpid());
exit(0);
}
exit(0);
}
评分:
primes (moderate)/(hard)
Write a concurrent prime sieve program for xv6 using pipes and the design illustrated in the picture halfway down this page and the surrounding text. This idea is due to Doug McIlroy, inventor of Unix pipes. Your solution should be in the file
user/primes.c
.Your goal is to use
pipe
andfork
to set up the pipeline. The first process feeds the numbers 2 through 35 into the pipeline. For each prime number, you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe. Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.
题目有点难读,但是很有意思。
题目的要求是建立很多进程和管道,由第一个进程将 \(2\dots35\) 之间的数传递到第二个进程,第二个进程筛掉第一个素数的倍数,将剩下的数传递到第三个进程,第三个进程筛掉第二个素数的倍数……以此类推。
代码难度不大,我用一个函数 test
来进行筛素数和传递,建立子进程以后递归调用这个函数即可。主函数只要来处理第一个进程和第二个进程之间的传递。
需要注意的是,我这里是利用题目中给的提示(Hint: read
returns zero when the write-side of a pipe is closed),当管道的发送端被关闭后,接收端的 read
函数会返回 \(0\) 这一特性来传递参数的,因此要记得在传递完成后关闭这个文件描述符。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void test(int fd) {
int prime, num;
if (read(fd, &prime, sizeof(prime)) == 0) exit(0);
printf("prime %d\n", prime);
int p[2];
pipe(p);
if (fork() == 0) {
close(p[1]);
test(p[0]);
exit(0);
}
close(p[0]);
while (read(fd, &num, sizeof(num)) != 0) {
if (num % prime != 0) {
write(p[1], &num, sizeof(num));
}
}
close(p[1]);
close(fd);
wait(0);
}
int
main(int argc, char *argv[])
{
int p[2];
pipe(p);
if (fork() == 0) {
close(p[1]);
test(p[0]);
exit(0);
}
close(p[0]);
for (int i = 2; i <= 35; ++i) {
write(p[1], &i, sizeof(i));
}
close(p[1]);
wait(0);
exit(0);
}
评测:
find (moderate)
Write a simple version of the UNIX find program for xv6: find all the files in a directory tree with a specific name. Your solution should be in the file
user/find.c
.
任务就是实现一个简单的 find
命令,找到给定目录下文件名为给定名称的文件。
题目说可以参考 user/ls.c
,所以我基本上就照搬了 ls.c
的内容,简单修改一下就可以了。
主要是把 fmtname
函数改成了 getname
,返回路径对应的文件名的指针。
然后把 ls
函数,改成 find
函数,在判断给定文件类型的时候,如果是普通的文件,那么就判断文件名是否相符。如果是目录,那么就枚举目录下所有文件进行递归,要记得判断文件名如果如果是 .
或者 ..
那么不能递归。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
char* getname(char *path) {
char *p;
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
return p;
}
void find(char *path, char *name)
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, O_RDONLY)) < 0){
fprintf(2, "find: cannot open %s\n", path);
return;
}
if(fstat(fd, &st) < 0){
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}
switch(st.type){
case T_DEVICE:
case T_FILE:
if (strcmp(getname(path), name) == 0) {
printf("%s\n", path);
}
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("find: 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;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if (strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
find(buf, name);
}
}
break;
}
close(fd);
}
int
main(int argc, char *argv[])
{
if(argc < 3){
fprintf(2, "Usage: find path name\n");
exit(0);
}
find(argv[1], argv[2]);
exit(0);
}
评测结果:
xargs (moderate)
Write a simple version of the UNIX xargs program for xv6: its arguments describe a command to run, it reads lines from the standard input, and it runs the command for each line, appending the line to the command's arguments. Your solution should be in the file
user/xargs.c
.
xargs
是一个神奇的 Unix 命令,它依次将输入的每一行都附加在参数的后面然后执行。
实现方法倒是不难,但是细节也不少。
我的思路是使用一个大数组 buf
记录输入,然后使用 args
数组记录参数字符串。每次读入一个字符,如果是普通字符就继续读入,如果是空白字符或者没有读入到字符,那么就说明一个参数结束了,那么就将读入的字符如改为 '\0'
,标志一个字符串,然后将这个参数的开始地址加入到 args
中。如果读入到换行符或者没有读入到字符,那么就说明一行结束了,调用函数执行即可,执行结束后将各个指针复位。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void run(char **args) {
if (fork() == 0) {
exec(args[0], args);
exit(0);
}
wait(0);
}
int
main(int argc, char *argv[])
{
char buf[1024], *bufp, *stp;
char *args[128], **argp, **argst;
argp = args;
for (int i = 1; i < argc; i++) {
*argp++ = argv[i];
}
argst = argp;
bufp = stp = buf;
while (1) {
int r = read(0, bufp, 1);
char c = *bufp;
if (r == 0 && bufp == buf) break;
if (r == 0 || c == ' ' || c == '\n') {
if (*(bufp - 1) != ' ') {
*bufp++ = '\0';
*argp++ = stp;
stp = bufp;
}
if (c == '\n' || r == 0) {
*argp = 0;
run(args);
argp = argst;
bufp = stp = buf;
}
if (r == 0) break;
} else {
++bufp;
}
}
exit(0);
}
测试:
总成绩
选做题
uptime (easy)
Write an uptime program that prints the uptime in terms of ticks using the
uptime
system call.
直接调用 uptime
的系统调用然后输出即可。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
printf("%d\n", uptime());
exit(0);
}
正则的 find
Support regular expressions in name matching for
find
.grep.c
has some primitive support for regular expressions. (easy)
根据题目中给出的提示,我们可以把 grep.c
里面的正则匹配相关的函数复制过来:
int
match(char *re, char *text)
{
if(re[0] == '^')
return matchhere(re+1, text);
do{ // must look at empty string
if(matchhere(re, text))
return 1;
}while(*text++ != '\0');
return 0;
}
// matchhere: search for re at beginning of text
int matchhere(char *re, char *text)
{
if(re[0] == '\0')
return 1;
if(re[1] == '*')
return matchstar(re[0], re+2, text);
if(re[0] == '$' && re[1] == '\0')
return *text == '\0';
if(*text!='\0' && (re[0]=='.' || re[0]==*text))
return matchhere(re+1, text+1);
return 0;
}
// matchstar: search for c*re at beginning of text
int matchstar(int c, char *re, char *text)
{
do{ // a * matches zero or more instances
if(matchhere(re, text))
return 1;
}while(*text!='\0' && (*text++==c || c=='.'));
return 0;
}
实际上我感觉这几个函数可能有点问题,因为我用 *
来通配的时候总是出现各种奇怪的结果……不过不管了,不是我 find
写的问题,我直接用 grep
命令出现的结果也是千奇百怪。
然后只需要将 find
程序中匹配的地方改过来即可:
case T_FILE:
if (match(name, getname(path))) {
printf("%s\n", path);
}
break;
总之带上 *
的结果就是很奇怪……不管了。