我做常规的事情:
- 叉子()
- 子进程中的 execvp(cmd, )
如果 execvp 因为没有找到 cmd 而失败,我怎么能在父进程中注意到这个错误?
#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
返回数据,就是我们孩子写的失败码。
您终止孩子(通过调用_exit())然后父母可以注意到这一点(例如通过waitpid())。例如,您的孩子可能会以 -1 的退出状态退出以指示执行失败。对此需要注意的一个问题是,无法从您的父母那里得知孩子处于其原始状态(即在执行之前)是否返回 -1 或者它是否是新执行的进程。
正如下面评论中所建议的那样,使用“不寻常”的返回代码将更容易区分您的特定错误和来自 exec() 的程序的错误。常见的是 1、2、3 等,而更高的数字 99、100 等则更不寻常。您应该将您的号码保持在 255(无符号)或 127(有符号)以下以增加可移植性。
由于 waitpid 阻塞了您的应用程序(或者更确切地说是调用它的线程),您要么需要将其放在后台线程上,要么使用 POSIX 中的信号机制来获取有关子进程终止的信息。请参阅 SIGCHLD 信号和sigaction函数来连接侦听器。
您还可以在分叉之前进行一些错误检查,例如确保可执行文件存在。
如果您使用Glib之类的东西,则有一些实用函数可以执行此操作,并且它们带有非常好的错误报告。查看手册的“生成过程”部分。
你不应该想知道如何在父进程中注意到它,但你应该记住你必须注意到父进程中的错误。对于多线程应用程序尤其如此。
在 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()
函数,它不会做这样的清理。
1)_exit()
不使用exit()
- 请参阅http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - 注意:适用fork()
于vfork()
.
2) 做比退出状态更复杂的 IPC 的问题是你有一个共享内存映射,如果你做任何太复杂的事情,就有可能得到一些讨厌的状态——例如在多线程代码中,一个被杀死的线程(在孩子)可能一直拿着锁。
好吧,您可以在父进程中使用wait
/函数。waitpid
您可以指定一个status
变量来保存有关已终止进程状态的信息。缺点是父进程被阻塞,直到子进程完成执行。
任何时候 exec 在子进程中失败,你应该使用 kill(getpid(),SIGKILL) 并且父进程应该总是有一个 SIGCLD 的信号处理程序,并以适当的方式告诉程序的用户该进程没有成功启动。