【问题标题】:Linux, waitpid, WNOHANG and zombiesLinux、waitpid、WNOHANG 和僵尸
【发布时间】:2012-11-09 01:32:09
【问题描述】:

我需要能够:

  1. fork 一个进程并使其成为 execvp(我这样做了)
  2. 检查子进程execvp是否成功(不知道如何)
  3. 检查子进程是否完成(有问题)

我正在分叉一个进程,但我无法检查孩子的 execvp 是否有效。如果它失败了,我需要能够知道它失败了。目前我正在使用

-1 != waitpid( pid, &status, WNOHANG )

但似乎如果pid进程的execv失败,waitpid不会返回-1。

我该如何检查呢?我阅读了 waitpid 手册页,但我不清楚;也许我的英语不够好。

编辑:为了解释更多:
我正在为家庭作业构建自己的终端。我需要输入一个命令字符串,比如说“ls”,然后我必须执行命令。
孩子分叉后,孩子调用execvp来执行命令(在我解析字符串之后),父母需要检查命令末尾是否有'&'。
如果命令末尾不存在符号“&”,则父级需要等待子级执行。

所以我需要知道 execvp 是否失败。如果它没有失败,那么父母使用 waitpid 等待孩子完成它的执行。如果失败,则父母不会等待孩子。

【问题讨论】:

  • 有史以来最好的问题标题。
  • 这里有几个因素可能会影响您的解决方案:您execvping 您的流程是什么?你有能力编辑那个吗?在孩子退出之前不知道 execvp 是否失败可以吗?如果您发布更详细的问题陈述,我们或许可以提供更好的建议。
  • 如果发生上述故障,您不能从孩子向父母发送信号吗?
  • 在这种情况下,您实际上不需要知道 execvp 是否成功。您需要做的就是决定是否将WNOHANG 用于waitpid。如果有 &,则使用它。否则,使用一些结构来监控后台任务。
  • 实际上,看看我给stackoverflow.com/questions/12824848/… 的答案——它提供了相当不错的代码解决方案。

标签: c++ c fork wait waitpid


【解决方案1】:

#2 的常见解决方案是在 fork() 之前打开一个管道,然后在 exec 之后的子进程中写入它。在父级中,成功读取意味着 exec 失败;不成功的读取意味着 exec 成功并且写入从未发生。

// ignoring all errors except from execvp...
int execpipe[2];
pipe(execpipe);
fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC);
if(fork() == 0)
{
    close(execpipe[0]);
    execvp(...); // on success, never returns
    write(execpipe[1], &errno, sizeof(errno));
    // doesn't matter what you exit with
    _exit(0);
}
else
{
    close(execpipe[1]);
    int childErrno;
    if(read(execpipe[0], &childErrno, sizeof(childErrno)) == sizeof(childErrno))
    {
        // exec failed, now we have the child's errno value
        // e.g. ENOENT
    }
}

这让父级明确地知道 exec 是否成功,并且作为副产品,如果不成功,errno 值是什么。

如果 exec 成功,子进程可能仍会失败并显示退出代码,并且使用 WEXITSTATUS 宏检查状态也会为您提供该条件。

注意:使用 WNOHANG 标志调用 waitpid 是非阻塞的,您可能需要轮询进程,直到返回有效的 pid。

【讨论】:

  • @Ryan-Callhoun,谢谢,但读取会阻止父母,不是吗?如果 execvp 成功,我需要父母不要等待。我编辑了我的帖子以澄清。谢谢!
  • @m1o2:注意FD_CLOEXEC。当execv* 成功时,管道在子节点上关闭。对于 EOF,读取将返回 0。
  • 如果可用,使用pipe2O_CLOEXEC 而不是pipe,后跟fcntl。它不仅效率更高,而且还可以在打开管道时自动设置 close-on-exec 标志,因此对于其他线程也可能正在运行外部命令的多线程使用是安全的。截至目前,pipe2 是一个扩展,但它已被接受包含在 POSIX 的下一版本中。
【解决方案2】:

如果成功,则 exec 调用根本不应该返回,因为它将当前进程映像替换为另一个,因此如果成功,则意味着发生了错误:

execvp(...);
/* exec failed and you should exit the
   child process here with an error */
exit(errno);

要让父进程知道exec 是否失败,您应该阅读子进程的状态:

waitpid(pid, &status, WNOHANG);

然后使用手册页中的WEXITSTATUS(status) 宏:

WEXITSTATUS(status) 返回子节点的退出状态。 这个 由状态参数的最低有效 8 位组成 在调用 exit(3) 或 _exit(2) 或作为 main() 中的 return 语句的参数时指定的子节点

注意最后一条语句意味着如果exec 成功并运行该命令,您将获得该命令的main() 函数的退出状态,换句话说您无法可靠地分辨失败之间的区别exec 和一个失败的命令,所以这取决于你是否重要。

另一个问题:

如果命令末尾不存在符号“&”,则 parent 需要等待 child 执行。

无论&如何,您都需要在程序中的某个时间点对子进程调用wait(),以避免使子进程处于僵尸状态,

注意:当您使用WNOHANG 时,这意味着如果没有进程更改其状态,waitpid() 将立即返回,即它不会阻塞,我假设您知道这一点,否则使用wait() 或调用@987654333 @ 作为主循环的一部分。

【讨论】:

  • 我认为这个问题的难点在于让 parent 进程知道 execvp 失败了。
  • 由于 OP 没有澄清,我将扮演魔鬼的拥护者:如果我需要确保孩子在发送信号或其他控制信息之前正确地 execvped,怎么办?因此不能等待它真正退出?
  • 哦,如果您的waitpidexecvp 失败之前执行并返回,则会产生误报。
  • @FrankieTheKneeMan 但 OP 的第三个问题是检查它是否完成,这意味着他正在等待子进程并且不想与它交谈 :)
  • @FrankieTheKneeMan 和 waitpid 不会返回,除非孩子的状态发生变化
猜你喜欢
  • 2014-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多