我需要刷新命名管道吗?

Posted

技术标签:

【中文标题】我需要刷新命名管道吗?【英文标题】:Do I need to flush named pipes? 【发布时间】:2018-05-03 07:58:21 【问题描述】:

我找不到命名管道是否被缓冲,因此问题。

手册页上写着https://linux.die.net/man/3/mkfifo:

FIFO 特殊文件类似于管道......任何进程都可以打开它进行读取或写入,就像普通文件一样。

管道没有缓冲,无需刷新。但在普通文件中,我会 fflush(或 fsync)文件描述符。

命名管道怎么样?

【问题讨论】:

管道没有缓冲你确定吗?此外,基于 C 标准库的 IO 默认带有额外的缓冲级别。 @SergeBallesta 据我所知,PIPE 中没有二级存储。只要您使用open(而不是fopen)打开管道流,读/写操作就会在内核内部发生。我错过了什么吗? 我没有考虑管道中的二级存储,但是 write 系统调用可能会使用系统缓冲区,并且只有在其内部缓冲区已满时才将输出刷新到管道。 @SergeBallesta 你有没有提到write 系统调用中的缓冲区的消息来源?我真的很好奇。 @rkioji,今天 fifos 和管道在内核核心内存中完成所有工作。但这不是很久以前发生的事情。最初,管道在文件系统上实现,并且它们消耗了一个 inode(它们继续这样做,用于锁定进程并有一些等待的东西 --- inode 被锁定以等待完整的write/read 系统调用)并且最初消耗了直接块那个inode。 【参考方案1】:

管道没有缓冲,无需刷新。

我实际上是反过来说的:对于大多数意图和目的,管道只不过是缓冲区。刷新它们没有意义,因为没有底层设备来接收数据。

此外,虽然 POSIX 没有明确禁止管道 I/O 的额外缓冲,但它确实提出了足够的行为要求,我认为没有任何方法可以通过观察来确定这种缓冲是否发生,除非可能通过 fsync()成功。换句话说,即使有额外的缓冲,也应该没有必要fsync()一个管道端。

但在普通文件中,我 将 fflush(或 fsync)文件描述符。

不,你不会fflush() 文件描述符。 fflush() 在由FILE 对象表示的 上运行,而不是在文件描述符上运行。这是一个至关重要的区别,因为大多数流都在 C 库级别缓冲,与下面文件的性质无关。 fflush() 与之交互的正是这个库级缓冲区。您可以通过setvbuf() 函数控制流的库级缓冲模式。

在那些提供它的系统上,fsync() 在不同的较低级别上运行。它指示操作系统确保之前写入指定文件描述符的所有数据都已传递到底层存储设备。换句话说,它会刷新操作系统级别的缓冲区。

请注意,您可以通过fdopen() 函数将流包装在管道端文件描述符周围。这不会使 管道 比以前更需要刷新,但是 默认情况下会被缓冲,因此刷新将与它相关。

还要注意,一些存储设备会执行自己的缓冲,因此即使在将数据移交给存储设备之后,也不确定它们是否会立即持久化。

命名管道怎么样?

上面关于流 I/O vs 的讨论。基于 POSIX 描述符的 I/O 也适用于此。如果您通过流访问命名管道,那么它与fflush() 的交互将取决于该流的缓冲。

但我想您的问题更多是关于操作系统级别的缓冲和刷新。 POSIX 似乎并没有说太多具体的内容,但是由于您标记了 [linux] 并在您的问题中参考了 Linux 手册页,因此我提供以下内容作为回应:

管道和 FIFO 之间的唯一区别是 它们被创建并打开。这些任务完成后, 管道和 FIFO 上的 I/O 具有完全相同的语义。

(Linux pipe(7) manual page.)

【讨论】:

