Linux进程间通信之有名管道编程
无名管道虽然可以在两个进程间交换数据,但也仅限于在相关的程序之间传递数据,即这些程序是由一个共同的祖先进程启动的。但如果我们想在不相关的进程间交换数据,这还是很不方便。
我们可以用有名管道来完成这项工作。有名管道(FIFO)是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但它的行为却和无名管道类似。
有名管道的创建
我们可以在命令行上创建命名管道,也可以在程序中创建它。命令行上用来创建有名管道如下所示:
mknod filename p
mkfifo filename
其中,并不是所有的UNIX系统都支持mknod命令,推荐使用mkfifo。
在程序中,我们可以使用两个不同的函数调用,如下:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);
mknod函数可以建立许多特殊类型的文件。要想通过这个函数创建一个有名管道,唯一具有可移植性的方法是使用一个dev_t类型的值0,并将文件访问模式与S_IFIFO按位或。
下面的例子介绍mkfifo函数的简单使用:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("my_fifo", 0777);
if(res == 0) printf("FIFO created\n");
exit(EXIT_SUCCESS);
}
这个程序用mkfifo函数创建了一个特殊的文件,虽然我们要求文件的模式是0777,但它被用户掩码设置给改变了,这与普通文件的创建是一样的,所以文件的最终模式是755。
访问FIFO文件
命令行访问
- 首先我们先来尝试读这个刚刚创建的FIFO文件my_fifo:
可以看到,在尝试读my_fifo时,my_fifo为空,所以该命令被阻塞挂起以等待数据出现在my_fifo中。为何阻塞而不是结束进程,在下面会介绍。 - 现在尝试向my_fifo写数据,当前shell被阻塞,所以必须用另一个shell来执行下面的命令:
命令执行完后将会看到cat命令产生输出。如果不向my_fifo写入任何数据,cat会一直被挂起,直到中断它。
我们也可以把cat命令放到后台执行,这样即可一次执行两个命令:
程序中访问FIFO文件
与通过pipe调用创建无名管道不同,FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须打开它,FIFO也用open和close函数打开和关闭,这和对文件的操作是一样的,但它多了一些其他的功能。对FIFO来说,传递给open调用的是FIFO的路径名,而不是一个正常的文件。
使用open打开FIFO文件
打开FIFO的一个主要限制是,程序不能以O_RDWR模式打开FIFO文件进行读写操作,这样做的后果并未明确定义。但这个限制是有道理的,因为我们通常使用FIFO只是为了单向传递数据,所以没有必要使用O_RDWR模式。如果一个管道以读/写方式打开,进程就会从这个管道读回它自己的输出。
如果确实需要在程序之间双向传递数据,最好使用一对FIFO或管道, 一个方向使用一个,或者(但并不常用)采用先关闭再重新打开FIFO的方法来明确地改变数据流的方向。
打开FIFO文件和打开普通文件的另一点区别是,对open_flag (open函数的第二个参数)的O_NONBLOCK选项的用法。使用这个选项不仅改变open调用的处理方式,还会改变对这次open调用返回的文件描述符进行的读写请求的处理方式。
O_RDONLY、O_WRONLY和O_NONBLOCK标志共用以下四种组合方式:
open(const char *path, O_RDONLY);
- 在上面这种情况下,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则他不会返回。这与上面介绍的cat访问FIFO的例子类似。
open(const char *path, O_RDONLY | O_NONBLOCK);
- 加上O_NONBLOCK后,即使没有其他进程以写方式打开FIFO,这个open调用也将成功并立刻返回。
open(const char *path, O_WRONLY);
- 在上面这种情况下,open调用将阻塞,除非有一个进程以读方式打开同一个FIFO。
open(const char *path, O_WRONLY | O_NONBLOCK);
- 这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO文件, open调用将返回一个错误-1并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO文件,那么我们就可以通过它返回的文件描述符对这个FIFO文件进行写操作。
请注意O_NONBLOCK分别搭配O_RDONLY和O_WRONLY在效果上的不同,如果没有进程以读方式打开管道,非阻塞写方式的open调用将失败,但非阻塞读方式的open调用总是成功。close调用的行为并不受O_NONBLOCK标志的影响。