命名管道 (有用的特点): 由于它们出现在文件系统中,所以他们可以像平常的文件名一样在命令中使用。
在创建的FIFO文件用在程序设计中之前,我们先通过普通的文件命令来观察FIFO 文件的行为。
实验:访问FIFO文件(首先空文件:/tmp/my_fifo)
(1)首先,尝试读取这个(空的)FIFO文件:如下(第一行是空的,第二行是在写入了字符之后重新读取的结果)
(2)我们可以将读取的数据放在后台执行,这样可以一次执行两个命令:
实验解析:
因为FIFO 中没有数据,所以cat和echo程序被阻塞了,cat等待数据的到来,而echo 等待其他进程读取数据。 (这个是在后台,在前台可能会被中断)
在上面的第三步中,cat进程一开始就在后台被阻塞了,当echo向它提供了一些数据后,cat命令读取这些数据并把他们打印到标准输出上,然后cat程序退出,不再等待更多的数据。
它没有阻塞是因为当第二个命令将数据放入FIFO 后,管道将被关闭,所以cat程序中的read调用返回0字节,表示已经到达文件尾。
说明:与通过pipe 调用创建管道的不同,FIFO 是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须先打开它。FIFO也用open 和close 函数打开和关闭,这与我们前面看到的对文件的操作一样,但它多了一些其他的功能。对FIFO 来说,传递给open 调用的是FIFO的路径名,而不是一个正常的文件。
(1)使用open打开FIFO 文件
打开FIFO的一个主题限制是:程序不能以O_RDWR模式(是什么???)打开FIFO文件进行读写操作,这样做的后果并未明确定义。但这个限制是有道理的,因为我们通常使用FIFO只是为了单向传递数据,所以没有必要使用O_RDWR模式。如果一个管道以读/写方式打开,进程就会从这个管道读回它自己的输出。 (这个模式应该是读写双向操作)不过命名管道应该是为了单向设计的(管道应该都是)
若是确实需要在程序之间双向传递数据,最好使用一对FIFO或管道,一个方向使用一个,或者(但并不常用)采用先关闭再重新打开FIFO的方法来明确的改变数据流的方向。
(本章后面讨论用FIFO 进行双向数据交换的问题)
PS: 管道应该都是用于程序之间单向传递数据。
打开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_ONOBLOCK 分别搭配O_RDONLY 和 O_WRONLY 在效果上的不同,如果没有进程以读方式打开管道,非阻塞写方式的open调用将失败,单非阻塞读方式的open调用总是成功。close调用的行为并不受O_ONOBLOCK标志的影响。
下面的例子是通过使用带O_NONBLOCK 标志的open调用的行为来同步两个进程。(这里没有选择使用多个实例程序的做法,而只是使用一个测试程序fifo2.c ,通过给该程序传递不同的参数来观察FIFO的行为)
源码在github上,
实验解析:
这个 程序能够在命令上指定我们希望使用的O_RDONLY、O_WRONLY 和O_NONBLOCK的组合方式。它会把命令行参数与程序中的常量字符串进行比较,如果匹配,就(用 |= 操作符)设置相应的标志。程序用access 函数俩检查FIFO 文件是否存在,如果不存在就创建它。
(在这个程序中,一直到最后都么有删除这个FIFO 文件,因为我们没有办法知道是否有其他的程序正在使用它。)
(2)不带O_NONBLOCK 标志的O_RDONLY 和O_WRONLY
测试程序可以使用不同的组合,注意:我们将第一陈旭(读取者)放在后台运行;
这个可能是命名管道最常用的方法了,它允许启动读进程,并在open调用中等待,当第二程序打开FIFO文件时,两个程序继续运行。注意:读进程和写进程在open调用处取得同步。
注意:当一个Linux进程被阻塞时,它并不消耗CPU资源,所以这种进程的同步方式对CPU来说是非常有效的。
(3) 带O_NONBLOCK标志的O_RDONLY和不带盖标志的O_WRONLY
读进程执行open调用并立刻继续执行,及时没有写进程的存在。随后写程序开始执行,它也在执行open调用后立刻继续执行,但这次因为FIFO已被读进程打开。
(4) 对FIFO进行读写操作
使用O_NONBLOCK模式会影响到FIFO的read和write调用。
对一个空的、阻塞的FIFO (即没有用O_NONBLOCK标志打开)的read调用将等待,知道有数据可以读时才执行。与此相反,对一个空的,非阻塞的FIFO 的read调用将立刻返回0字节。
对于一个完全阻塞的FIFO的write调用将等待,知道有数据可以被写入时才继续执行。如果FIFO不能接收所有写入的数据,它将按下面的规则执行。
- 如果请求写入的数据的长度小于等于PIPE_BUF字节,调用失败,数据不能写入。
*如果请求写入的数据的长度大于PIPE_BUF字节,将写入部分数据,返回实际吸入的字节数,返回值也可能是0.
FIFO 的长度是需要考虑的一个很重要的因素。系统对任意时刻在一个FIFO中可以存在的而数据长度是有限制的。它由#define PIPE_BUF 语句定义,通常可以在头文件limits.h 中找到他。在Linux和许多其他类UNIX 系统中,它的值通常是4096 字节,但在某些系统中他可能会小到512 字节。系统规定:在一个以O_WRONLY 方式(即阻塞方式)打开的FIFO 中,如果写入的数据长度小于等于PIPE_BUF,那么或者写入全部字节,或者一个字节都不写入。
虽然,对只有一个FIFO写进程和一个FIFO读进程的简单情况来说,这个显示并不是非常重要,但只使用一个FIFO并允许多个不同的程序向一个FIFO 读继承发送请求的情况很常见的。如果几个不同的程序尝试同时向FIFO写数据,能否拨正来自不同陈旭的数据块不相互交错就非常关键了。也就是每个写操作都必须是“原子化”,如果达到这一点?
如果能够保证所有的写请求是发往一个阻塞的FIFO的,并且每个写请求的 数据长度小于等于PIPE_BUF字节,系统就可以确保数据决不会交错在一起。通常静每次通过FIFO传递的数据长度限制为PIPE_BUF 字节是个好方法,除非你只使用了一个写继承和一个读进程。
使用FIFO实现进程间通讯。
(管道中的进程通讯是在亲缘的进程之间进行通讯)
命名管道可以在没有亲缘关系的命名管道进行通信的,我们需要用到两个独立的程序;
【分别是fifo3.c(生产者) 、fifo4.c(消费者)】