是的,我的问题更多的是关于操作系统级别的缓冲。 I/O on pipes and FIFOs has exactly the same semantics,这对我来说不是很清楚。它们可能具有相同的语义,但行为不同。 @rkioji,您关注的是引文的错误部分。关于相同语义的位是为了补充要点,即“管道和 FIFO 之间的唯一区别是它们的创建和打开方式。”但是,不,具有相同语义提供了两者行为不同的可能性。这几乎意味着完全相反。 有什么方法可以在不进行读取操作的情况下从 pipe/fifo 中刷新或清除数据 @ixnisarg,拒绝待处理的输入不是“刷新”,因此不是本问答的内容。话虽如此,不,没有用于排放管道的专用机制。如果你想这样做,那么你只需从管道中读取数据并忽略它。然而,这有点令人担忧,因为没有多少数据可以拒绝? “目前在管道中的所有数据”不是一个非常可靠的衡量标准。 但是,当管道不再打开以供任何进程读取或写入时,管道将不复存在,并且任何未读取的数据都将丢失。这适用于与未命名管道相同的命名管道,只要命名管道的管道部分与创建和访问它的文件系统条目区分开来。【参考方案2】:

我不太明白你想问什么,但正如你已经被告知的那样,管道不仅仅是缓冲区。

从历史上看,fifo(或管道)消耗用于维护它们的 inode 的直接块,并且它们与某些文件系统中的文件(是否有名称)相关联。

今天,我不知道 fifo 的确切实现细节,但基本上,内核会缓冲写入者已经写入但读取者尚未读取的所有数据。 fifo 对它们可以支持的缓冲区数量有一个上限(系统定义),但通常在 10-20kb 左右的数据时会失败。

内核缓冲,但写入者和读取者之间没有延迟,因为一旦写入者在管道上写入,内核就会唤醒所有等待它获取数据的读取者。反之亦然,如果管道充满数据,一旦读者消费它,所有写入者都会被唤醒以允许再次填充它。

无论如何,您关于冲洗的问题与管道无关(好吧,不像,让我自己解释一下),而是与<stdio.h> 包有关。 <stdio.h> 确实缓冲,它单独处理每个 FILE * 上的缓冲,所以当你希望它们成为 write(2)n 到磁盘时,你可以调用刷新缓冲区。

具有动态行为,允许优化缓冲,而不是强制程序员每次都必须刷新。这取决于与FILE * 指针关联的文件描述符的类型。

FILE * 指针与串行 tty 相关联时(它会检查对 isatty(3) 调用的调用,该调用在内部进行 ioctl(2) 调用,这允许 <stdio.h> 查看您是否针对串行设备,一个字符设备。如果发生这种情况,那么<stdio.h> 会进行行缓冲,这意味着总是当'\n' 字符输出到设备时,缓冲区会自动缓冲。

这假设了一个优化问题,因为当您使用cat(1) 复制文件时,通常假设最大的缓冲区是最有效的方法。那么<stdio.h>就来解决问题了,因为当输出不是tty设备时,它会做全缓冲,只有当FILE *指针满了数据时才会刷新内部缓冲区。

所以问题是:<stdio.h> 如何处理先进先出(或管道)节点?答案很简单....不是字符设备(或 tty),所以<stdio.h> 对其进行完全缓冲。如果您正在两个进程之间传递数据,并且您希望读者在您拥有printf(3)ed 后立即接收数据,那么您最好使用fflush(3),因为如果您不这样做,您可能会等待永远不会出现的响应,因为您所写的内容尚未被写入(不是由内核编写,而是由<stdio.h> 库编写)

正如我所说,我不知道这是否正是您问题的答案,但可以肯定它可以提示您问题可能出在哪里。

【讨论】:

以上是关于我需要刷新命名管道吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以将 cv::Mat 作为 JPEG 数据写入命名管道吗?

您可以使用单个命名管道客户端进行读写吗?

命名管道(fifo)有多贵?

您可以通过命名管道自托管 ServiceStack 吗?

关于命名管道,通常如何实现“握手”

命名管道名称可以有反斜杠吗?