【问题标题】:read() hangs on zombie processread() 挂在僵尸进程上
【发布时间】:2018-11-30 10:51:23
【问题描述】:

我有一个 while 循环,它通过将子进程的 stdout 重定向到父进程来使用阻塞 I/O 从子进程读取数据。通常,一旦子进程退出,在这种情况下会返回阻塞read(),因为从中读取的管道已被子进程关闭。

现在我遇到了read() 调用不会为完成的子进程退出的情况。子进程最终处于僵尸状态,因为操作系统正在等待我的代码获取它,但我的代码却阻塞了 read() 调用。

子进程本身在挂起时没有任何子进程正在运行,并且在查看/proc/<child process PID>/fd 时我没有看到任何文件描述符。然而,子进程确实派生了两个守护进程,其目的似乎是监视子进程(子进程是我无法控制的专有应用程序,因此很难确定)。

当从终端运行时,我尝试 read() 的子进程自动退出,而它派生的守护进程也随之终止。

Linux 版本为 4.19.2。

read() 在这种情况下没有返回的原因是什么?

跟进:How to avoid read() from hanging in the following situation?

【问题讨论】:

  • 为什么不使用信号处理程序杀死子进程时关闭管道?
  • @BumsikKim 这里没有进程被杀死,子进程应该简单地退出。我将编辑问题以澄清这一点。
  • SIGCHLD 设置一个信号处理程序,并通过调用wait()waitpid() 在其中收割孩子。
  • @alk,感谢您的指点,我会调查一下。不过,我不明白为什么read() 不会在子进程退出后简单地返回。
  • 在孩子退出后”它没有(完全)。正如你提到的,它仍然处于僵尸状态。

标签: c linux pipe fork zombie-process


【解决方案1】:

子进程确实派生了两个守护进程...read() 在这种情况下没有返回的原因是什么?

当子进程终止时,分叉进程仍然打开文件描述符。因此read 调用永远不会返回 0。

那些守护进程应该关闭所有文件描述符并打开文件进行日志记录。

【讨论】:

  • 啊啊,当然……不幸的是我不控制守护进程,所以我必须走SIGCHLD路线。
  • @TonvandenHeuvel 如果您控制调用fork 的代码,那么这就是关闭文件描述符的地方——就在fork 之后。
  • 是的,我知道,但在这种情况下,我不想从子进程中关闭标准,我想从中读取。
  • @TonvandenHeuvel 您的子进程分叉,现在您有两个进程。您只需要关闭孩子的孩子中的文件描述符。它们在孩子体内保持开放。
  • @MaximEgorushkin,澄清一下;我无法控制子进程及其派生的守护进程。我只能控制父应用程序。
【解决方案2】:

read(2) 阻塞带有死子的管道的一个可能原因(最常见)是父级没有关闭管道的写入端,因此仍然有一个打开(用于写入)描述符管道。在读取之前关闭父进程中管道的写入端。孩子已经死了(你说僵尸),所以 它不可能是管道写入端打开的进程。并且不要忘记为父母中的孩子wait(2),否则你会得到一个充满僵尸的系统:)

请记住,您必须在代码中进行两次关闭:

  • 父进程中的一个,关闭管道的写入端,只留下一个读取描述符。

  • 子进程中的一个(就在exec(2)ing 之前)关闭管道的读取端,只给子进程留下一个写入描述符。

如果您想使用pipe(2)给孩子发送信息,请将上述两点的阅读改为写作,反之亦然。

【讨论】:

  • 感谢您的建议,尽管这些操作是在父进程中执行的。子进程中没有关闭读取端,因为在文件描述符上设置了O_CLOEXEC,这是不需要的。父进程阻塞的原因是子进程派生的守护进程仍然有文件描述符打开到用于将 STDOUT 从子进程重定向到父进程的管道。另请参阅此问题中的后续问题链接。
  • 嗯,这可能是另一种可能性,我不记得读过你在哪里建立了深度的进程层次结构......事实上,我只知道父母和僵尸孩子。僵尸对系统无能为力。这是一个死进程。它从未被执行过,没有附加任何资源,是文件描述符或任何其他东西。
  • 就在问题中:“子进程确实派生了两个守护进程,其目的似乎是监视子进程(子进程是专有应用程序,我没有任何控制权结束了,所以很难确定)。”
猜你喜欢
  • 2014-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-08
  • 2021-05-26
  • 1970-01-01
相关资源
最近更新 更多