Подтвердить что ты не робот

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

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

  • вилка()
  • execvp (cmd,) в дочернем

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

После 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(), которая не выполняет такую ​​очистку.

Ответ 4

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

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

Ответ 5

Ну, вы можете использовать функции wait/waitpid в родительском процессе. Вы можете указать переменную status, которая содержит информацию о статусе завершенного процесса. Недостатком является то, что родительский процесс блокируется, пока дочерний процесс не завершит выполнение.

Ответ 6

В любом случае exec не работает в подпроцессе, вы должны использовать kill (getpid(), SIGKILL), и родитель должен всегда иметь обработчик сигнала для SIGCLD и сообщать пользователю программы соответствующим образом, что процесс не был успешно началось.