【问题标题】:Why stdin of parent process still accepts inputs after closing stdin file descriptor from a forked child process?为什么从分叉的子进程关闭标准输入文件描述符后,父进程的标准输入仍然接受输入?
【发布时间】:2017-07-06 13:13:21
【问题描述】:

fork(2) 我正在运行的 Linux 系统上的手册页显示以下内容:

子级继承父级打开文件描述符集的副本。子文件中的每个文件描述符与父文件中的相应文件描述符引用相同的打开文件描述(请参阅 open(2))。这意味着两个文件描述符共享打开文件状态标志、文件偏移量和信号驱动的 I/O 属性(参见 fcntl(2) 中 F_SETOWN 和 F_SET-SIG 的描述)。

还有 Python 文档提到

_exit() 通常只能在fork() 之后的子进程中使用。

当然,_exit 不会调用清理处理程序,问题是,如果你看一下这段代码:

newpid = os.fork()
if newpid == 0:
    os.close(0)
else:
    time.sleep(.25)
    input()

尽管子进程关闭了标准输入,但父进程仍然接受来自标准输入的输入。很好,下面是颠倒的代码:

newpid = os.fork()
if newpid == 0:
    input()
else:
    time.sleep(.25)
    os.close(0)

现在,情况正好相反,这次是父进程关闭标准输入,而不是子进程。这会为子进程中的input() 调用引发EOFError。

这看起来就像当 [child] 进程写入/修改父的文件描述符时,它不会影响 [parent]。也就是说,子进程获取更新的文件描述。

那么,如果子进程执行的操作不影响父进程,为什么要调用_exit 以防止调用清理处理程序作为 Python 文档所述?我们来看看_EXIT(2) man page:

函数_exit() terminates the calling process "immediately". Any open file descriptors belonging to the process are closed; any children of the process are inherited by process 1, init, and the process's parent is sent a SIGCHLD`信号。

函数_exit() 类似于exit(3),但不调用任何用atexit(3) 或on_exit(3) 注册的函数。打开的 stdio(3) 流不会被刷新。另一方面,_exit() 会关闭打开的文件描述符,这可能会导致未知延迟,等待挂起的输出完成。

fork() 手册页没有提到子进程的清理处理程序是从父进程继承的。这对父母有什么影响?换句话说,为什么不让子进程自行清理,为什么不呢?

【问题讨论】:

  • EOFError 不是由于os.close(0)。即使您使用time.sleep(5),您也会立即得到它。
  • 如果标准输入被重定向到管道或文件也不会发生。

标签: python linux


【解决方案1】:

我假设您是从终端中的 shell 运行它。

shell 在新进程组中启动 Python 进程,并使用tcsetpgrp() 将其设置为 TTY 上的前台进程组。

一旦父 Python 进程终止,shell 就会收回对终端的控制(它将自己设置为前台进程组)。 shell 不知道来自 Python 的分叉子代仍在运行。

当不属于前台进程组的进程尝试从终端读取时,它通常会收到SIGTTIN 信号。但是,在这种情况下,进程组已被孤立,因为其领导者已终止,因此子进程在 TTY 上从read() 收到EIO 错误。 Python 将其视为EOFError。

【讨论】:

  • 错误发生在父进程终止之前。输入更长的time.sleep,您会在父级退出之前立即看到EOFError。
  • 另外,如果你把它改成print(input()),你会看到它在报错后成功读取了输入。
  • @Barmar 这不是我在我的 Linux 机器上观察到的行为。
  • @Barmar OP 在 Linux 上。
  • @direprobs 哦,我实际上需要稍微调整一下我的答案。 SIGTTIN 如果进程组是后台的,则会发生,但在这种情况下,您的孩子现在是 orphaned 进程组的一部分,这意味着从控制台读取会产生 EIO,Python 会将其转换为一个EOFError。
猜你喜欢
  • 2018-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-05
  • 1970-01-01
  • 2012-02-23
  • 2013-02-16
  • 1970-01-01
相关资源
最近更新 更多