13

我做常规的事情:

  • 叉子()
  • 子进程中的 execvp(cmd, )

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

4

6 回答 6

21

众所周知的自管技巧可以用于此目的。

#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
孩子的 execvp:没有这样的文件或目录
$ (睡眠 1 && killall -QUIT 睡眠 &); ./a.out 睡眠 60
等孩子...
被3人杀死的孩子
$ ./a.out 真
等孩子...
孩子以 0 退出

这是如何工作的:

创建一个管道,并设置写入端点:成功执行CLOEXEC时它会自动关闭。exec

在孩子身上,尝试exec. 如果成功了,我们就不再有控制权了,但是管道是关闭的。如果失败,将失败代码写入管道并退出。

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

于 2009-10-18T23:01:06.663 回答
6

您终止孩子(通过调用_exit())然后父母可以注意到这一点(例如通过waitpid())。例如,您的孩子可能会以 -1 的退出状态退出以指示执行失败。对此需要注意的一个问题是,无法从您的父母那里得知孩子处于其原始状态(即在执行之前)是否返回 -1 或者它是否是新执行的进程。

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

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

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

如果您使用Glib之类的东西,则有一些实用函数可以执行此操作,并且它们带有非常好的错误报告。查看手册的“生成过程”部分。

于 2009-10-18T14:07:47.200 回答
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 库清理的一部分,该清理应仅在进程终止时执行。相反,使用_exit()函数,它不会做这样的清理。

于 2009-10-18T14:11:35.903 回答
1

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

2) 做比退出状态更复杂的 IPC 的问题是你有一个共享内存映射,如果你做任何太复杂的事情,就有可能得到一些讨厌的状态——例如在多线程代码中,一个被杀死的线程(在孩子)可能一直拿着锁。

于 2009-10-18T15:41:26.030 回答
0

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

于 2009-10-18T14:10:32.507 回答
0

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

于 2013-02-19T02:08:13.403 回答