ref:https://pdos.csail.mit.edu/6.828/2020/xv6.html
实验:Lab: Xv6 and Unix utilities
环境搭建
实验环境搭建:https://blog.csdn.net/qq_45512097/article/details/126741793
搭建了1天,大家自求多福吧,哎。~搞环境真是折磨人
anyway,我搞好了:
开整实验
内容1:sleep
创建user/sleep.c
在Makefile中UPROGS下添加$U/_sleep
编写sleep.c
运行测试程序grade-lab-util并且指明要测试的函数。如 grade-lab-util sleep
c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void main(int argc, char *argv[])
{
if(argc != 2){
// 2 输出到哪里,输出内容,
fprintf(2,"usage:sleep <time> \n");
exit(1);
}
int sec = atoi(argv[1]);
sleep(sec);
//printf("usage:sleep %d \n",sec);
exit(0);
}
修改makefile,只需要改这一个地方
验证命令,要退出qume才能执行,否则会报错,
这种时候单项验证sleep
./grade-lab-util sleep
bash
justin@DESKTOP-NIK28BI:~/vc6/xv6-labs-2020$ ./grade-lab-util sleep
fatal: detected dubious ownership in repository at '/mnt/d/code/vc6/xv6-labs-2020'
To add an exception for this directory, call:
git config --global --add safe.directory /mnt/d/code/vc6/xv6-labs-2020
make: 'kernel/kernel' is up to date.
== Test sleep, no arguments == fatal: detected dubious ownership in repository at '/mnt/d/code/vc6/xv6-labs-2020'
To add an exception for this directory, call:
git config --global --add safe.directory /mnt/d/code/vc6/xv6-labs-2020
sleep, no arguments: OK (1.3s)
== Test sleep, returns == sleep, returns: OK (0.8s)
== Test sleep, makes syscall == sleep, makes syscall: OK (1.0s)
内容2:pingpong
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 ": 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.
c
#include "kernel/types.h"
#include "user/user.h"
#define RD 0 // pipe的read端
#define WR 1 // pipe的write端
int main(){
char buf[10]; //缓冲区字符数组,存放传递的信息
int fd_c2p[1]; // child->parent
int fd_p2c[1]; // parent->child
int ret1 = pipe(fd_c2p);
int ret2 = pipe(fd_p2c);
if(ret1==-1){
printf("child->parent pipe");
exit(1);
}
if(ret2==-1){
printf("parent->child pipe");
exit(1);
}
int pid = fork();
if(pid==0){
//子进程
// 关闭父管道的写入端和子管道的读取端
close(fd_p2c[WR]); // 显示关闭是为了严谨,不关闭不会报错
close(fd_c2p[RD]); //
read(fd_p2c[RD],buf,4);
printf("child:%d received %s\n", getpid(),buf);
write(fd_c2p[WR],"pong",4);
}else if(pid>0){
close(fd_p2c[RD]);
close(fd_c2p[WR]);
// 父进程
write(fd_p2c[WR],"ping",4);
int status;
int child_id = wait(&status);
printf("child:%d done!\n",child_id);
read(fd_c2p[RD],buf,4);
printf("parent:%d received %s\n", getpid(),buf);
printf("parent:%d done!\n", getpid());
}
exit(0);
return 0;
}
bash
hart 1 starting
hart 2 starting
init: starting sh
$ pingpong
child:4 received ping
child:4 done!
parent:3 received pong
parent:3 done!
注意没有exit(0); 会出现乱码,原因未知,欢迎大神留言解惑:
bash
hart 1 starting
hart 2 starting
init: starting sh
$ pingpong
child:4 received ping
usertrap(): unexpected scause 0x000000000000000d pid=4
sepc=0x0000000000000100 stval=0x0000000000003f64
child:4 done!
parent:3 received pong
parent:3 done!
usertrap(): unexpected scause 0x000000000000000d pid=3
sepc=0x0000000000000100 stval=0x0000000000003f64
内容3:Primes(素数,难度:Moderate/Hard)
YOUR JOB
使用管道编写prime sieve(筛选素数)的并发版本。这个想法是由Unix管道的发明者Doug McIlroy提出的。请查看这个网站(翻译在下面),该网页中间的图片和周围的文字解释了如何做到这一点。您的解决方案应该在user/primes.c文件中。
您的目标是使用pipe和fork来设置管道。第一个进程将数字2到35输入管道。对于每个素数,您将安排创建一个进程,该进程通过一个管道从其左邻居读取数据,并通过另一个管道向其右邻居写入数据。由于xv6的文件描述符和进程数量有限,因此第一个进程可以在35处停止。
提示:
- 请仔细关闭进程不需要的文件描述符,否则您的程序将在第一个进程达到35之前就会导致xv6系统资源不足。
- 一旦第一个进程达到35,它应该使用wait等待整个管道终止,包括所有子孙进程等等。因此,主primes进程应该只在打印完所有输出之后,并且在所有其他primes进程退出之后退出。
- 提示:当管道的write端关闭时,read返回零。
- 最简单的方法是直接将32位(4字节)int写入管道,而不是使用格式化的ASCII I/O。
- 您应该仅在需要时在管线中创建进程。
- 将程序添加到Makefile中的UPROGS。
如果您的解决方案实现了基于管道的筛选并产生以下输出,则是正确的:
$ make qemu
...
init: starting sh
$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31
$
考虑所有小于1000的素数的生成。Eratosthenes的筛选法可以通过执行以下伪代码的进程管线来模拟:
p = get a number from left neighbor
print p
loop:
n = get a number from left neighbor
if (p does not divide n)
send n to right neighbor
p = 从左邻居中获取一个数
print p
loop:
n = 从左邻居中获取一个数
if (n不能被p整除)
将n发送给右邻居
生成进程可以将数字2、3、4、...、1000输入管道的左端:行中的第一个进程消除2的倍数,第二个进程消除3的倍数,第三个进程消除5的倍数,依此类推。
这是我调试版本的代码,没有在xv6中打印,可以在任何linux中运行。
取数据:lpipe,写数据rpipe, 从图中可以看到,每个矩形的操作是重复的,所以应该用递归调用function primes,
而且每次递归调用,生成新的子进程,继续递归调用。
c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#define RD 0
#define WR 1
/**
* @brief 寻找素数
* @param lpipe 左邻居管道
*/
void primes(int lpipe[2]){ //接收的参数当左通道用
close(lpipe[WR]);
int first;
if(read(lpipe[RD],&first,sizeof(int))==sizeof(int)){ // 读到了【左管道】(第一次是main)送过来的数据
printf("prime %d\n",first);
printf("get first:%d\n",first);
// 定义右管道
int rpipe[2];
pipe(rpipe); // 生成右管道,做数据过滤
int data;
while(read(lpipe[RD], &data, sizeof(int))==sizeof(int)){
if (data % first){// 除尽的过滤掉,除不尽的留下,放到右管道
write(rpipe[WR], &data, sizeof(int));
}
}
if (fork() == 0) {
primes(rpipe); // 递归的思想,但这将在一个新的进程中调用
} else {
close(rpipe[WR]);// 关闭右管道的写
wait(0);//等待回收子进程
}
}
}
int main(){
int p[2];
pipe(p);
int res;
for (size_t i = 2; i <= 35; i++)
{
// 依次写入初始数据 2,3,4,...35
res = write(p[WR],&i,sizeof(size_t));
printf("pid:%d, send:%d, res:%d\n",getpid(),i,res);
}
if (fork()==0)
{ //子进程做筛选素数操作,是递归筛选
primes(p);
}else{
//父进程啥也不干,等着回收子进程即可
close(p[WR]); // 这里写关不关闭无所谓,但是谨慎起见,还是关闭为好
close(p[RD]);//这里必须要关闭读,否则子进程无法从管道读取数据。
wait(0);
}
exit(0);
}
main函数的 close(p[RD]);//这里必须要关闭读,否则子进程无法从管道读取数据。
读操作只能确保开放给一个进程!这个题目是很难的!
4.find(难度:Moderate)
YOUR JOB
写一个简化版本的UNIX的find程序:查找目录树中具有特定名称的所有文件,你的解决方案应该放在user/find.c
5.xargs(难度:Moderate)
YOUR JOB
编写一个简化版UNIX的xargs程序:它从标准输入中按行读取,并且为每一行执行一个命令,将行作为参数提供给命令。你的解决方案应该在user/xargs.c
下面的例子解释了xargs的行为
$ echo hello too | xargs echo bye
bye hello too
$
注意,这里的命令是echo bye,额外的参数是hello too,这样就组成了命令echo bye hello too,此命令输出bye hello too
请注意,UNIX上的xargs进行了优化,一次可以向该命令提供更多的参数。 我们不需要您进行此优化。 要使UNIX上的xargs表现出本实验所实现的方式,请将-n选项设置为1。例如
$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2
$
提示:
- 使用fork和exec对每行输入调用命令,在父进程中使用wait等待子进程完成命令。
- 要读取单个输入行,请一次读取一个字符,直到出现换行符('\n')。
- kernel/param.h声明MAXARG,如果需要声明argv数组,这可能很有用。
- 将程序添加到Makefile中的UPROGS。
- 对文件系统的更改会在qemu的运行过程中保持不变;要获得一个干净的文件系统,请运行make clean,然后make qemu
- xargs、find和grep结合得很好