1.题目描述:在xv6上实现pingpong程序,即两个进程在管道两侧来回通信。父进程将”ping”写入管道,子进程从管道将其读出并打印<pid>:received ping
,其中<pid>
是子进程的进程ID。子进程从父进程收到字符串后,将”pong“写入另一个管道,然后由父进程从该管道读取并打印<pid>:received pong
,其中<pid>
是父进程的进程ID。请将代码写在user/pingpong.c文件中。运行效果应该如下:
2. 实验原理:
先附上参考链接。
(1)官方网址:https://pdos.csail.mit.edu/6.828/2020/index.html
(2)xv6 book:https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf
(3)原理概念:
管道(pipe)是一种最基本的进程间通信机制。管道分为 读出端 和 写入端 两个部分,进程可以向写端写入数据,也可以从读端读出数据。通过pipe系统调用,内核会为用户进程创建管道,同时返回两个文件描述符,用以描述管道的读写端,例如:
int p[2];
int ret;
ret = pipe(p); /*正常创建后,p[1]为管道写入端,p[0]为管道读出端*/
通过文件描述符,程序可以按读写文件的形式读写管道,例如:
int write = write(p[1], buffer, n);
int read = read(p[0], buffer, n);
进程通常只持有某个管道的读出端或者写入端,因此使用的时候需要将另一端关闭,例如(一般通过fork()开启一个新的进程,父进程和子进程分别实现对管道流的读和写):
/* 子进程读管道,父进程写管道 */
int ret = fork();
if (ret == 0) {
/* 子进程 */
close(p[1]); // 关闭写端
...
read(...);
...
close(p[0]); // 读取完成,关闭读端
} else if (ret>0) {
/* 父进程 */
close(p[0]); // 关闭读端
...
write(...);
...
close(p[1]); // 写入完成,关闭写端
}
(4)实现思路:
思路一:
单管道实现:采用一个管道,父进程实现"ping"的写入和"pong"的读取,子进程实现"pong"的写入和"ping"的读取。
(值得注意的是:由于管道内同时会放入字符串"ping"和"pong",所以如果不对两个字串读出和写入过程进行限制,会导致资源访问的冲突。因此,我们采取调用函数wait()的方式,保证同一时刻管道内只有一个资源的写入和读取)
思路二:
双管道实现:采用两个管道,如:管道1实现"ping"的读取和写入,管道2实现"pong"的读取和写入。实现过程同思路一,仍然采取父子进程的方式。
(虽然,思路二增加了一定的代码量。但是,避免了进程中调用wait()的做法,实现思路更加易于理解,并且两个管道各司其职不易出现错误)
3.代码实现:
(注意:子进程一定要使用exit(0)退出,否则会出现错误。始终记住,在linux系统中:父进程的wait()一定和子进程的exit()成对出现配合使用。同时,父进程也需要调用exit(0))
思路一代码:
#include "kernel/types.h"
#include "user.h"
int main(int argc,char* argv[]){
//创建两个管道,分别实现ping、pong的读写
int p[2];
pipe(p);
char readtext[10];//作为父进程和子进程的读出容器
//子程序读出
int pid = fork();
if(pid==0){
read(p[0],readtext,10);
printf("%d: received %s\n",getpid(),readtext);
write(p[1],"pong",10);
exit(0);//子进程一定要退出
}
//父程序写入
else{
write(p[1],"ping",10);
wait(0);//父进程阻塞,等待子进程读取
read(p[0],readtext,10);
printf("%d: received %s\n",getpid(),readtext);
//exit(0);//父进程一定要退出
}
return 0;
}
(2)思路二代码大同小异,不过多赘述