简体   繁体   English

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

[英]How to handle execvp(…) errors after fork()?

I do the regular thing: 我做常规的事情:

  • fork() 叉子()
  • execvp(cmd, ) in child 小孩的execvp(cmd,)

If execvp fails because no cmd is found, how can I notice this error in parent process? 如果execvp因为没有找到cmd而失败,我怎么能在父进程中注意到这个错误?

The well-known self-pipe trick can be adapted for this purpose. 著名的自管技巧可以适用于这一目的。

#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;
}

Here's a complete program. 这是一个完整的计划。

$ ./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

How this works: 这是如何工作的:

Create a pipe, and make the write endpoint CLOEXEC : it auto-closes when an exec is successfully performed. 创建管道,并使写入端点CLOEXEC :成功执行exec时自动关闭。

In the child, try to exec . 在孩子,尝试exec If it succeeds, we no longer have control, but the pipe is closed. 如果成功,我们将无法控制,但管道已关闭。 If it fails, write the failure code to the pipe and exit. 如果失败,请将失败代码写入管道并退出。

In the parent, try to read from the other pipe endpoint. 在父级中,尝试从其他管道端点读取。 If read returns zero, then the pipe was closed and the child must have exec successfully. 如果read返回零,则管道关闭,子exec必须成功exec If read returns data, it's the failure code that our child wrote. 如果read返回数据,那就是我们孩子写的失败代码。

You terminate the child (by calling _exit() ) and then the parent can notice this (through eg waitpid() ). 你终止了孩子(通过调用_exit() )然后父母可以注意到这一点(通过例如waitpid() )。 For instance, your child could exit with an exit status of -1 to indicate failure to exec. 例如,您的孩子可以退出,退出状态为-1,表示执行失败。 One caveat with this is that it is impossible to tell from your parent whether the child in its original state (ie before exec) returned -1 or if it was the newly executed process. 有一点需要注意的是,无法告诉你的父母处于原始状态(即exec之前)的孩子是否返回-1或者是否是新执行的过程。

As suggested in the comments below, using an "unusual" return code would be appropiate to make it easier to distinguish between your specific error and one from the exec()'ed program. 正如下面的评论中所建议的,使用“不寻常”的返回代码是合适的,以便更容易区分您的特定错误和exec()'ed程序中的错误。 Common ones are 1, 2, 3 etc. while higher numbers 99, 100, etc. are more unusual. 常见的是1,2,3等,而更高的数字99,100等更不寻常。 You should keep your numbers below 255 (unsigned) or 127 (signed) to increase portability. 您应该将数字保持在255(无符号)或127(有符号)以下以增加可移植性。

Since waitpid blocks your application (or rather, the thread calling it) you will either need to put it on a background thread or use the signalling mechanism in POSIX to get information about child process termination. 由于waitpid会阻止你的应用程序(或者更确切地说是调用它的线程),你需要将它放在后台线程上,或者使用POSIX中的信令机制来获取有关子进程终止的信息。 See the SIGCHLD signal and the sigaction function to hook up a listener. 请参阅SIGCHLD信号和sigaction函数以挂接侦听器。

You could also do some error checking before forking, such as making sure the executable exists. 您还可以在分叉之前执行一些错误检查,例如确保可执行文件存在。

If you use something like Glib , there are utility functions to do this, and they come with pretty good error reporting. 如果您使用像Glib这样的东西,那么有实用程序功能可以执行此操作,并且它们具有非常好的错误报告功能。 Take a look at the " spawning processes " section of the manual. 看一下本手册的“ 产卵过程 ”部分。

Not should you wonder how you can notice it in parent process, but also you should keep in mind that you must notice the error in parent process. 你不应该不知道你如何发现它的父进程,但你也应该记住,你一定要注意在父过程中的误差。 That's especially true for multithreaded applications. 对于多线程应用程序尤其如此。

After execvp you must place a call to function that terminates the process in any case. 在execvp之后,你必须调用函数来终止进程。 You should not call any complex functions that interact with C library (such as stdio), since effects of them may mingle with pthreads of libc functionality of parent process. 您不应该调用任何与C库交互的复杂函数(例如stdio),因为它们的效果可能与父进程的libc功能的pthread混合在一起。 So you can't print a message with printf() in child process and have to inform parent about the error instead. 因此,您无法在子进程中使用printf()打印消息,而是必须通知父级有关错误的信息。

The easiest way, among the other, is passing return code. 最简单的方法是传递返回代码。 Supply nonzero argument to _exit() function (see note below) you used to terminate the child and then examine the return code in the parent. _exit()函数提供非零参数(请参阅下面的注释),用于终止子项,然后检查父项中的返回码。 Here's the example: 这是一个例子:

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 
...
}

Instead of _exit() , you might think of exit() function, but it's incorrect, since this function will do a part of the C-library cleanup that should be done only when parent process terminates. 您可能会想到exit()函数而不是_exit() ,但它不正确,因为此函数将执行C库清理的一部分,只有在进程终止时才应该执行。 Instead, use _exit() function, that doesn't do such a cleanup. 相反,使用_exit()函数,它不进行这样的清理。

1) Use _exit() not exit() - see http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - NB: applies to fork() as well as vfork() . 1)使用_exit()而不是exit() - 参见http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - 注意:适用于fork()以及vfork()

2) The problem with doing more complicated IPC than the exit status, is that you have a shared memory map, and it's possible to get some nasty state if you do anything too complicated - eg in multithreaded code, one of the killed threads (in the child) could have been holding a lock. 2)执行比退出状态更复杂的IPC的问题是你有一个共享的内存映射,如果你做了太复杂的事情,就可能会出现一些讨厌的状态 - 例如在多线程代码中,一个被杀死的线程(在孩子本来可以拿着锁。

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

Well, you could use the wait / waitpid functions in the parent process. 好吧,您可以在父进程中使用wait / waitpid函数。 You can specify a status variable that holds info about the status of the process that terminated. 您可以指定一个status变量,其中包含有关已终止进程状态的信息。 The downside is that the parent process is blocked until the child process finishes execution. 缺点是父进程被阻塞,直到子进程完成执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM