管道(pipe)概述:(怎样使用管道实现父子进程间通信)不相关的两个进程无法通过无名管道进行进程间通信。
管道又称无名管道(没有名字),无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。
管道是最古老的UNIX IPC方式,其特点是:
1> 半双工,数据在同一时刻只能在一个方向上流动,读写不能同时进行(单工-收音机)
2> 由两个文件描述符引用,一个表示读端fd【0】,一个表示写端fd【1】;
数据只能从管道的一端写入,从另一端读出。
3> 写入管道中的数据遵循先入先出的规则。
4> 管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
5> 管道不是普通的文件,不属于某个文件系统,只存在于内存中。
6> 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
7> 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。即数据一旦被读走,便不在管道中存在,不可反复读。
8> 管道没有名字,只能在具有公共祖先的进程之间使用。
Linux shell允许重定向,而重定向使用的就是管道。例如:ls | more
创建管道的那个进程(父进程)具有管道的两个文件描述符,继承这个进程(子进程)也具有这两个文件描述符,所以就可以对管道进行操作。父进程创建管道—>得到管道的两个读写端->创建子进程,子进程继承文件描述符(管道的两个读写端)->父子进程进行进程间通信。
实际上,管道是一个固定大小的缓冲区,在linux中,该缓冲区的大小是4k(8×512Bytes),即一页。
创建管道:
#include<unistd.h>
int pipe(int filedes[2]);
功能:经由参数filedes返回两个文件描述符
参数:
>>filedes为int型数组的首地址,其存放了管道的文件描述符filedes[0],filedes[1].
>>filedes[0]为读而打开,filedes[1]为写而打开管道,filedes[0]的输出是filedes[1]的输入。
返回值:成功返回0,失败返回-1.
从管道中读数据的特点:
1) 默认用read函数从管道中读数据是阻塞的。
2) 调用write函数向管道里写数据,当缓冲区已满时write也会阻塞。
3) 通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到SIGPIPE信号)退出。
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char * argv[])
{
int fd_pipe[2];
char buf1[] = "hello world";
char buf2[15];
pid_t pid;
if(pipe(fd_pipe) < 0)
perror("pipe");
pid = fork();
if(pid < 0){
perror("fork");
exit(-1);
}
if(pid == 0){
write(fd_pipe[1],buf1,strlen(buf1));
_exit(0);
}else{
wait(NULL);//parent process,wait any child process's termination
memset(buf2,0,sizeof(buf2));
read(fd_pipe[0],buf2,sizeof(buf1));
printf("buf2 = [%s]\n",buf2);
}
return 0;
}
运行结果:
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char * argv[])
{
int fd_pipe[2];
char buf[] = "hello world";
pid_t pid;
if(pipe(fd_pipe) < 0)
perror("pipe");
pid = fork();
if(pid < 0){
perror("fork");
exit(-1);
}
if(pid == 0){
int i = 0;
close(fd_pipe[0]);
while(1){
write(fd_pipe[1],buf,strlen(buf));
i++;
printf("i = %d\n",i);
sleep(1);
}
_exit(0);
}else{
int i;
for(i = 0;i < 5;i++){
memset(buf,0,sizeof(buf));
read(fd_pipe[0],buf,sizeof(buf));
printf("read: buf = %s\n",buf);
}
close(fd_pipe[0]);
wait(NULL);//parent process,wait any child process's termination
}
return 0;
}
父子进程都关闭读端之后,写端也将自动关闭,运行结果如下:
读写行为总结
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
读管道:
1. 管道中有数据,read返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
写管道:
1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
2. 管道读端没有全部关闭:
(1) 管道已满,write阻塞。
(2) 管道未满,write将数据写入,并返回实际写入的字节数