众所周知作为UNIX IPC中老的一种形式管道,是所有UNIX系统都提供的一种通信机制,因而它应用的范围非常广泛。例如我们可以使用管道符“|”来连接进程。在Linux系统中,由管道连接起来的进程可以自动运行,就如同在他们有一个数据流一样。根据管道的适用范围将其分为:无名管道(pipe)和有名管道(fifo)。本文主要围绕二者出发,讨论管道通信的机制。
一、无名管道(pipe)
1.什么是管道
一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。类似时空隧道的概念,建立两个进程之间的通讯桥梁。数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
2.无名管道的特性
(1)只能用于具有亲缘关系的进程之间的通信,通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可以通过管道通信。
(2)半双工的通信模式,具有固定的读端和写端:传输方向同时只能是一个方向,。
(3)管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数:但是在文件系统里并不存在pipe对应的文件而且不支持如lseek() 操作。
3.无名管道的创建
无名管道可以由pipe()函数创建
#include
int pipe(int pipefd[2]);
pipe函数调用成功返回0,调用失败返回-1。
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过pipefd参数传出给用户程序两个文件描述符,pipefd[0]指向管道的读端,pipefd[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(pipefd[0]);或者write(pipefd[1]);向这个文件读写数据其实是在读写内核缓冲区。
详细的创建流程:
step1: 父进程创建一个pipe,其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
Step2:父进程fork,子进程继承了父进程的管道
Step3:之后取决于我们想要的数据流方向来关闭相应的端。
4.无名管道读写注意事项
当管道的一端被关闭后:
(1)当读一个写端已被关闭的管道时,在所有数据都被读取后, read返回0,以指示达到了文件结束处(从技术方面考虑,管道的写端还有进程时,就不会产生文件的结束。可以复制一个管道的描述符,使得有多个进程具有写打开文件描述符。但是,通常一个管道只有一个读进程,一个写进程)。
(2)如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错返回,errn设置为EPIPE。在写管道时,常数PIPE_BUF规定了内核中管道缓存器的大小。如果对管道进行write调用,而且要求写的字节数小于等于PIPE_BUF,则此操作不会与其他进程对同一管道(或FIFO)的write相交错。但是,若有多个进程同时写一个管道(或FIFO),而且某个或某些进程要求写的字节数超过PIPE_BUF字节数,则数据可能会与其他写操作的数据相交错。
5.popen和pclose函数
因为常见的操作是创建一个连接到另一个进程的管道,然后读其输出或向其发送输入,所以标准I / O库为实现这些操作提供了两个函数popen和pclose。在这边就不详细说明了,大家感兴趣的化可以查看Man手册中的描述。
6.示例代码
#include
#include
#include
int pid1, pid2;
int main( )
{
int fd[2];
char outpipe[100], inpipe[100];
pipe(fd); /*创建一个管道*/
while ((pid1 = fork( )) == -1);
if (pid1 == 0)
{
sprintf(outpipe, "child 1 process is sending message!");
/*把串放入数组outpipe中*/
write(fd[1], outpipe, 50); /*向管道写长为50字节的串*/
exit(0);
}
else
{
while((pid2 = fork( )) == -1);
if (pid2 == 0)
{
sprintf(outpipe, "child 2 process is sending message!");
write(fd[1], outpipe, 50);
exit(0);
}
else
{
//wait(NULL); /*同步*/
read(fd[0], inpipe, 50); /*从管道中读长为50字节的串*/
printf("%s\n", inpipe);
//wait(NULL);
read(fd[0], inpipe, 50);
printf("%s\n",inpipe);
exit(0);
}
}
return 0;
}
二、有名管道(fifo)
1.有名管道的概念
为何要提出有名管道的说法,目的是为了克服无名管道的不足之处:
(1)无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围
(2)有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
为了这种有名管道,Linux中专门设立了一个专门的特殊文件系统--管道文件,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。但在磁盘上只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样。
2.有名管道的创建
(1)有名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo myfifo
(2)有名管道也可以从程序里创建,相关API有:
#include
#include
int mkfifo(const char *filename,mode_t mode);
mkfifo函数成功返回0,失败返回-1并且设置errno。
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的一个参数是一个已经存在路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数open就可以了。
3.FIFO的open打开规则
O_RDONLY、O_WRONLY和O_NONBLOCK标志共有四种合法的组合方式:
flags=O_RDONLY:open将会调用阻塞,除非有另外一个进程以写的方式打开同一个FIFO,否则一直等待。
flags=O_WRONLY:open将会调用阻塞,除非有另外一个进程以读的方式打开同一个FIFO,否则一直等待。
flags=O_RDONLY|O_NONBLOCK:如果此时没有其他进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。
flags=O_WRONLY|O_NONBLOCK:立即返回,如果此时没有其他进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1。
总而言之:
● 在一个FIFO上打开一个读端
● 在一个FIFO上打开一个写端
4.有名管道的读写规则
有名管道的读写原则和无名管道的读写原则基本一致,主要参考无名管道的读写原则即可。
5.示例代码
使用完成拷贝文件的功能:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m)
do {
perror(m);
exit(EXIT_FAILURE);
} while(0)
int main(int argc, char *argv[])
{
mkfifo("tp", 0644);
int infd = open("Makefile", O_RDONLY);
if (infd == -1)
ERR_EXIT("open error");
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1)
ERR_EXIT("open error");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0)
write(outfd, buf, n);
close(infd);
close(outfd);
return 0;
}
三、无名管道与有名管道的区别与联系
1.PIPE和FIFO的区别:
对于FIFO和无名管道的编码区别:
(1)创建并打开一个管道只需调用pipe。创建并打开一个FIFO则需在调用mkfifo后再调用open。
(2)管道在所有进程终都关闭它之后自动消失。FIFO的名字则只有通过调用unlink才文件系统删除。
FIFO需要额外调用的好处是:FIFO在文件系统中有一个名字,该名字允许某个进程创建个FIFO,与它无亲缘关系的另一个进程来打开这个FIFO。对于管道来说,这是不可能的。
系统规定 :如果写入的数据长度小于等于PIPE_BUF字节,那么或者写入全部字节,要么一个字节都不写入。
在非阻塞的write调用情况下,如果FIFO 不能接收所有写入的数据,将按照下面的规则进行:
(1)请求写入的数据的长度大于PIPE_BUF字节,调用失败,数据不能被写入。
(2)请求写入的数据的长度小于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0。
其中。PIPE_BUF是FIFO的长度,它在头文件limits.h中被定义。在linux或其他类UNIX系统中,它的值通常是4096字节。注意:PIPE_BUF与FIFO容量是有区别的,PIPE_BUF表示可原子的写往一个管道或FIFO的大数据量。PIPE_BUF为4096,但是FIFO的容量为65536.
2.PIPE和FIFO的相同点:
(1)虽然管道,特别是有名管道可以很方便地在双向上打开读写,但其内核实现依然是单向的。严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
(2)pipe, fifo都不支持诸如lseek()等文件定位操作。
(3)对于pipe或者fifo,如果在读端或者写端打开了多个读写端(进程),之间的读写是不确定的,需要通过其他的同步机制实现多进程通讯的同步。