我做常规事情:
如果execvp因为没有找到cmd而失败,我怎么能在父进程中注意到这个错误?
答案 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的信号处理程序,并以适当的方式告诉用户该程序该进程不是成功开始。