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

StackOverflow https://stackoverflow.com/questions/900411

Вопрос

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

До добавления возможности запуска их в фоновом режиме все процессы выполнялись на переднем плане.Для этого я просто вызывал 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;
}
Это было полезно?

Решение

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

ПРОДОЛЖЕНИЕ

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

ВНОХАНГ

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

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

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

Вероятно, вы не хотите игнорировать SIGCHLD и т. д., и ваш обработчик сигналов, вероятно, должен установить флаг, сообщающий вашему основному циклу: «Ой;там мертвый ребенок – иди забери этот труп!».

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

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

Другие советы

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

#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(), снова установите обработчик.Таким образом, будут обрабатываться только фоновые процессы.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top