我需要清空命名管道吗?

3
我不知道命名管道是否被缓冲,因此出现了这个问题。
手册页面上写道:https://linux.die.net/man/3/mkfifo
FIFO特殊文件类似于管道……任何进程都可以像普通文件一样打开它进行读取或写入。
管道没有缓冲区,因此不需要刷新。但是在普通文件中,我会fflush(或fsync)文件描述符。
那么命名管道呢?

1
“Pipes are not buffered” 你真的确定吗?此外,基于C标准库的IO默认带有额外的缓冲层。 - Serge Ballesta
我没有考虑管道中的二级存储,但是write系统调用可能会使用系统缓冲区,并且只有在其内部缓冲区已满时才将输出刷新到管道。 - Serge Ballesta
@SergeBallesta,我认为你刚刚在两件不同的事情之间转换了。一方面,C标准库的I/O子系统(stdio)确实通常提供缓冲。这将是fwrite()fprintf()等,但不是 write()系统调用。write()不是stdio的一部分,也没有被C语言标准规定。相反,它属于POSIX系统接口之一。 - John Bollinger
1
@rkioji,今天FIFO和管道都在内核核心内存中完成所有工作。 但这不是很久以前发生的事情。 最初,管道是在文件系统上实现的,并且它们消耗了一个inode(它们继续这样做,用于锁定进程并等待某些内容 - inode被完全的“写/读”系统调用锁定),最初消耗该inode的直接块。 - Luis Colorado
@rkioji,看一下我的回答,你就会知道你的停止数据在哪里了。它是 <stdio.h> 隐藏并且没有发送缓冲数据,因为缓冲区没有满,<stdio.h> 在缓冲区填满或者你强制执行 fflush(3) 之前不会发送它。 - Luis Colorado
显示剩余4条评论
2个回答

6
管道不会缓存,无需刷新。实际上,对于大多数目的来说,管道只是缓存,因为没有底层设备接收数据,所以刷新它们是没有意义的。此外,虽然POSIX没有明确禁止管道I/O的额外缓存,但它确实提供了足够的行为要求,我认为除非通过fsync()是否成功来确定是否发生了这样的缓存,否则没有任何方法可以从观察中确定是否发生了这样的缓存。换句话说,即使有额外的缓存,也不应该需要对管道端进行fsync()操作。在普通文件中,我会使用fflush(或fsync)文件描述符。不,你不会fflush()文件描述符。fflush()操作的是流,由FILE对象表示,而不是文件描述符。这是一个关键的区别,因为大多数流都在C库级别上进行缓冲,与底层文件的性质无关。正是这个库级别的缓冲区与fflush()交互。您可以通过setvbuf()函数控制流的库级别缓冲模式。在那些提供它的系统上,fsync()在不同的、更低的级别上运行。它指示操作系统确保之前写入指定文件描述符的所有数据已经传递到底层存储设备。换句话说,它刷新操作系统级别的缓冲区。请注意,您可以通过fdopen()函数将流包装在管道端文件描述符周围。这并不会使管道需要刷新,就像以前一样,但流默认情况下将被缓冲,因此刷新将与其相关。还要注意,一些存储设备执行自己的缓冲,因此即使数据已经移交给存储设备,也不能确定它们是否立即持久。关于命名管道怎么样?上面关于流I/O与POSIX基于描述符的I/O的讨论在这里也适用。如果您通过流访问命名管道,则其与fflush()的交互取决于该流的缓冲方式。但我想你的问题更多地涉及操作系统级别的缓冲和刷新。POSIX似乎没有提供太多具体的信息,但由于您标记了[linux]并在问题中引用了Linux手册页面,所以我作出了以下回应:管道和FIFO之间唯一的区别是它们创建和打开的方式。完成这些任务后,管道和FIFO的I/O具有完全相同的语义。

(Linux 管道(7) 手册页面。)


是的,我的问题更多关于操作系统级别的缓冲。在管道和FIFO上进行I/O具有完全相同的语义,但这对我来说并不是很清楚。它们可能具有相同的语义,但行为不同。 - rph
1
@rkioji,你关注的引用部分不正确。同义词位于主要观点之后,即“管道和FIFO之间唯一的区别在于它们的创建和打开方式”。然而,拥有相同的语义并不意味着两者会有不同的行为。这几乎意味着完全相反。 - John Bollinger
有没有办法在不进行读操作的情况下清空管道/ FIFO 中的数据? - ixnisarg
@ixnisarg,拒绝挂起的输入不是“清空”,因此不是本问答讨论的重点。话虽如此,没有特殊机制可以排除管道中的数据。如果您想这样做,那么您只需从管道中读取数据并忽略它就可以了。然而,这有些冒险,因为一个人如何知道要拒绝多少数据呢?“目前在管道中的所有数据”不是非常可靠的衡量标准。 - John Bollinger
然而,当管道不再被任何进程打开进行读取或写入时,它就会停止存在,并且任何未读取的数据都将丢失。这同样适用于命名管道和无名管道,可以通过文件系统条目来创建和访问命名管道中的管道部分。 - John Bollinger

2
我不是很明白你想问什么,但是就像其他人已经告诉过你的那样,管道只不过是缓冲区而已。
从历史上来看,FIFO(或管道)会消耗用于维护它们的inode的直接块,并且它们会绑定到某个文件(无论它是否有名称)在某个文件系统中。
如今,我不知道fifo的确切实现细节,但基本上内核会缓冲所有写入数据,但读者还没有读取的数据。 FIFO具有上限(系统定义)的缓冲区量,但通常失败于10-20KB的数据。
内核进行了缓冲,但是写入者和读者之间没有延迟,因为一旦写入者在管道上写入,内核就会唤醒等待数据的所有读者。反之亦然,在管道充满数据的情况下,只要一个读者使用它,所有写入者就会被唤醒以允许再次填充它。
总之,你关于刷新的问题与管道无关(好吧,不是完全无关,让我解释一下),而是与包有关。 进行缓冲,并单独处理每个的缓冲,因此您可以在需要将其写入磁盘的缓冲区时调用刷新缓冲区的调用。 指针关联到串行tty时(它会检查调用isatty(3)调用,该调用在内部进行ioctl(2)调用,允许看到您是否正在使用串行设备,字符设备。如果发生这种情况,则进行行缓冲,这意味着每当输出一个“\n”字符到设备时,缓冲区就会自动缓冲。
这引起了优化问题,因为例如使用cat(1)复制文件时,通常最大的缓冲区是最有效的方法。好吧,来解决这个问题,因为当输出不是tty设备时,它进行完全缓冲,并且仅在指针的内部缓冲区已满时才刷新它们。
问题是:<stdio.h>在fifo(或pipe)节点上的行为如何?答案很简单……不是字符设备(或tty),因此<stdio.h>对其进行完全缓冲。如果您正在两个进程之间通信数据,并且希望读取器一旦您使用printf(3)输出数据就立即接收到数据,则最好使用fflush(3),因为如果您不这样做,您可能会等待从未到来的响应,因为您编写的内容还没有被写入(不是由内核写入,而是由<stdio.h>库写入)。

正如我所说,我不知道这是否完全回答了您的问题,但肯定可以给您一个提示,让您知道问题出在哪里。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接