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

phi*_*294 8 pipe tail cat fifo

我使用了一个文件描述符

mkfifo fifo
Run Code Online (Sandbox Code Playgroud)

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

tail -f fifo
Run Code Online (Sandbox Code Playgroud)

或者

while true; do cat fifo; done
Run Code Online (Sandbox Code Playgroud)

?

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

tail -f -s 0 fifo
Run Code Online (Sandbox Code Playgroud)

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

Sté*_*las 12

当你这样做时:

cat fifo
Run Code Online (Sandbox Code Playgroud)

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

cat当所有其他写入进程将其文件描述符关闭到fifo. 在这些点cat终止并且管道被破坏¹。

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

在:

tail -f file
Run Code Online (Sandbox Code Playgroud)

就像cattail将等待一个进程打开一个文件进行写入。但是在这里,由于您没有-n +1从头指定要复制,tail因此需要等到 eof 才能找出最后 10 行是什么,因此在写入结束关闭之前您将看不到任何内容。

在此之后,tail将不会关闭它的fd到,这意味着管道实例不会被破坏,并且仍将尝试从管道每秒(在Linux上阅读管道,轮询可以通过使用避免inotify和一些版本GNUtail在那里这样做)。这read()将返回 eof (马上,这就是为什么你看到 100% CPU with -s 0(这与 GNUtail意味着不在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
Run Code Online (Sandbox Code Playgroud)

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

在不起作用的系统上,您可以改为:

cat < fifo 3> fifo
Run Code Online (Sandbox Code Playgroud)

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

所以,总结一下:

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

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

  • @Blauhirn(续),通过以读+写模式打开 fifo,我们在管道的两端都有一个 fd(不是双向的;即使在常规管道是双向的系统上,命名管道也只有一个方向),因此在另一端总是一个 fd(与我们正在阅读的那个 fd 相同),我们永远不会看到 eof,因为在另一端仍然有一个作家:我们。 (2认同)