linux程序设计——命名管道FIFO(第十三章)

来源:互联网 发布:淘宝号能买到吗 编辑:程序博客网 时间:2024/05/28 05:15


13.6    命名管道:FIFO

至此,还都只是在相关的程序之间传递数据,即这些数据是由一个共同的祖先进程启动的但如果想在不相关的额进程之间交换数据,这还不是很方便。
可以用FIFO文件来完成这项工作,它通常也被称为命名管道(named pipe)。命名管道是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但它的行为却和此前见过的没有名字的管道类似。
可以在命令行上创建命名管道,也可以在程序中创建它。过去,命令行上用来创建命名管道的程序是mknod,如下所示:
$ mknod filename p
但mknod命令并未出现在X/Open规范的命令列表中,所以可能并不是所有的类UNIX系统都可以这样做。推荐使用的命令行命令是:
$ mkfifo filename
在程序中,可以使用两个不同的函数调用,如下所示:
#include <sys/types.h>#include <sys/stat.h>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 <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>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 <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#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模式的最常见的额组合形式,可以用这个示例程序随意尝试其他组合方式.


0 0
原创粉丝点击