如何防止孩子在 fork() 之后干扰父母的标准输入

Posted

技术标签:

【中文标题】如何防止孩子在 fork() 之后干扰父母的标准输入【英文标题】:How to prevent child from interfering with parent's stdin after fork() 【发布时间】:2015-06-12 19:22:16 【问题描述】:

我有一个从标准输入读取的程序,在执行此操作时,它有时需要 fork() 并异步执行命令。有时在此分叉后不久(可能在命令运行时),父级似乎丢失了从标准输入读取的数据。如果只丢失一个字节,程序将永远无法恢复。

fork() 的手册页说子进程继承了父进程的打开文件描述符集的副本,但是在 fork() 之后和 execvp() 之前尝试 close(STDIN_FILENO) 没有帮助。

我的程序基本上是这样的:

if (we_need_to_run_a_command_asynchronously) 
  if (!fork()) 
    close(STDIN_FILENO);
    char *args[] =  "command", "arg", "etc", 0 ;
    setpriority(PRIO_PROCESS, 0, -19);
    execvp("command", args);
    exit(0);  // Ignore errors
  

如何确保孩子不会干扰父母的标准输入(或其他任何属于父母的东西)?执行的命令通常是一个 shell 脚本。

我不知道我的程序到底出了什么问题,但只要它不运行任何命令,它就可以正常运行。只有有时(并非总是)在它运行命令之后,它从标准输入读取的数据以某种方式被破坏,这就是为什么我怀疑问题与 fork() 之后的标准输入有关。

【问题讨论】:

您能否通过编写两个小型测试程序来检验您的假设,一个从标准输入读取,一个不读取,然后让您的父程序执行这些程序,看看会发生什么? 请分享一个可编译的小程序。它可以帮助我们进入它 您可以为标准输入设置 close-on-exec 标志,而不是显式关闭。但是,问题可能有不同的原因(演示问题的简短示例程序可能会有所帮助)。 也许可以产生一个 pthread 而不是一个新进程? 您执行的命令有权打开标准输入。你可以给它/dev/null 来阅读。您对command 了解多少(除了名称command 是一个内置的shell)?你写了吗?它是从什么读来的?基本上,如果您将其标准输入连接到/dev/null,您应该不会看到问题。 【参考方案1】:

您可以打开另一个文件并使用dup2 将新文件描述符复制到标准输入上,而不是关闭标准输入。如果您打开/dev/null 并在fork 之后和之前以及exec 之前将其重定向到标准输入,则孩子不会干扰父母的标准输入。

【讨论】:

/dev/null 是比 /dev/zero 更传统的选择,这是有充分理由的。【参考方案2】:

我想我已经解决了这个问题。事实证明,在我的程序很少采用的路径中,它也执行了我在 fork() 之后忘记对子项中的 stdin 执行任何操作的命令。这段代码确实有效:

int pid = fork();
if (!pid) 
  int fd = open("/dev/null", O_RDWR);
  dup2(fd, 0);
  dup2(fd, 1);
  dup2(fd, 2);
  if (fd > 2)
    close(fd);

  execvp(...);

所以,我的错误,但也许我的问题仍然可以作为一个重要的提醒,即在分叉执行应该与父进程分开运行的命令时,通常需要对 stdin/stdout/stderr 执行某些操作。我认为上述方式是一种干净的方式。

【讨论】:

【参考方案3】:

我在execve() 的 POSIX 规范中发现了一个有趣的注释 及相关功能:

如果文件描述符 0、1 或 2 在成功调用 exec 系列函数之一后将被关闭,则实现可能会为新进程映像中的文件描述符打开一个未指定的文件。如果执行标准实用程序或符合标准的应用程序时文件描述符 0 未打开以供读取或文件描述符 1 或 2 未打开以供写入,则执行实用程序或应用程序的环境应被视为不符合标准,因此实用程序或应用程序的行为可能与本标准中所述不同。

推测您的实现是否正在执行“可能”选项很有趣。显式打开/dev/null 作为标准输入是最安全的。

【讨论】:

以上是关于如何防止孩子在 fork() 之后干扰父母的标准输入的主要内容,如果未能解决你的问题,请参考以下文章

如何防止拖累孩子,但允许拖累父母?

命名管道,使用 Fork()

fork之后调试子进程(跟随fork-mode子配置)

防止孩子增加父母的宽度[重复]

如何在vfork()之后恢复父级

防止父母李+李携带给孩子