13.6 命名管道:FIFO
至此,还都
只是在相关的程序之间传递数据,即这些数据是由一个共同的祖先进程启动的。
但如果想在不相关的额进程之间交换数据,这还不是很方便。
可以
用FIFO文件来完成这项工作,它通常也被称为命名管道(named pipe)。命名管道是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但它的行为却和此前见过的没有名字的管道类似。
可以在命令行上创建命名管道,也可以在程序中创建它。过去,命令行上用来创建命名管道的程序是mknod,如下所示:
$ mknod filename p
但mknod命令并未出现在X/Open规范的命令列表中,所以可能并不是所有的类UNIX系统都可以这样做。推荐使用的命令行命令是:
$ mkfifo filename
在程序中,可以使用两个不同的函数调用,如下所示:
#include
#include
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mod_t mode | S_IFIFO, (dev_t) 0);
与mknod命令一样,可以用mknod函数建立许多特殊类型的文件。要想通过这个函数创建一个命名管道,唯一具有可移植性的方法是使用一个dev_t类型的值0,并将文件访问模式与S_IFIFO按位或。
编写程序fifo1.c.
/*************************************************************************
> File Name: fifo1.c
> Description: fifo1.c程序用mkfifo创建一个特殊文件
> Author: Liubingbing
> Created Time: 2015/7/13 19:33:30
> Other: fifo1.c程序
************************************************************************/
#include
#include
#include
#include
#include
int main()
{
int res = mkfifo("/tmp/my_fifo", 0777);
if (res == 0)
printf("FIFO created\n");
exit(EXIT_SUCCESS);
}
可以用下面的命令来创建和查找管道:
$ ./fifo1
$ ls -lF /tmp/my_fifo
注意,
输出结果中的第一个字符为p,表示这是一个管道。最后的|字符是由ls命令的-F选项添加的,它也表示这是一个管道。
这个程序用mkfifo函数创建一个特殊的文件,虽然要求的文件模式是0777,但它被用户掩埋(umask)设置(在本例中是022)给改变了,这与普通文件的创建是一样的,所以文件的最终模式是755.如果掩码设置与这里不同,比如是0002,那么将看到创建的文件拥有一个不同的权限。
可以像删除一个普通文件那样用rm命令删除FIFO文件,或者也可以在程序中用unlink系统调用来删除它。
13.6.1 访问FIFO文件
命名管道的一个非常有用的特点是:由于它们出现在文件系统中,所以它们可以像平常的文件名一样在命令中使用。在把创建的FIFO文件用在程序设计中之前,先通过普通的文件命令来观察FIFO文件的行为。
访问FIFO文件
首先,尝试用读下面这个(空的)FIFO文件:
$ cat < /tmp/my_fifo
现在,尝试向FIFO写数据,必须用另一个终端来执行下面的命令,因为
第一个命令现在被挂起以等待数据出现在FIFO中。
$ echo "hello world" > /tmp/my_fifo
将
看到cat命令产生输出。如果不向FIFO发送任何数据,cat命令将一直挂起,直到中断它,常用的中断方式是使用组合键Ctrl+C
可以将第一个命令放在后台执行,这样既可一次执行两个命令:
$ cat < /tmp/my_fifo &
$ echo "hello world" > /tmp/my_fifo
因为FIFO中没有数据,所以cat和echo程序都阻塞了,cat等待数据的到来,而echo等待其他进程读取数据。
在上面的第三步中,cat进程一开始就在后台被阻塞了,当echo向它提供了一些数据后,cat命令读取这些数据并把它们打印到标准输出上,然后cat程序退出,不再等待更多的数据。它没有阻塞是因为第二个命令将数据放入FIFO后,管道将被关闭,所以cat程序中的read调用返回0字节,表示已经到达文件尾。
现在已看过用命令行程序访问FIFO的情况,接下来将仔细分析FIFO的编程接口,它使得在访问FIFO文件时更多地控制器读写行为。
与通过pipe调用创建管道不同,
FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须先打开它。FIFO也用open和close函数打开和关闭,这与此前看到的对文件的操作一样,但它多了一些其他的功能。
对FIFO来说,传递给open调用的是FIFO的路径,而不是一个正常的文件。
1.使用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标志共有4种合法的组合方式.
open(const char *path, O_RDONLY);
在这种情况下,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不会返回.这与前面第一个cat命令的例子类似.
open(const char *path, O_RDONLY | 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标志的影响.
打开FIFO文件
下面来看,如何通过使用带O_NONBLOCK标志的open调用的行为来同步两个进程,使用一个测试程序fifo2.c,通过给该程序传递不同的参数的方法来观察FIFO的行为.
程序的开头部分是头文件和#define定义,然后检查是否在命令行提供了正确数目的参数:
编写程序fifo2.c
/*************************************************************************
> File Name: fifo2.c
> Description: fifo2.c程序是一个测试程序,通过给它传递不同的参数来观察FIFO的行为
> Author: Liubingbing
> Created Time: 2015年07月13日 星期一 21时52分15秒
> Other: fifo2.c
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#define FIFO_NAME "/tmp/my_fifo"
int main(int argc, char *argv[])
{
int res;
int open_mode = 0;
int i;
if (argc < 2) {
fprintf(stderr, "Usage: %s < some combination of O_RDONLY O_WRONLY O_NONBLOCK >\n", *argv);
exit(EXIT_FAILURE);
}
/* 通过测试,根据命令参数来设置open_mode的值 */
for (i = 1; i < argc; i++) {
if (strncmp(*++argv, "O_RDONLY", 8) == 0)
open_mode |= O_RDONLY;
if (strncmp(*argv, "O_WRONLY", 8) == 0)
open_mode |= O_WRONLY;
if (strncmp(*argv, "O_NONBLOCK", 10) == 0)
open_mode |= O_NONBLOCK;
}
/* 检查FIFO文件是否存在,如果有必要就创建它 */
if (access(FIFO_NAME, F_OK) == -1) {
/* mkfifo函数创建命名管道 */
res = mkfifo(FIFO_NAME, 0777);
if (res != 0) {
fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO\n", getpid());
/* open函数调用FIFO_NAME文件 */
res = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), res);
sleep(3);
/* 关闭FIFO */
if (res != -1)
(void)close(res);
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
现在
有了测试程序,可以逐个尝试标志的不同组合方式.注意,将第一个程序(读取者)放在后台运行:
2.不带O_NONBLOCK标志的O_RDONLY和O_WRONLY
$ ./fifo2 O_RDONLY &
$ ./fifo2 O_WRONLY
3.带O_NONBLOCK标志的O_RDONLY和O_WRONLY
这次,读进程执行的open调用并立刻继续执行,即使没有写进程的存在.随后写进程开始执行,它也在执行open调用后立刻继续执行,但这次是因为FIFO已被读进程打开.
$ ./fifo2 O_RDONLY O_NONBLOCK &
$ ./fifo2 O_WRONLY
这两个例子可能是open模式的最常见的额组合形式,可以用这个示例程序随意尝试其他组合方式.