Как обрабатывать ошибки execvp (...) после fork ()?

Я делаю обычную вещь:

  • вызов Fork()
  • execvp (cmd, ) в дочернем

Если execvp не удается, потому что не найден cmd, как я могу заметить эту ошибку в Родительском процессе?

6 ответов


известный самообман трубы трюк может быть адаптированный для этой цели.

#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 возвращает данные, это код сбоя, который написал наш ребенок.


вы завершаете ребенка (вызывая для _exit()), а затем родитель может заметить это (например,waitpid ()). Например, ваш ребенок может выйти со статусом выхода -1, чтобы указать на сбой exec. Одно предостережение при этом заключается в том, что невозможно сказать от вашего родителя, вернул ли ребенок в исходном состоянии (т. е. до exec) -1 или это был недавно выполненный процесс.

Как предложено в комментариях ниже, с использованием "необычного" код возврата будет соответствующим, чтобы было легче различать вашу конкретную ошибку и одну из программы exec (). Общие одни 1, 2, 3 etc. пока более высокие номера 99, 100, etc. более необычные. Для повышения переносимости номера должны быть ниже 255 (без знака) или 127 (со знаком).

поскольку waitpid блокирует ваше приложение (или, скорее, вызывающий его поток) , вам нужно будет либо поместить его в фоновый поток, либо использовать механизм сигнализации в POSIX, чтобы получить информация о прекращении дочернего процесса. См. сигнал SIGCHLD и sigaction функция для подключения слушателя.

вы также можете выполнить проверку ошибок перед разветвлением, например, убедиться, что исполняемый файл существует.

Если вы используете что-то вроде Глеб, есть функции утилиты для этого, и они поставляются с довольно хорошей отчетностью об ошибках. Взгляните на "процессы нереста раздел" руководства.


1) Использовать _exit() не exit() - смотрите http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - NB: применяется к fork() а также vfork().

2) Проблема с выполнением более сложного IPC, чем статус выхода, заключается в том, что у вас есть карта общей памяти, и можно получить неприятное состояние, если вы делаете что - то слишком сложное-например, в многопоточном коде один из убитых потоков (в ребенке) мог бы удерживать блокировку.


не следует удивляться, как вы can обратите внимание на это в Родительском процессе, но также вы должны иметь в виду, что вы должны обратите внимание на ошибку в Родительском процессе. Это особенно верно для многопоточных приложений.

после execvp вы должны вызов функции, которая завершает процесс в любом случае. Вы не должны вызывать сложные функции, которые взаимодействуют с библиотекой C (например, stdio), так как их эффекты могут смешиваться с pthreads функциональности libc родительского процесса. Таким образом, вы не можете распечатать сообщение с помощью 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 и сообщить пользователю программы соответствующим образом, что процесс не был успешно запущен.