管道/FIFO注意事项
管道
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
数据一旦被读走,便不在管道中存在,不可反复读取。
缓冲区是有限
由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
只能在有公共祖先的进程间使用管道。
所传送的是无格式字节流
关闭未使用的管道文件描述符不仅仅是为了确保进程不会耗尽其文件描述符的限制——-这对于正确使用管道是非常重要的。 使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志): 1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。 2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。 3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。 4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
https://blog.csdn.net/oguro/article/details/53841949
管道在它的引用计数(访问同一个管道的打开着的描述符的个数)为0后消失。
FIFO
用mkfifo函数创建一个命名管道,若已存在则返回错误。
函数参数中,第一个为文件的相对路径或绝对路径,表明在系统中创建一个管道文件,第二个参数为文件的权限信息。
创建好管道文件之后,就可以使用了,因为是文件,所以可以打开,在一个程序中打开文件,如果以读写方式打开就不会阻塞,如果以只读方式打开就会阻塞直到有以写方式打开此文件为止,同样,如果以只写方式打开就会阻塞直到有读方式打开文件为止。 (O_RDWR mode 打开会绕开 FIFO 的阻塞行为, 因为其不具有可移植性,开发人员应该避免使用, 而是应该是用O_NONBLOCK的方式进行,所以书上是写只能 只读 或者只写)
linux 默认read/write操作是阻塞的, 可以在打开的时候设置O_NONBLOCK
为非阻塞(或者之后使用 fcntl 函数进行设置)。
内核为管道维护了一个访问计数, 统计打开文件描述符的个数, 调用了unlink 函数后, 如果计数不为0, 不会直接删除,会等到最后一个 close 调用使得计数为0, 才调用删除 FIFO, 如果没有调用 unlink, 即使计数为0, 也不会删除FIFO。
但是当管道最后一个打开的文件描述符被关闭,其中的数据被丢弃。删除FIFO是指删除这个路径名。unlink是会直接删除这个路径名,但是仍打开着的描述符不受影响。 实际上FIFO的路径名也会使得引用计数+1,因此上面那段文字就很好理解了。
打开的时候要小心顺序,可能会造成死锁。DoS型攻击就是依靠这个原理的。
在设置管道文件的权限时,需要考虑umask的影响。
https://blog.51cto.com/2627lounuo/1762949
FIFO是一种只能在单台主机上使用的IPC模式,
请求读出的大小大于当前可用大小时,则只返回可用的数据。
请求写入大小小于或等于 PIPE_BUF时,write保证是原子的。如果大于,则不保证原子。 如果设置了o_NONBLOCK 待写的字节数小于等于PIPE_BUF: 如果管道或FIFO中没有足以存放所有请求字节数的空间,那么立即返回一个EAGAIN错误。(因为保证不了原子性,所以返回错误) 待写的字节数大于PIPE_BUF 如果管道或FIFO中至少有1字节。那么写入能容纳的字节,同时把字节数作为返回值。(因为不需要保证原子性,所以直接写入就行) 如果管道或FIFO已满,则立即返回一个EAGAIN错误。
如果向一个没有为读打开着的管道或FIFO写入,则内核产生一个SIGPIPE信号。该信号默认是终止进程
如果向一个没有为写打开着的管道或FIFO读入,则要么阻塞(等待写描述符且等待写入),要么返回EOF(原本的写描述符关闭了),如果再有写的描述符打开,则继续阻塞。
所以有个技巧就是,服务器的写描述符不关闭。这样当客户端close之后就不会出现服务端read到0,然后被迫重打开FIFO阻塞到read。如果服务器的写描述符不关闭,则客户端都close之后,read会阻塞直到来了新的客户端。
管道及FIFO限制
OPEN_MAX 一个进程在任意时刻能打开的最大描述符数
PIPE_BUG 可原子地写往一个管道或FIFO的最大数据量
他们都是数据流,数据流,流。类似socket的东西