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

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

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

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

Я считаю, что должен обработать SIGCHLD и что-то там сделать, но мне еще не до конца понятно, когда отправляется сигнал SIGCHLD, потому что я пытался также добавить ожидание (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 ]

5 голосов
/ 23 мая 2009

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

WCONTINUED

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

WNOHANG

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

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

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

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

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

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

2 голосов
/ 07 мая 2011

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

if(!background)
    pause();

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

2 голосов
/ 23 мая 2009

Это должно помочь вам начать. Основное отличие состоит в том, что я избавился от дочернего обработчика и добавил 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, что приведет к прерыванию системного вызова. В зависимости от ОС, он может автоматически перезапускать системный вызов или отправлять сигнал, который вы должны обработать.

0 голосов
/ 24 мая 2009

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

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

    waitpid(pid, NULL, 0);

    signal(SIGCHLD, childSignalHandler);
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...