xv6环境搭建过程

1. 安装VMware虚拟机

2. 下载Ubuntu ISO包

3. 于VMware创建Ubuntu 系统虚拟机

4. xv6的环境搭建

4.1 在虚拟机中运行以下命令

1
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

​ 运行中出现以下错误

​ 按提示运行命令:

1
sudo apt-get update

​ 再次运行命令1后安装成功。

​ 检验qwmu是否安装成功:

4.2 安装Xv6

​ 安装git:

1
sudo apt install git

​ 然后运行以下命令:

1
2
3
4
git clone git://g.csail.mit.edu/xv6-labs-2021
cd xv6-labs-2021
git checkout util
git commit -am 'my solution for util lab exercise 1'

​ 此时需要配置用户名、邮箱:

1
2
git config --global user.name 'Kevin'
git config --global user.email '1171407839@qq.com'

​ 在xv6目录下运行以下命令:

1
make qemu

​ 得到以下结果:

XV6环境搭建完毕。

5.三个练手小程序

5.1 sleep

1
Implement the UNIX program sleep for xv6; your sleep 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 file user/sleep.c. 
5.1.1 解题思路

​ 从user/user.h 中system call可以看到sleep函数的声明为 int sleep(int);

​ 所以,可以从main中的参数argc(argument count)确定参数的个数,其中,第一个参数为程序的名称,即sleep,而正确运行还需要一个且仅需要一个int型参数。所以当argc不为2时,则输出错误信息并退出;否则,将argv[1]转化为int传入sleep(),随后正常退出。

5.1.2 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/stat.h"

int main(int argc,const char* argv[]){
// argv[0]为程序的名称sleep,argv[1]为参数
if(argc != 2){
// 若除了sleep外参数传入个数不为1
printf("%s","Error!Wrong Format!");
exit(1); // 错误退出
}
sleep(atoi(argv[1]));
// 将argv[1]转化为整型传入sleep
exit(0);
}
5.1.3 运行结果测试

5.2 pingpong

1
Write a program that uses UNIX 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 "<pid>: received ping", where <pid> 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 "<pid>: received pong", and exit. Your solution should be in the file user/pingpong.c. 
5.2.1 解题思路

​ fork()用于创建子进程,当fork返回0时,表示当前正处于子进程;在父进程中,fork返回新创建子进程的进程ID;若出现错误,fork返回负值。

​ pipe()被调用时开辟一块缓冲区(管道)用于通信,拥有读端与写端,可通过filedes参数传出给用户程序两个文件描述符,其中filedes[0]指向读端,filedes[1]指向写端。管道在程序中如同打开的文件,所以可以通过write(filedes[1])和read(filedes[0])读写内核缓冲区。

​ 当前进程为子进程时,关闭子进程的写与父进程的读端,当子进程从管道0读到父进程写入管道1的字节时,输出当前pid 与 ping;当前进程为父进程时,关闭父进程的写与子进程的读端,当父进程从管道1读到子进程写入管道0的字节时,输出当前pid 与 pong,最后关闭两个管道的所有读写端口。

5.2.2 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

const int READ = 0;
const int WRITE = 1;

int main()
{
int child; //若此处写成int child = fork() 测试时会报错
int filede0[2]; // 子进程的读写端
int filede1[2]; // 父进程的读写端

if (pipe(filede0) || pipe(filede1)) // pipe调用返回-1表示失败,返回1表示成功
{
printf("Error!Pipe Call Fails!");
exit(1);
}

if ((child= fork()) == 0) // 创建子进程且当前为子进程时
{
close(filede0[WRITE]); // 关闭子进程的写
close(filede1[READ]); // 关闭父进程的读
write(filede0[WRITE], "child", 1);
read(filede1[READ], "from father", 1);
printf("%d: received ping\n", getpid());
//先关闭读再关闭写
close(filede0[READ]);
close(filede1[WRITE]);
exit(0);
}
else
{
close(filede0[READ]);
close(filede1[WRITE]);
write(filede0[WRITE], "father", 1);
read(filede1[READ], "from child", 1);
printf("%d: received pong\n", getpid());
close(filede1[READ]);
close(filede0[WRITE]);
wait(0);
}
exit(0);
}
5.2.3 运行结果测试

5.3 primes

1
Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c. 
5.3.1 解题思路

​ 进程1输入2-35;进程2打印第一个质数2后,将所有进程1输入的、是进程2打印的质数的倍数的丢弃,把剩余的交给进程3;进程3打印第二个质数3,将所有进程2输入的、是进程3打印的质数的倍数丢弃,把剩余交给进程4……一直重复,直至剩余的输入为空。

5.3.2 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

const int MAX = 35;
const int READ = 0;
const int WRITE = 1;

void primes(int pre_read)
{
int prime;
if (read(pre_read, &prime, sizeof(int)) <= 0) // 若prime不为空或文件尾
exit(0);
printf("prime %d\n", prime);
int filede[2];
pipe(filede);
if (fork() == 0) // 若为上个进程的子进程,即清空上一个输出的质数的倍数后
{
close(filede[WRITE]); // 关闭写
primes(filede[READ]); // 递归
close(filede[READ]);
}
else
{
int temp;
close(filede[READ]);
while (read(pre_read, &temp, sizeof(int)))
if (temp % prime != 0) // 剔除prime的倍数,将剩余的写回pipe中
write(filede[WRITE], &temp, sizeof(int));
close(filede[WRITE]);
wait(0);
}
exit(0);
}

int main()
{
int filede[2];
pipe(filede);
if (fork() == 0)
{
close(filede[WRITE]);
primes(filede[READ]);
close(filede[READ]);
}
else
{
close(filede[READ]);
for(int i=2;i<=MAX;i++)
write(filede[WRITE],&i,sizeof(int)); //将2-35依次写入filede
close(filede[WRITE]);
wait(0);
}
exit(0);
}

5.3.3 运行结果测试

6.实验心得

​ 首先,配置环境应该选比较充裕的时间,防止校园网网速过慢导致一个学生失去他的梦想,几个G的Ubuntu系统,以及几百M的VMware虚拟机软件等,还没开始配置一个下午就过去了;不仅如此,git clone 在 github+校园网双重debuff下,更是交出了10k/s的下载速度,看他下载又看了一晚上,不过最后也终于可以运行了。

​ 回到练手小程序上,首先比较重要的是看懂hints的内容,对其中出现的如fork(),pipe()等系统调用,想要编写程序必须得先明白他们的作用,以及需要用到哪些参数,我自己是习惯先看中文的解释,等到差不多看明白能够使用了,再好好看看xv6内部是如何进行调用的。

​ 最后,学好英语很重要,无论是Ubuntu系统的Terminal终端,还是各种官方的文档,都是全英的,报错信息都有自带的命令建议。