进程间通信(五)

有名管道:FIFO

到目前为止,我们只是可以在两个相关的程序之间传递数据,也就是说,由一个共同的祖先进程启动的程序。通常这并不是十分方便,因为我们希望不相关的进程也可以交换数据。

我们可以使用FIFO来实现这个操作,通常称之为有名管道。一个有名管道是在文件系统中作为名字存在的一个特殊的文件类型(记住,在Linux一切皆文件),但是行为类似于我们已经了解的无名管道。

我们可以通过命令行或是在一个程序内部创建有名管道。由于历史原因,用于创建有名管道的命令为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, mode_t mode | S_IFIFO, (dev_t) 0);

类似于mknod命令,我们可以使用mknod函数创建许多特殊的文件类型。使用值为dev_t类型的0值以及使用S_IFIFO的或文件访问模型是创建有名管道函数的唯一可移植用法。在我们的例子中我们会使用简化的mkfifo。

试验--创建有名管道

作为fifo1.c,我们只需要输入下面的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.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);
}

我们可以用下面的命令来查看这个管道:

$ ls -lF /tmp/my_fifo
prwxr-xr-x 1 rick users 0 July 10 14:55 /tmp/my_fifo|

注意,输出的第一个字符是p,表明是一个管道。后面的|是ls命令的-F选项添加上的,也表明是一个管道。

工作原理

程序使用mkfifo函数创建一个特殊的文件。尽管我们请求一个0777的模式,但是这个模式被用户掩码设置进行了修改,就如同在普通的文件创建中一样,所以最终的文件模式为755。如果我们的umask设置不同,例如为0002,我们就会看到不同的权限。

我们可以使用rm命令或是在一个程序中通过使用unlink系统调用就如同移除一个普通文件一样删除FIFO。

访问FIFO

有名管道的一个非常有用的特性就在于,因为他们出现在文件系统中,我们可以像平时使用文件名一样来使用他们。在我们使用我们创建的FIFO进行更多的编程之前,我们可以使用通常的文件命令来探索FIFO文件的行为。

试验--访问FIFO文件

1 首先,让我们试着读取(空)FIFO文件:

$ cat < /tmp/my_fifo

2 现在试着向FIFO写入数据。我们需要使用另一个不同的终端,因为第一个终端现在正被挂起,等待数据出现在FIFO中。

$ echo "sdsdfasdf" > /tmp/my_fifo

我们可以看到由cat命令输出的结果。如果我们没有向FIFO发送任何数据,cat命令就会一直挂起直到我们中断他,通常是使用Ctrl+C。

3 我们可以通过将第一个命令放入后端而同时完成两个操作:

$ cat < /tmp/my_fifo &
[1] 1316
$ echo “sdsdfasdf” > /tmp/my_fifo
sdsdfasdf
[1]+ Done cat </tmp/my_fifo
$

工作原理

因为在FIFO中没有数据,cat与echo程序就会阻塞,分别等待数据到达与某个进程读取数据。

看一下第三步,cat进程初始在后台被阻塞。当echo命令使得一些数据可用时,cat命令就会读取数据并将其输出到标准输出。注意到cat命令会立即退出而不会等待更多的数据。他并没有阻塞,因为第二个命令将数据完全放入FIFO中之后,管道将会被关闭,所以在cat程序中的read调用会返回0,表明文件的结束。

现在我们已经看到当我们使用命令行程序FIFO是如何动作的,现在让我们更详细的了解一下程序接口,这是当我们访问FIFO时允许我们在读取与写入的动作上执行更多控制的地方。

注意:与pipe调用所创建的管道不同,FIFO是作为一个命名文件而存在的,而不是一个打开的文件描述符,而且他在可以读取与写入之后必须打开。我们可以使用我们在前面对文件进行操作时的open与close函数来打开与关闭一个FIFO。open调用需要传递一个FIFO的路径名,而不是一个常规文件。

使用open打开FIFO

打开FIFO的主要限制在于使用O_RDWR模型的程序也许会并不会打开一个FIFO进行读取与写入。如果一个程序这样做,结果是未定义的。这是一个合理的限制,因为通常我们使用FIFO只是在一个方向上传递数据,所以我们并不需要O_RDWR模型。进程将会读取其自己的输出端。

