如何在fork()之后处理execvp(...)错误?

时间:2009-10-18 13:58:02

标签: c++ linux posix exec fork

我做常规事情:

  • fork()的
  • execvp(command,)in child

如果execvp因为没有找到cmd而失败,我怎么能在父进程中注意到这个错误?

6 个答案:

答案 0 :(得分:18)

为此目的,众所周知的self-pipe trick可以是adapted

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>

int main(int argc, char **argv) {
    int pipefds[2];
    int count, err;
    pid_t child;

    if (pipe(pipefds)) {
        perror("pipe");
        return EX_OSERR;
    }
    if (fcntl(pipefds[1], F_SETFD, fcntl(pipefds[1], F_GETFD) | FD_CLOEXEC)) {
        perror("fcntl");
        return EX_OSERR;
    }

    switch (child = fork()) {
    case -1:
        perror("fork");
        return EX_OSERR;
    case 0:
        close(pipefds[0]);
        execvp(argv[1], argv + 1);
        write(pipefds[1], &errno, sizeof(int));
        _exit(0);
    default:
        close(pipefds[1]);
        while ((count = read(pipefds[0], &err, sizeof(errno))) == -1)
            if (errno != EAGAIN && errno != EINTR) break;
        if (count) {
            fprintf(stderr, "child's execvp: %s\n", strerror(err));
            return EX_UNAVAILABLE;
        }
        close(pipefds[0]);
        puts("waiting for child...");
        while (waitpid(child, &err, 0) == -1)
            if (errno != EINTR) {
                perror("waitpid");
                return EX_SOFTWARE;
            }
        if (WIFEXITED(err))
            printf("child exited with %d\n", WEXITSTATUS(err));
        else if (WIFSIGNALED(err))
            printf("child killed by %d\n", WTERMSIG(err));
    }
    return err;
}

这是一个完整的程序。

$ ./a.out foo
child's execvp: No such file or directory
$ (sleep 1 && killall -QUIT sleep &); ./a.out sleep 60
waiting for child...
child killed by 3
$ ./a.out true
waiting for child...
child exited with 0

这是如何运作的:

创建管道,并使写入端点CLOEXEC:成功执行exec时自动关闭。

在孩子身上,尝试exec。如果成功,我们将无法控制,但管道已关闭。如果失败,请将失败代码写入管道并退出。

在父级中,尝试从其他管道端点读取。如果read返回零,则管道已关闭且子项必须成功exec。如果read返回数据,那就是我们孩子写的失败代码。

答案 1 :(得分:6)

您终止孩子(通过致电_exit())然后父母可以注意到这一点(例如waitpid())。例如,您的孩子可以退出,退出状态为-1,表示执行失败。有一点需要注意的是,无法告诉你的父母处于原始状态(即在exec之前)的孩子是否返回-1或者是否是新执行的过程。

正如下面的评论中所建议的,使用“不寻常”的返回代码是合适的,以便更容易区分您的特定错误和exec()'ed程序中的错误。常见的是1,2,3等,而更高的数字99,100等更不寻常。您应该将数字保持在255(无符号)或127(有符号)以下,以提高可移植性。

由于waitpid会阻止你的应用程序(或者更确切地说是调用它的线程),你需要将它放在后台线程上,或者使用POSIX中的信令机制来获取有关子进程终止的信息。请参阅SIGCHLD信号和sigaction函数以挂接监听器。

您还可以在分叉之前进行一些错误检查,例如确保可执行文件存在。

如果您使用类似Glib的内容,则可以使用实用程序功能,并且它们具有非常好的错误报告功能。请查看手册的“spawning processes”部分。

答案 2 :(得分:1)

你不应该想知道能够强烈地>在父进程中注意到它,但是你应该记住你必须注意到父进程中的错误。对于多线程应用程序尤其如此。

在execvp之后,你必须调用函数来终止进程。您不应该调用任何与C库交互的复杂函数(例如stdio),因为它们的效果可能与父进程的libc功能的pthread混合在一起。因此,您无法在子进程中使用printf()打印消息,而是必须通知父级有关错误的信息。

最简单的方法是传递返回代码。为_exit()函数提供非零参数(请参阅下面的注释),用于终止子项,然后检查父项中的返回码。这是一个例子:

int pid, stat;
pid = fork();
if (pid == 0){
   // Child process
   execvp(cmd);
   if (errno == ENOENT)
     _exit(-1);
   _exit(-2);
}

wait(&stat);
if (!WIFEXITED(stat)) { // Error happened 
...
}

而不是_exit(),您可能会想到exit()函数,但它是不正确的,因为此函数将执行C库清理的一部分,只有在 parent <时才应该这样做/ em>进程终止。相反,使用_exit()函数,它不会进行这样的清理。

答案 3 :(得分:1)

1)使用_exit()而非exit() - 请参阅http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - 注意:适用于fork()以及vfork()

2)做出比出口状态更复杂的IPC的问题在于你有一个共享的内存映射,如果你做了太复杂的事情,就有可能得到一些讨厌的状态 - 例如在多线程代码中,其中一个被杀死的线程(在子进程中)可能一直处于锁定状态。

答案 4 :(得分:0)

好吧,您可以在父进程中使用wait / waitpid函数。您可以指定一个status变量,该变量包含有关已终止进程状态的信息。缺点是父进程被阻塞,直到子进程完成执行。

答案 5 :(得分:0)

任何时候exec在子进程中失败,你应该使用kill(getpid(),SIGKILL)并且父应该总是有一个SIGCLD的信号处理程序,并以适当的方式告诉用户该程序该进程不是成功开始。