user9303970 Asked: 2018-04-11 13:05:24 +0800 CST2018-04-11 13:05:24 +0800 CST 2018-04-11 13:05:24 +0800 CST FIFO(命名管道)与常规管道(未命名管道)有何不同?[复制] 772 FIFO(命名管道)与常规管道 (|) 有何不同?据我从Wikipedia了解到,与常规管道不同,FIFO 管道在流程结束后“继续存在”,并且可以在之后的某个时间删除。 cat x | grep y但是如果进程是基于一个包含管道( 此外,常规管道也具有它获得的第一个标准输出,作为另一个命令的标准输入,那么它不也是一种先进先出管道吗? pipe fifo 2 个回答 Voted Best Answer derobert 2018-04-11T15:11:22+08:002018-04-11T15:11:22+08:00 “命名管道”实际上是一个非常准确的名称——它就像一个常规管道,除了它有一个名称(在文件系统上)。 管道——使用的常规、未命名(“匿名”)some-command | grep pattern是一种特殊的文件。我的意思是文件,你读写它就像你对其他文件一样。Grep 并不真正关心¹它是从管道而不是终端³或普通文件中读取。 从技术上讲,幕后发生的事情是 stdin、stdout 和 stderr 是传递给每个命令运行的三个打开文件(文件描述符)。文件描述符(在每个系统调用中用于读取/写入/等文件)只是数字;stdin、stdout 和 stderr 是文件描述符 0、1 和 2。因此,当您的 shell 设置some-command | grep它所做的事情时,如下所示: 向内核询问匿名管道。没有名称,因此对于普通文件无法使用openlike 来完成 - 而是使用pipeor完成pipe2,它返回两个文件描述符。⁴ 分叉出一个子进程(fork()创建父进程的副本;管道的两端在此处打开),将管道的写入端复制到 fd 1(stdout)。内核有一个系统调用来复制文件描述符编号;它是dup2()或dup3()。然后它关闭读取端和写入端的其他副本。最后,它用于execve执行some-command. 由于管道是 fd 1,stdout ofsome-command是管道。 另一个子进程的分叉。这一次,它将管道的读取端复制到 fd 0 (stdin),并执行grep. 因此 grep 将从管道中读取为标准输入。 然后它等待这两个孩子退出。 此时,内核注意到管道不再打开,并且垃圾收集它。这就是真正破坏管道的原因。 命名管道只是通过将匿名管道放入文件系统中来为其命名。所以现在任何open进程,在未来的任何时候,都可以通过使用普通的系统调用来获取管道的文件描述符。从概念上讲,管道不会被销毁,直到所有读取器/写入器都关闭它并且它是unlink从文件系统中编辑的。² 顺便说一下,这就是文件在 Unix 上的一般工作方式。unlink(后面的系统调用rm)只是删除文件的名称之一;只有当所有名称都被删除并且没有任何文件打开时,它才会被实际删除。这里有几个答案探讨了这一点: 为什么硬链接似乎与原始链接占用相同的空间? 日志程序如何继续记录已删除的文件? Linux 有什么不同之处,允许我删除/替换 Windows 会抱怨文件当前正在使用的文件? 脚注 从技术上讲,这可能不是真的——通过了解可能会进行一些优化,并且实际的 grep 实现通常已经过大量优化。但从概念上讲,它并不关心(实际上 grep 的直接实现也不会)。 当然,内核实际上并没有将所有数据结构永远保存在内存中,而是在第一个程序打开命名管道时透明地重新创建它们(然后只要它打开就保持它们)。因此,就好像它们存在的时间一样长。 终端不是 grep 读取的常见位置,但当您不指定另一个标准输入时,它是默认的标准输入。因此,如果您只grep pattern在 shell 中输入,grep将从终端读取。想到的唯一用途是如果您要将某些内容粘贴到终端。 在 Linux 上,匿名管道实际上是在一个特殊的文件系统 pipefs 上创建的。有关详细信息,请参阅管道在 Linux中的工作原理。请注意,这是 Linux 的内部实现细节。 Peter Cordes 2018-04-11T16:21:29+08:002018-04-11T16:21:29+08:00 我认为您在管道的 shell 语法与底层 Unix 系统编程之间混淆了。管道 / FIFO 是一种不存储在磁盘上的文件,而是通过内核中的缓冲区将数据从写入器传递到读取器。 管道/FIFO 的工作方式是相同的,无论写入器和读取器是通过系统调用(如open("/path/to/named_pipe", O_WRONLY);),还是使用 apipe(2)创建新的匿名管道并将打开的文件描述符返回给读取端和写入端。 fstat(2)在管道文件描述符上会给你sb.st_mode & S_IFMT == S_IFIFO任何一种方式。 当你运行foo | bar: 对于任何非内置命令,shell 都会像平常一样分叉 然后进行pipe(2)系统调用以获取两个文件描述符:匿名管道的输入和输出。 然后它再次分叉。 孩子(fork()返回 0) 关闭管道的读取端(保持写入 fd 打开) 并重定向stdout到 write fddup2(pipefd[1], 1) 然后确实execve("/usr/bin/foo", ...) 父级(fork()返回非 0 子 PID) 关闭管道的写入端(保持读取 fd 打开) stdin并从读取的 fd重定向dup2(pipefd[0], 0) 然后确实execve("/usr/bin/bar", ...) 如果你跑步,你会遇到非常相似的情况foo > named_pipe & bar < named_pipe。 命名管道是进程在彼此之间建立管道的集合点。 这种情况类似于匿名 tmp 文件与具有名称的文件。您可以open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);创建一个没有名称( O_TMPFILE) 的临时文件,就像您打开"/path/to/dir/tmpfile"然后O_CREAT取消链接它一样,为您留下一个已删除文件的文件描述符。 使用linkat,您甚至可以将该匿名文件链接到文件系统,并为其命名,如果它是用O_TMPFILE. (但是,对于使用名称创建然后删除的文件,您不能在 Linux 上执行此操作。)
“命名管道”实际上是一个非常准确的名称——它就像一个常规管道,除了它有一个名称(在文件系统上)。
管道——使用的常规、未命名(“匿名”)
some-command | grep pattern
是一种特殊的文件。我的意思是文件,你读写它就像你对其他文件一样。Grep 并不真正关心¹它是从管道而不是终端³或普通文件中读取。从技术上讲,幕后发生的事情是 stdin、stdout 和 stderr 是传递给每个命令运行的三个打开文件(文件描述符)。文件描述符(在每个系统调用中用于读取/写入/等文件)只是数字;stdin、stdout 和 stderr 是文件描述符 0、1 和 2。因此,当您的 shell 设置
some-command | grep
它所做的事情时,如下所示:向内核询问匿名管道。没有名称,因此对于普通文件无法使用
open
like 来完成 - 而是使用pipe
or完成pipe2
,它返回两个文件描述符。⁴分叉出一个子进程(
fork()
创建父进程的副本;管道的两端在此处打开),将管道的写入端复制到 fd 1(stdout)。内核有一个系统调用来复制文件描述符编号;它是dup2()
或dup3()
。然后它关闭读取端和写入端的其他副本。最后,它用于execve
执行some-command
. 由于管道是 fd 1,stdout ofsome-command
是管道。另一个子进程的分叉。这一次,它将管道的读取端复制到 fd 0 (stdin),并执行
grep
. 因此 grep 将从管道中读取为标准输入。然后它等待这两个孩子退出。
此时,内核注意到管道不再打开,并且垃圾收集它。这就是真正破坏管道的原因。
命名管道只是通过将匿名管道放入文件系统中来为其命名。所以现在任何
open
进程,在未来的任何时候,都可以通过使用普通的系统调用来获取管道的文件描述符。从概念上讲,管道不会被销毁,直到所有读取器/写入器都关闭它并且它是unlink
从文件系统中编辑的。²顺便说一下,这就是文件在 Unix 上的一般工作方式。
unlink
(后面的系统调用rm
)只是删除文件的名称之一;只有当所有名称都被删除并且没有任何文件打开时,它才会被实际删除。这里有几个答案探讨了这一点:脚注
grep pattern
在 shell 中输入,grep
将从终端读取。想到的唯一用途是如果您要将某些内容粘贴到终端。我认为您在管道的 shell 语法与底层 Unix 系统编程之间混淆了。管道 / FIFO 是一种不存储在磁盘上的文件,而是通过内核中的缓冲区将数据从写入器传递到读取器。
管道/FIFO 的工作方式是相同的,无论写入器和读取器是通过系统调用(如
open("/path/to/named_pipe", O_WRONLY);
),还是使用 apipe(2)
创建新的匿名管道并将打开的文件描述符返回给读取端和写入端。fstat(2)
在管道文件描述符上会给你sb.st_mode & S_IFMT == S_IFIFO
任何一种方式。当你运行
foo | bar
:pipe(2)
系统调用以获取两个文件描述符:匿名管道的输入和输出。fork()
返回 0)stdout
到 write fddup2(pipefd[1], 1)
execve("/usr/bin/foo", ...)
fork()
返回非 0 子 PID)stdin
并从读取的 fd重定向dup2(pipefd[0], 0)
execve("/usr/bin/bar", ...)
如果你跑步,你会遇到非常相似的情况
foo > named_pipe & bar < named_pipe
。命名管道是进程在彼此之间建立管道的集合点。
这种情况类似于匿名 tmp 文件与具有名称的文件。您可以
open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
创建一个没有名称(O_TMPFILE
) 的临时文件,就像您打开"/path/to/dir/tmpfile"
然后O_CREAT
取消链接它一样,为您留下一个已删除文件的文件描述符。使用
linkat
,您甚至可以将该匿名文件链接到文件系统,并为其命名,如果它是用O_TMPFILE
. (但是,对于使用名称创建然后删除的文件,您不能在 Linux 上执行此操作。)