pipe
1.管道由pipe函数创建
2.管道的本质是伪文件(不占用磁盘空间,只占用内存)
3.管道由两个文件描述符的引用,一个fd[0]读,一个fd[1]写
4.数据从管道的写端流入,读端流出
5.管道的原理是内核缓冲区(4k)借助环形队列机制实现
pipe的局限性
1.进程不能自己写数据自己读数据,需要有两端
2.管道中的数据不可以反复读取,一旦读走,管道不复存在
3.管道四半双工通信,数据只能单向移动
4.只有在拥有共同祖先的时候才能使用管道
pipe函数的定义
pipe函数创建并打开管道
#include <unistd.h>
int pipe(int fd[2])
fd[0]:读端
fd[1]:写端
返回值:
成功:0
失败:-1, 并设置errno
父子进程利用管道实现通信
1.利用pipe函数创建一个管道;
2.父进程利用fork函数创建子进程,子进程与父进程共享文件描述符,也就是说子进程也有pipe的读端和写端;
3.父进程去关闭读端,子进程关闭写端,如下图,这样就可以实现父子进程之前的通信。
管道的读写行为
读行为:
1.管道中有数据:
read返回实际读到的字节数
2.管道中无数据:
a.无写端,read返回0(类似于读到文件尾部)
b.有写端,read阻塞等待
写行为:
1.管道被关闭(无读端),进程异常终止(SIGPIPE信号)
2.有读端:
a.管道未满,write将数据写入,并返回实际写入的字节数
b.管道已满,write阻塞
案例一:
利用pipe实现简单的父子进程之间的通信
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2];
pid_t pid;
char buf[1024];
int ret=pipe(fd);
if(ret==-1)
{
perror("pipe error");
exit(1);
}
//创建子进程,实现子进程向父进程发送数据
pid=fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}else if(pid==0){
//进入子进程
//1.子进程关闭读端
close(fd[0]);
const char *senddata="hello pipe\n";
int ret=write(fd[1],senddata,strlen(senddata));
if(ret==-1)
{
perror("write error");
exit(1);
}
}else if(pid>0){
//进入父进程
//2.父进程关闭写端
close(fd[1]);
int ret=read(fd[0],buf,sizeof(buf));
if(ret==-1)
{
perror("read error");
exit(1);
}
int ret2=write(STDOUT_FILENO,buf,ret);
if(ret2==-1)
{
perror("write error");
exit(1);
}
}
return 0;
}
案例2:
利用父子进程实现 ls | wc -l 命令
子进程实现 ls 命令,父进程实现 wc -l命令
/*
* 利用pipe父子进程通信实现ls | wc-l
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main()
{
//变量声明
pid_t pid;
char buf[1024];
int fd[2];
int ret=pipe(fd);
if(ret==-1)
{
perror("pipe error");
exit(1);
}
pid=fork();
if(pid==0)
{
//子进程
//子进程关闭读端,去写入到管道中
//fd[0] 读端 ,fd[1]写端
close(fd[0]);
dup2(fd[1],STDOUT_FILENO); //将原本要输入到屏幕上的数据输入到管道中
execlp("ls","ls",NULL);
perror("execlp error");
exit(1);
}else if(pid>0){
//父进程
//父进程读取管道的内容,然后将fd[0]绑定到标准输入
//以下代码为了看起来简洁省去了返回值的检查
close(fd[1]); //关闭文件的写端,父进程要读取
dup2(fd[0],STDIN_FILENO);
execlp("wc","wc","-l",NULL); //wc指令输入的内容来自显示屏
}
return 0;
}
案例3:
实现兄弟进程之间的通信
1.利用循环fork产生两个子进程
2.关闭父进程的读写端,子进程1的读端,子进程2的写端
/*
* 利用管道实现兄弟进程之间的通信
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
int main()
{
pid_t pid;
int fd[2],i;
char buf[1024];
//创建管道
int ret= pipe(fd);
if(ret==-1)
{
perror("pipe error");
exit(1);
}
for(i=0;i<2;i++) //父进程出口
{
pid=fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
if(pid==0) //子进程出口
break;
}
if(i==2)
{
//父进程
//父进程需要关闭读端和写端并且需要回收两个子进程
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);
}
else if(i==0){
//第一个子进程负责写
//关闭读端
close(fd[0]);
const char *str="hello brother\n";
int ret= write(fd[1],str,strlen(str));
if(ret==-1)
{
perror("write error");
exit(1);
}
close(fd[1]);
}else if(i==1){
//第二个进程负责读
//关闭写端
close(fd[1]);
int ret=read(fd[0],buf,sizeof(buf));
if(ret==-1)
{
perror("read error");
exit(1);
}
write(STDOUT_FILENO,buf,ret);
close(fd[0]);
}
return 0;
}