如果我们希望在程序之间进行双向的数据传递,最好是使用一对FIFO,或是管道,每个用于一个方向,或是通过显示的关闭与重新打开FIFO来改变数据流的方向。我们将会在本章的稍后部分来探讨双向的数据交换。

打开一个FIFO与打开一个常规文件的另一个不同就在于带有O_NONBLOCK选项的打开标记(open的第二个参数)。使用这个打开模式不仅会改变open调用是如何处理的,而且会改变在返回的文件描述符上read与write请求是如何处理的。

O_RDONLY,O_WRONLY与O_NONBLOCK标记有四种合法的组合。我们将会依次考虑每一个。

open(const char *path, O_RDONLY);

在这种情况下,open调用会阻塞;直到有一个进程打开同一个FIFO用于写入时这个调用才会返回。这种情况类似于前面的cat例子。

open(const char *path, O_RDONLY | O_NONBLOCK);

open调用将会成功而且会立即返回,尽管此时的FIFO还没有被任何进程打开用于写入数据。

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调用就会失败,但是非阻塞的读取调用不会失败。O_NONBLOCK标记不会影响close调用的行为。

试验--打开FIFO文件

下面我们来看一下如何使用带有O_NONBLOCK标记的open来同步两个进程。我们不会使用多个例子程序,相反我们会编写一个测试程序,fifo2.c,这个程序会通过传递不同的参数使得我们来探讨FIFO的行为。

1 我们由头文件与#define开始,并且检测所提供的命令行参数的正确个数:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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;

if (argc < 2)
{
fprintf(stderr, "Usage: %s <some comination of O_RDONLY O_WRONLY O_NONBLOCK> \n", *argv);
exit(EXIT_FAILURE);
}

2 假设程序传递了正确的参数,我们现在由这些参数设置open_mode的值:

argv++;
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;

argv++;
if(*argv)
{
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;
}

3 我们现在检测FIFO是否存在,如果需要我们要进行创建。然后FIFO会被打开并产生输出。最后FIFO会被关闭。

if (access(FIFO_NAME, F_OK) == -1)
{
res = mkfifo(FIFO_NAME, 0777);
if(res != 0)
{
fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
exit(EXIT_FAILURE);
}
}

printf("Process %d open FIFO\n", getpid());
res = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), res);
sleep(5);
if(res != -1) (void)close(res);
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}

工作原理

这个程序允许我们在命令行上指定我们希望使用的O_RDONLY,O_WRONLY与O_NONBLOCK的组合。通过将已知的字符串与命令行参数进行比较,如果字符串匹配就会设置相应的标记。这个程序使用access函数来检测FIFO是否已经存在,如果需要则会创建这个文件。

我们并没有销毁FIFO,因为我们并没有办法来区分是否还有其他的程序正在使用FIFO。

带有O_NONBLOCK的O_RDONLY与O_WRONLY

现在我们有了测试程序,让我们来试验一些组合;注意我们将文件程序放入后台:

$ ./fifo2 O_RDONLY &
[1] 152
Process 152 opening FIFO
$ ./fifo2 O_WRONLY
Process 153 opening FIFO
Process 152 result 3
Process 153 result 3
Process 152 finished
Process 153 finished

这是有名管道的最通常用法。他会使用读取进程启动并且在open调用中等待,然后当第二个程序打开FIFO时,两个程序都会继续。注意在open调用时,读取进程与写入进程都进行了同步。

注意:当一个Linux进程被阻塞时,他并不消耗CPU资源,所以这种进程同步方法是很有效的。

带有O_NONBLOCK的O_RDONLY与O_WRONLY

这一次,读取进程会执行open调用并立即继续,尽管并不存在写入进程。写入进程也会在open调用之后立即继续,因为FIFO已经打开用于读取。

$ ./fifo2 O_RDONLY O_NONBLOCK &
[1] 160
Process 160 opening FIFO
$ ./fifo2 O_WRONLY
Process 161 opening FIFO
Process 160 result 3
Process 161 result 3
Process 160 finished
Process 161 finished
[1]+ Done fifo2 O_RDONLY O_NONBLOCK

这两个例子是open模式的最通常组合。我们可以使用其他的一些组合来试验这个程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值