Linux进程间通信之管道pipe
本文主要简述管道的相关概念、原理、特质、局限性以及分析管道的通信步骤及原理.
一、概念
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,通过使用pipe函数在内核中开辟一段缓冲区来实现两个进程之间的通信.
二、原理
管道实为内核使用环形队列机制,借助内核缓冲区(4K)实现,是一个典型的生产者-消费者模型.
三、特质
1.管道其本质是一个伪文件(实为内存缓冲区),因此具有IO的特性,其生命周期随进程.
2.管道由两个文件描述符引用,一个表示读端,一个表示写端.
3.管道规定数据从写端流入,从读端流出.
4.管道的属于字节流传输,需要
四、局限性
1.数据不能自己读,自己写.
2.数据一旦被读走,便不会在管道中存在,不可反复读取.
3.管道采用半双工通信方式,因此,数据只能在一个方向上流动.
4.只有在有公共祖先的进程间使用管道.
五、通信步骤及原理
1.父进程使用pipe函数创建一个管道.
当父进程使用pipe函数时,相当于此时打开了两个文件,fd0和fd1,fd[0]和fd[1]都在文件的描述符表中.
2.父进程通过fork函数创建出子进程.
当父进程fork出子进程时,子进程会继承父进程的文件描述符表,因此子进程也能看到
3.父进程关闭读端fd[0],子进程关闭写端fd[1].
4.以文件方式进行通信.
六、函数原型
创建管道主要用到pipe函数,pipe的原型如下:
#include <unistd.h>
int pipe(int pipefd[2]);
参数:一个整型数组,管道创建成功后,pipefd[0]表示管道的读端,pipefd[1]表示管道的写端.
成功返回0,失败返回-1,同时errno被设置.
七、父子进程通信
上文中,我们描述了父子进程通过管道来进行通信的整个过程,现在我们用C语言来实现:
#include<stdio.h>
#include<unistd.h>
int main(){
int fds[2] = {0};
//1.创建管道
if(pipe(fds)==-1){
perror("pipe");
return 1;
}
//2.fork子进程
pid_t pid = fork();
if(pid > 0){ //father
//3.父进程关闭读端,向写端写入数据
close(fds[0]);
char buff[1024]={0};
printf("parent to child#");
fflush(stdout);
ssize_t s = read(0,buff,sizeof(buff)-1);
buff[s-1] = 0;
write(fds[1],buff,s);
close(fds[1]);
}else if(pid==0){ //child
//3.子进程关闭写端,从读端读出数据
close(fds[1]);
char buff[1024]={0};
ssize_t s = read(fds[0],buff,sizeof(buff));
printf("child to receive#%s\n",buff);
close(fds[0]);
}else{
perror("fork");
return 2;
}
wait(NULL); //回收子进程
return 0;
}
上述代码中,我们展示的是父子进程普通的通信情况,除了正常情况之外,我们还需考虑四种异常情况:
八、四种异常情况
为了节省文章篇幅,代码部分省略,读者可以自行测试.
1.父进程不断向管道中写数据,子进程不从管道中读取数据,子进程fds[0]读端保持开启.
这种情况将导致管道被写满,同时父进程被阻塞,直到管道中有空位置.
2.父进程不断向管道中写数据,子进程不从管道中读取数据,子进程关闭了fds[0]读端.
这种情况下,父进程会收到SIGPIPE信号,进而被终止.
3.父进程向管道中写了一些数据,子进程不断从管道中读取数据,父进程fds[1]写端保持开启.
这种情况下,子进程将管道中数据读取完毕之后,会进入阻塞状态.直到管道中有新数据产生.
4.父进程向管道中写了一些数据,子进程不断从管道中读取数据,父进程关闭了fds[1]写端.
这种情况下,子进程会读完管道中数据,最后read返回0,就像读到文件末尾一样.