Python close_fds不清楚

8

我在Python27中使用close_fds时遇到了问题,经过一些研究后,我找到了这个示例

from subprocess import Popen, PIPE, STDOUT
p1 = Popen(['cat'], stdin=PIPE, stdout=PIPE)
p2 = Popen(['grep', 'a'], stdin=p1.stdout, stdout=PIPE)
p1.stdin.write("aaaaaaaaaaaaaaaa\n")
p1.stdin.close()
p2.stdout.read()

我的问题是我不理解为什么 `p1.stdin` 保持打开状态。`p1` 不是 `p2` 的子进程,因此除了显式传递的 `p1.stdout` 外,`p2` 不应继承任何 `p1` 资源。此外,为什么在 `p2` 中设置 `close_fds=True` 可以解决这个问题?在这里写道:

如果 close_fds 为 True,则在执行子进程之前会关闭除0、1和2之外的所有文件描述符。

因此,即使我能够理解 `p1` 和 `p2` 之间的继承关系,`p1.stdin` 也不应该被 `close_fds=True` 关闭,因为它是标准输入(1)。

有趣的是,在Linux 2.7.7上,p1.stdin没有保持打开状态。在调用p1.stdin.close之前,我检查了p2的打开fds,p2没有任何额外的fds。不确定为什么现在会发生这种情况。这是在Linux上。最近的实施有什么变化吗? - Tom
看起来实际文件和终端的文件描述符被继承了,但是管道的文件描述符没有被继承? - Tom
顺便提一下,看起来2.7.7中的这种行为是由提交https://github.com/python/cpython/commit/2a34eb31040ad3eb2f09e22a0237afa98755390d#diff-cc136486b4a8e112e64b57436a0619eb引起的,该提交确保在任何管道上设置CLOEXEC,从而使它们不可继承。 - Tom
1个回答

13

由于 p1p2 是兄弟进程,它们之间没有直接的继承关系。

然而,考虑一下父进程看到的文件描述符 p1.stdin,被 p1 继承并重定向为其 stdin。这个文件描述符存在于父进程中(使用一个不是 0、1 或 2 的数字可以验证这一点——您可以通过打印 p1.stdin.fileno() 来进行验证),它必须存在,因为我们打算从父进程写入它。正是这个文件描述符被意外地继承并保持开启状态给 p2 带来了问题。

当一个打开的文件被多个文件描述符引用时,例如 p1.stdin 的情况,只有当所有描述符都关闭时才会关闭它。这就是为什么需要同时关闭 p1.stdin p2 中传递 close_fds 参数的原因。(如果您手动实现生成代码,只需在第二个 fork() 后关闭文件描述符即可。)


1
现在更清楚了。所以,有一个用于与p1通信的PIPE fd被继承而没有关闭,并且有一个不同的fd p1.stdin(我没有清楚指针和地址),正确打开/关闭。现在read()中的挂起是由继承的打开PIPE引起的,该继承的PIPE正在等待关闭。这个理解正确吗?我能从p2关闭PIPE吗?您能否解释一下用于与p1通信的PIPE和p1.stdin之间的区别,它们不是相同的吗?非常感谢。 - Luigi Tiburzi
2
@LuigiTiburzi p1.stdin 是 Python 对 fd 的对象包装器 - 它们是同一个东西。你可以关闭 p2 的描述符,但是 Python 不会授予你对 p2 的控制权。这就是为什么 close_fds 可以解决这个问题的原因。 - user4815162342
5
p1.stdin和它的文件描述符是同一底层资源(文件描述符,指针无关紧要,因为打开的文件是由内核分配的操作系统级资源)。 p2进程继承了文件描述符,并且该副本保持打开状态。这就是为什么关闭p1.stdin(也不会关闭其文件描述符)没有帮助的原因。您可以将文件描述符视为对“打开文件”的引用-关闭描述符会删除对打开文件的单个引用。只有当所有文件描述符都关闭时,打开的文件才会真正关闭。 - user4815162342
4
这句话的意思是,由于所有的fd指向相同的打开文件资源,所以它有点像按引用传递。但是底层资源是参考计数的,因此只有当所有引用被关闭时,它才会关闭。 - user4815162342
1
@LancelodLiu 使用 close_fds=True 创建 p2 可以解决此问题,因为它确保 p2 不会继承用于与 p1 通信的管道的 p1.stdin 端点。关键点在于 p1.stdin 不是 父进程中的 stdin(或stdout),而是对应于用于与 p1 通信的管道的文件对象。正如答案所说,您可以通过打印 p1.stdin.fileno() 来验证它具有不同于 (0, 1, 2) 的数字。在生成 p2 父进程时传递 close_fds=True 确保在 p2 进程 中关闭 p1.stdin。(在父进程中显式关闭。) - user4815162342
显示剩余3条评论

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