如何处理fork()后的execvp(…)错误?

我常做的事情是:

  • 叉子()
  • 儿童中的execvp(cmd,)

如果execvp由于没有findcmd而失败,我怎么能注意到父进程中的这个错误?

Solutions Collecting From Web of "如何处理fork()后的execvp(…)错误?"

众所周知的自我管道技巧可以适应这个目的。

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

这是如何工作的:

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

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

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

你终止孩子(通过调用_exit() ),然后父母可以注意到这一点(通过例如waitpid() )。 例如,您的孩子可以退出,退出状态为-1表示无法执行。 有一点需要注意的是,不可能从父母那里得知初始状态(即执行前)的孩子是否返回-1,或者是否是新执行的过程。

正如在下面的评论中所建议的那样,使用“异常”的返回代码将适合于更容易区分你的具体错误和exec()的ed程序中的错误。 常见的有1,2,3等,而高数字99,100等更为不寻常。 你应该保持你的数字低于255(无符号)或127(有符号)以增加可移植性。

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

在分叉之前,你也可以做一些错误检查,比如确保可执行文件存在。

如果你使用Glib之类的东西,有一些实用的功能可以做到这一点,并且他们有很好的错误报告。 看看手册的“ 产卵过程 ”部分。

1)使用_exit()exit() – 请参阅http://opengroup.org/onlinepubs/007908775/xsh/vfork.html – NB:适用于fork()以及vfork()

2)比退出状态更复杂的IPC的问题是,你有一个共享的内存映射,如果你做了太复杂的事情,可能会得到一些令人讨厌的状态 – 例如在多线程代码中,杀死的线程之一孩子)可能一直在锁。

你不应该怀疑你在父母过程中怎么注意到它,而且你应该记住,你必须注意到父母过程中的错误。 多线程应用程序尤其如此。

在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()函数,不做这样的清理。

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

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