读取命名管道:tail 还是cat?

读取命名管道:tail 还是cat?

我使用创建了一个文件描述符

mkfifo fifo

一旦有东西写入这个管道,我就想重用它立即地。我应该使用

tail -f fifo

或者

while true; do cat fifo; done

他们似乎做同样的事情,我无法衡量性能上的差异。但是,当系统不支持inotify时(例如Busybox),则需要将前者设置为

tail -f -s 0 fifo

但这会占用 100% 使用率的 CPU(测试一下:mkfifo fifo && busybox tail -f -s 0 fifo & echo hi>fifo/cancel withfg 1CtrlC)。那么 while-true-cat 是更可靠的解决方案吗?

答案1

当你这样做时:

cat fifo

假设还没有其他进程打开fifo用于​​写入,cat则会阻塞open()系统调用。当另一个进程打开文件进行写入时,管道将被实例化并open()返回。catread()循环调用并read()阻塞,直到其他进程将数据写入管道。

cat当所有其他写入进程都关闭其文件描述符时,将看到文件结束符 (eof) fifo。此时cat终止并且管道被破坏。

您需要cat再次运行以读取之后写入的内容fifo(但通过不同的管道实例)。

在:

tail -f file

就像cattail将等待进程打开文件进行写入。但在这里,由于你没有指定-n +1从头开始复制,tail所以需要等到 eof 才能找出最后 10 行是什么,所以直到写入结束关闭你才看到任何东西。

之后,tail不会将其 fd 关闭到管道,这意味着管道实例不会被销毁,并且仍然会尝试每秒从管道中读取数据(在 Linux 上,可以通过使用inotify某些版本来避免轮询) GNUtail在那里做那件事)。这read()将与 eof 返回(立即,这就是为什么你会看到 100% CPU -s 0(对于 GNU 来说tail意味着不在read()s 之间等待而不是等待一秒钟)),直到其他进程再次打开该文件进行写入。

相反,在这里,您可能想要使用cat,但要确保管道实例在实例化后始终保留在附近。为此,在大多数系统上,您可以执行以下操作:

cat 0<> fifo # the 0 is needed for recent versions of ksh93 where the
             # default fd changed from 0 to 1 for the <> operator

cat的 stdin 将开放用于读取和写入,这意味着cat永远不会在其上看到 eof (即使没有其他进程打开该管道进行fifo写入,它也会立即实例化管道)。

在这不起作用的系统上,您可以这样做:

cat < fifo 3> fifo

这样,一旦其他进程打开fifo写入,第一个只读open()将返回,此时 shell 将在open()启动之前执行只写cat,这将防止管道再次被破坏。

所以,总结一下:

  • 相比之下cat file,第一轮之后就不会停止。
  • 相比之下tail -n +1 -f file:在第一轮之后,它不会read()每秒都做无用的事情,管道的一个实例上永远不会有 eof,当第二个进程在管道之后打开管道进行写入时,不会有长达一秒的延迟。第一个已经关闭了它。
  • 相比tail -f file。除了上述内容之外,它不必等待第一轮结束就可以输出某些内容(仅输出最后 10 行)。
  • 与循环相比cat file,只有一个管道实例。 1 中提到的竞争窗口将被避免。

1 此时,在最后一个read()表示 eof 和cat终止并关闭管道读取端之间,实际上有一个小窗口,在此期间进程可以fifo再次打开管道进行写入(并且不会被阻止,因为仍然有一个读取端)。然后,如果它在cat退出后且在另一个进程打开该进程fifo进行读取之前写入了某些内容,则它将被 SIGPIPE 杀死。

答案2

让我提出另一个解决方案。只要有进程在第二端写入,管道就可供读取。所以你可以cat在后台(或在另一个终端)创建一些假的,例如:

mkfifo fifo
cat >fifo &
cat fifo

现在,您可以根据需要写入 fifo,完成后只需使用 终止当前进程catC-c然后首先从后台fg启动,最后停止它。catC-d

相关内容