Как правильно ждать переднего плана / фоновых процессов в моей собственной оболочке в C?

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

Прежде чем добавить возможность запуска их в фоновом режиме, все процессы работали на переднем плане. И для этого я просто вызвал wait(NULL) после выполнения любого процесса с помощью execvp(). Теперь я проверяю символ '&' в качестве последнего аргумента и, если он есть, запускаю процесс в фоновом режиме, не вызывая wait(NULL), и процесс может успешно выполняться в фоновом режиме, если я вернусь в свою оболочку.

Это все работает должным образом (я думаю), проблема сейчас в том, что мне также нужно как-то вызвать wait () (или waitpid ()?), Чтобы фоновый процесс не оставался "зомби". Это моя проблема, я не уверен, как это сделать...

Я полагаю, что должен обработать SIGCHLD и что-то сделать там, но мне еще не до конца понятно, когда отправляется сигнал SIGCHLD, потому что я пытался также добавить wait(NULL) в childSignalHandler (), но это не сработало, потому что, как только я выполнив процесс в фоновом режиме, была вызвана функция childSignalHandler () и, следовательно, ожидание (NULL), означающее, что я ничего не мог сделать со своей оболочкой, пока не завершился "фоновый" процесс. Который больше не работал в фоновом режиме из-за ожидания в обработчике сигнала.

Чего мне не хватает во всем этом?

И последнее, часть этого упражнения. Мне также нужно напечатать изменения статуса процессов, например, завершение процесса. Таким образом, любое понимание этого также очень ценится.

Это мой полный код на данный момент:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>

#include "data.h" // Boolean typedef and true/false macros


void childSignalHandler(int signum) {
    //
}

int main(int argc, char **argv) {
    char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
    bool background;
    ssize_t rBytes;
    int aCount;
    pid_t pid;

    //signal(SIGINT, SIG_IGN);

    signal(SIGCHLD, childSignalHandler);

    while(1) {
        write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
        rBytes = read(0, bBuffer, BUFSIZ-1);

        if(rBytes == -1) {
            perror("read");
            exit(1);
        }

        bBuffer[rBytes-1] = '\0';

        if(!strcasecmp(bBuffer, "exit")) {
            exit(0);
        }

        sPtr = bBuffer;
        aCount = 0;

        do {
            aPtr = strsep(&sPtr, " ");
            pArgs[aCount++] = aPtr;
        } while(aPtr);

        background = FALSE;

        if(!strcmp(pArgs[aCount-2], "&")) {
            pArgs[aCount-2] = NULL;
            background = TRUE;
        }

        if(strlen(pArgs[0]) > 1) {
            pid = fork();

            if(pid == -1) {
                perror("fork");
                exit(1);
            }

            if(pid == 0) {
                execvp(pArgs[0], pArgs);
                exit(0);
            }

            if(!background) {
                wait(NULL);
            }
        }
    }

    return 0;
}

4 ответа

Решение

Существуют различные варианты waitpid() чтобы помочь вам (цитаты из стандарта POSIX):

WCONTINUED

Функция waitpid() должна сообщать о состоянии любого продолжающегося дочернего процесса, указанного в pid, состояние которого не было сообщено, так как оно продолжалось после остановки управления заданиями.

WNOHANG

Функция waitpid() не должна приостанавливать выполнение вызывающего потока, если статус не сразу доступен для одного из дочерних процессов, указанных в pid.

В частности, WNOHANG позволит вам увидеть, есть ли какие-нибудь трупы, которые нужно собрать, не заставляя ваш процесс блокировать ожидание трупа.

Если вызывающему процессу установлено SA_NOCLDWAIT или для SIGCHLD установлено значение SIG_IGN, и у процесса нет ожидающих дочерних элементов, которые были преобразованы в процессы зомби, вызывающий поток должен блокироваться до тех пор, пока все дочерние элементы процесса, содержащего вызывающий поток, не завершатся, и wait() и waitpid() завершаются с ошибкой и устанавливают errno [ECHILD].

