进程间的通信-匿名管道pipe
管道:
实现原理: 内核借助环形队列机制,使用内核缓冲区实现。
特质:
1. 伪文件(不占用磁盘空间)
2. 管道中的数据只能一次读取。
3. 数据在管道中,只能单向流动。
局限性:
1. 自己写,不能自己读。(不能同时进行读写操作)
2. 数据不可以反复读。
3. 双向半双工通信。(可以读也可以写,但同一进程只能是其中的一种)
4. 血缘关系进程间可用。
pipe函数:
int pipe(int fd[2]);
函数功能:创建,并打开管道。
传出参数: fd[0]: 读端文件描述符。 fd[1]: 写端文件描述符。
返回值: 成功: 0; 失败: -1 errno
管道的读写行为:
读管道:
-
管道有数据,read返回实际读到的字节数。
-
管道无数据: 1)无写端,read返回0 (类似读到文件尾)
2)有写端,read阻塞等待。
写管道:
-
无读端, 异常终止。 (SIGPIPE导致的)
-
有读端: 1) 管道已满, 阻塞等待(一般不会出现这种情况,当管道的buf满了时,内核会自动扩容)
2) 管道未满, 返回写出的字节个数。
PS:
pipe(int fd[2])函数的传出参数是2个int型的文件描述符,故对管道进行读写操作的函数是:
写操作:
int write(fd[1],str, strlen(str)); //将str的数据写入管道
读操作:
int read(fd[0]),buf, sizeof(buf);//将管道的数据读取到buf中
关闭:
close(fd[0]); //关闭读端
close(fd[1]); //关闭写端
父子进程共享文件描述符,在使用管道时,父进程如果想回收在进行read()的子进程时,父进程需要先**close(fd[1]);(关闭写端)**否则子进程会一直阻塞等待其他进程写入数据,进行管道读操作,所以子进程不会结束,所以父进程wait(NULL)时,也无法对子进程进行回收,会一直阻塞。(原因:读操作时,如果管道没有数据,则查看是否有写端存在,如果有,则阻塞等待写入;如果无,则返回0)
Linux命令创建管道文件:mkfifo 文件名
(fifo对应的是first in first out)
创建出来的管道文件大小为0;
管道文件对应的属性是p。(-:普通文件;d:目录文件;l:符号连接文件)
附一段实现代码:功能实现ls |wc -l
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//实现ls -rlt|wc -l功能(父进程实现ls -rlt,将数据通过管道传个子进程,子进程实现wc -l)
int main(){
int iRet = 0;
pid_t pid = 0;
pid_t wpid = 0;
int fd[2]={0,0};
char str[100]={0};
char sRcvBuf[1024+1]={0};
int i = 0;
int j = 0;
//创建并打开管道
iRet = pipe( fd );
if( -1 == iRet ){
perror( "pipe error:" );
exit (-1);
}
printf( "pipe successed!read:fd[0]=%d,write:fd[1]=%d\n", fd[0], fd[1] );
//父子进程共享文件描述符
for( i = 0; i < 2; i++ ){
pid = fork( );
if( pid == -1 ){
perror( "fork error:" );
exit(-1);
}else if( pid == 0 ){
break;
}
printf( "fork success;pid=%d\n", pid );
}
if( i == 2 ){ //父进程
//管道规则:一个进程只能读或者只能写,不能同时读写
//阻塞等待子进程结束,回收子进程
sleep(2);
close( fd[0] ); //关闭读
close( fd[1] ); //关闭写,先关闭,不然第2个子进程的读不会结束(当所有的写端关闭后,读端才会结束,否则会阻塞等待)
printf( "this is parent\n" );
for( j=0;j<2;j++ ){
wpid = wait(NULL);
if( wpid == -1 ){
perror( "wait error:" );
}
printf( "wait pid = %d\n", wpid );
}
printf( "parent close success\n" );
}else if( i == 0 ){ //第1个子进程;执行ls
//写数据到管道
printf( "into 1th child\n" );
close( fd[0] );
dup2( fd[1], STDOUT_FILENO );
execlp( "ls", "ls", "-lrt", NULL );
}else if( i == 1 ){ //第2个子进程:执行wc -l
printf( "into 2th child\n" );
sleep(1);
//wc命令输入后,会要求继续输入数据,然后crtl+D结束,然后统计出行数
close(fd[1]);
dup2( fd[0], STDIN_FILENO );
execlp( "wc", "wc", "-l", NULL );
}
exit(0);
}