没有正确使用FIFO的程序就会发生微妙的问题。如果我们没有任何进程打开某FIFO来写,那么打开该FIFO来读的进程就会阻塞。
如果父子进程都打开同一个FIFO来读,然而没有任何其他进程已打开该文件来写,于是父子进程都会阻塞,这种现象叫死锁(deadlock)
所以打开文件和打开FIFO还是不一样的。
内核为管道和FIFO维护着一个访问计数器,他的值是访问同一个管道或FIFO的打开着的描述符的个数。
有了访问计数器后,客户或者服务器就能成功地调用unlink,尽管该函数从文件系统中删除了所指定的路径名,先前已经打开着的描述符却不受影响。
然而,对其他形式的IPC来说,如System V消息队列,这样的计数器是不存在的。因此要是服务器在向某个消息队列写入了自己的最终消息后删除了改队列,那么当客户端尝试去读取这个最终消息时,该队列可能已经消失了。
用shell命令 echo 把数据写入到管道test.pip中,
用另外一个进程去读取管道中的数据(cat),这个两个命令之间可以间隔任意长时间。表面上,可能会使我们认为即使没有进程打开着的FIFO,数据也会以某种形式保存在该FIFO中。其实不是这样的。
真正的规则:当对一个管道或者FIFO的最终close发生时,该管道中的任何残余数据都会被丢弃。当echo数据到test.pip时,该FIFO的open调用会一直阻塞到我们在以后的某个时刻执行cat命令为止。
mkfifo test.pip #创建一个 FIFO
echo '123' > test.pip
第二个shell
echo '456' > test.pip
第三个shell
echo '789' > test.pip
这几个shell都会阻塞,直到如下shell
cat test.pip
#输出如下:
789
456
123
#然后上面三个shell阻塞取消,进入正常输入模式
所以说,管道或者FIFO不能保存数据,如果要写入,必然是有进程在读,如果要读,必然是有进程在写。不然就会阻塞,直到阻塞取消,才能读写。虽然管道的打开关闭读写的函数和普通文件一样,但是结果是不一致的。比如,以写的方式打开一个文件,然后关闭文件,文件内容就清空了。如果是管道或者FIFO,以写的方式打开的进程们都会被阻塞,知道有进程来读取,这时这些阻塞的open调用才会进入正常模式,依次往FIFO中写入数据,先进先出,被读进程取出。
当然以上都是管道的默认行为,可以通过设置非阻塞标志来影响一个管道或FIFO的行为
当前操作 | 管道或FIFO的现有打开操作 | 阻塞(默认) | O_NONBLOCK设置 |
---|---|---|---|
open FIFO只读 | FIFO打开来写 | 返回成功 | 返回成功 |
同上 | FIFO不是打开来写 | 阻塞到FIFO打开来写为止 | 返回成功 |
open FIFO只写 | FIFO打开来读 | 返回成功 | 返回成功 |
同上 | FIFO不是打开来读 | 阻塞到FIFO打开来读为止 | 返回ENXIO错误 |
从空管道或空FIFO read | 管道或者FIFO打开来写 | 阻塞到管道或FIFO中有数据或者管道或FIFO不再为写打开着为止 | 返回EAGAIN错误 |
同上 | 管道或者FIFO不是打开来写 | read返回0(文件结束符) | read返回0(文件结束符) |
往管道或FIFO中write | 管道或FIFO打开来读 | (一般正常,若干规则) | (一般正常,若干规则) |
同上 | 管道或FIFO不是打开来读 | 给线程产生SIGPIPE | 给线程产生SIGPIPE |