Вы, вероятно, не хотите игнорировать SIGCHLD и т. Д., И ваш обработчик сигнала, вероятно, должен установить флаг, чтобы сообщить вашему главному циклу: "Упс, мертвый ребенок - иди и собери этот труп!".

Сигналы SIGCONT и SIGSTOP также будут иметь отношение к вам - они используются для перезапуска и остановки дочернего процесса, соответственно (в данном контексте, во всяком случае).

Я бы порекомендовал взглянуть на книгу Рочкинда или книгу Стивенса - они подробно освещают эти вопросы.

Это должно начать вас. Основное отличие состоит в том, что я избавился от дочернего обработчика и добавил waitpid в основном цикле с обратной связью. Протестировано и работает, но, очевидно, нужно больше TLC.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>

int main(int argc, char **argv) {
        char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
        int background;
        ssize_t rBytes;
        int aCount;
        pid_t pid;
        int status;
        while(1) {
                pid = waitpid(-1, &status, WNOHANG);
                if (pid > 0)
                        printf("waitpid reaped child pid %d\n", pid);
                write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
                rBytes = read(0, bBuffer, BUFSIZ-1);
                if(rBytes == -1) {
                        perror("read");
                        exit(1);
                }
                bBuffer[rBytes-1] = '\0';
                if(!strcasecmp(bBuffer, "exit")) 
                        exit(0);
                sPtr = bBuffer;
                aCount = 0;
                do {
                        aPtr = strsep(&sPtr, " ");
                        pArgs[aCount++] = aPtr;
                } while(aPtr);
                background = (strcmp(pArgs[aCount-2], "&") == 0);
                if (background)
                        pArgs[aCount-2] = NULL;
                if (strlen(pArgs[0]) > 1) {
                        pid = fork();
                        if (pid == -1) {
                                perror("fork");
                                exit(1);
                        } else if (pid == 0) {
                                execvp(pArgs[0], pArgs);
                                exit(1);
                        } else if (!background) {
                                pid = waitpid(pid, &status, 0);
                                if (pid > 0)
                                        printf("waitpid reaped child pid %d\n", pid);
                        }
                }
        }
        return 0;
}

РЕДАКТИРОВАТЬ: Добавление обратно в обработке сигналов не сложно с waitpid() используя WNOHANG. Это так же просто, как перемещение waitpid() вещи из верхней части цикла в обработчик сигнала. Вы должны знать две вещи, хотя:

Во-первых, даже "передний план" процессов отправит SIGCHLD. Поскольку может быть только один процесс переднего плана, вы можете просто сохранить pid переднего плана (возвращаемое родителем значение из fork()) в переменной, видимой для обработчика сигнала, если вы хотите выполнить специальную обработку переднего и заднего плана.

Во-вторых, вы в настоящее время делаете блокировку ввода / вывода на стандартном входе (read() в верхней части главной петли). Вы, скорее всего, будете заблокированы на read() когда происходит SIGCHLD, что приводит к прерыванию системного вызова. В зависимости от ОС он может перезапустить системный вызов автоматически или может послать сигнал, который вы должны обработать.

Вы можете использовать:

if(!background)
    pause();

Таким образом, процесс блокируется, пока не получит SIGCHLD сигнал, и обработчик сигнала будет делать вещи ожидания.

Вместо того, чтобы использовать глобальную переменную, я подумал о другом решении:

if(!background) {
    signal(SIGCHLD, NULL);

    waitpid(pid, NULL, 0);

    signal(SIGCHLD, childSignalHandler);
}

Если я запускаю процесс переднего плана, "удалите" обработчик для SIGCHLD, чтобы он не вызывался. Затем, после waitpid(), снова установите обработчик. Таким образом, будут обрабатываться только фоновые процессы.

Как вы думаете, что-то не так с этим решением?

Другие вопросы по тегам