一、安装环境
我使用的Ubuntu22.04。
//安装必须的工具链
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
//单独移除掉qemu的新版本
sudo apt-get remove qemu-system-misc
//额外安装一个旧版本qemu
wget https://download.qemu.org/qemu-5.1.0.tar.xz
tar xf qemu-5.1.0.tar.xz
cd qemu-5.1.0
./configure --disable-kvm --disable-werror --prefix=/usr/local --target-list="riscv64-softmmu"
make
sudo make install
//克隆xv6仓库
git clone git://g.csail.mit.edu/xv6-labs-2020
cd xv6-labs-2020
git checkout util
//进行编译
make qemu
//编译成功并进入xv6操作系统的shell
xv6 kernel is booting
hart 2 starting
hart 1 starting
init: starting sh
输入ls指令. 1 1 1024
.. 1 1 1024
README 2 2 2059
xargstest.sh 2 3 93
cat 2 4 23688
echo 2 5 22512
forktest 2 6 13256
grep 2 7 26832
init 2 8 23336
kill 2 9 22456
ln 2 10 22304
ls 2 11 25856
mkdir 2 12 22576
rm 2 13 22560
sh 2 14 40584
stressfs 2 15 23552
usertests 2 16 142288
grind 2 17 36968
wc 2 18 24656
zombie 2 19 21832
sleep 2 20 22344
pingpong 2 21 23648
primes 2 22 24104
find 2 23 26160
xargs 2 24 24488
console 3 25 0
在安装的过程中,可能出现缺少某些包,百度后直接安装即可。
二、sleep
实现xv6的UNIX程序sleep,你的睡眠程序应该暂停一段用户指定的ticks,tick是xv6内核定义的时间概念,即来自定时器的两次中断之间的时间差。你的解决方案应在user/sleep.c中。
这个只需要调用sleep()即可。
#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char *argv[]){
if(argc != 2){
fprintf(2,"usage sleep\n");
exit(1);
}
int ticks = atoi(argv[1]);
int ret = sleep(ticks);
exit(ret);
}
写好之后,在Makefile中的UPROGS后面添加即可,MakeFile在xv6-labs-2020目录下,可以会隐藏,ls -l可以看到,直接vim对其操作即可。注意后面不要加空格,否则会报错。
最后测试结果如下
三、pingpong
编写一个程序,使用UNIX系统调用在一对管道上的两个进程之间“pingpiong”的一个字节,每个方向上一个。父进程应向子进程发送一个字节;子进程打印出其pid:received ping,之后将管道上的字节写入父进程,父进程打印出pid:received pong,然后退出。你的解决方案应在文件user/pingpong中。
创建
#include "kernel/types.h"
#include "user/user.h"
int main(int argc,char *argv[]){
int fd_1[2],fd_2[2];
int pid;
char buf = 'A';
char receiveBuf;
if(pipe(fd_1) < 0){
fprintf(2,"creat pipe error");
exit(1);
}
if(pipe(fd_2) < 0){
fprintf(2,"creat pipe error");
exit(1);
}
pid = fork();
if(pid == 0){
close(fd_1[1]);
close(fd_2[0]);
read(fd_1[0],&receiveBuf,1);
printf("%d: received ping\n",getpid());
write(fd_2[1],&receiveBuf,1);
close(fd_1[0]);
close(fd_2[1]);
}
else if(pid > 0){
close(fd_1[0]);
close(fd_2[1]);
write(fd_1[1],&buf,1);
read(fd_2[0],&buf,1);
printf("%d: received pong\n",getpid());
close(fd_1[1]);
close(fd_2[0]);
}
else{
fprintf(2,"fork error");
}
exit(0);
}
两个管道,一个用于父进程向子进程发送数据,一个用于子进程向父进程发送数据。在不用管道哪个口时及时将其关闭。
注意:receive ping不要拼写错误,要不然测试不通过,而且ping的P也不能大写。
测试结果如下:
四、primes
使用管道编写primes sieve,这个想法要归功于Unix管道的发明者Doug McIlroy。本页下半部分的图片和周围的文字解释了如何做到这一点。您的解决方案应该在文件user/primes.c中。
你的目标是通过pipe和fork来设置管道。第一个进程将2-35送入到管道中。对于每个素数,创建一个进程,该进程从上一个进程中读取到数据,并通过另一个管道向下一个进程中写入数据。由于xv6具有有限数量的文件描述符和pid,因此第一个进程在35停止。
这道题目的意思可以很好的用这张图来表示。
即输入2-35,第一个进程首先从管道中读取,然后判读是不是第一个素数(也就是2)的倍数,将不是2的倍数的写入管道中,交给下一个进程,然后下一个进程判断是不是传入到这个进程第一个数(也就是3)的倍数。如此循环往复,直到数据判断完毕。由于每一个进程执行的过程都是读、判断、写的过程,因此使用递归。递归要注意递归的三要素,功能,结束条件,和等价调用。功能自然就是判断一个数是不是素数,结束条件是官道镇是否有数据,等价调用就是读、判断、写。
还有一个要注意的点的read()在读取管道的时候,在管道开启,管道中没有数据情况下,read()会阻塞;在管道已关闭,管道中没有数据的情况下,read()会返回0。
程序如下:
#include "kernel/types.h"
#include "user/user.h"
int recur(int pass);
int main(int argc, char **argv){
int fd[2];
if(pipe(fd) < 0){
fprintf(2, "pipe error\n");
exit(1);
}
int pid = fork();
if(pid == 0){
close(fd[1]);
recur(fd[0]);
}
else if(pid > 0){
close(fd[0]);
for(int i = 2; i <= 35; i++){
write(fd[1], &i, 4);
}
close(fd[1]);
wait(0);//等待子进程退出
}
else{
fprintf(2, "fork error\n");
exit(1);
}
exit(0);
}
int recur(int pass){
int f[2];
int readBuf;
int primes;
if(read(pass,&readBuf,4) == 0){//管道中数据读取完毕
close(pass);
}
else{
primes = readBuf;
printf("prime %d\n", primes);
pipe(f);
int pid = fork();
if(pid == 0){
close(f[1]);
recur(f[0]);
}
else if(pid > 0){
close(f[0]);
while((read(pass,&readBuf,4))){//判断数据是否读完,读完后,由于管道关闭,会返回0
if(readBuf % primes != 0){//判断是否是第一个数的倍数
write(f[1],&readBuf,4);
}
}
close(f[1]);//关闭写管道
wait(0);//等待子进程退出
}
}
exit(0);
}
测试结果如下:
后面两个明天写。