2009-05-22 2 views
8

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

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

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

Я считаю, что мне приходится обрабатывать SIGCHLD и делать что-то там, но мне еще нужно понять, когда посылается сигнал SIGCHLD, потому что я пытался также add wait (NULL) для childSignalHandler(), но это не сработало, потому что, как только я выполнил процесс в фоновом режиме, была вызвана функция childSignalHandler(), и, следовательно, wait (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, и этот процесс не имеет unwaited-для детей, которые были преобразованы в зомби процессы, вызывающий поток не должен блокироваться, пока все дети процесса, содержащего вызывающий поток завершается, а wait() и waitpid() прерываются и устанавливают errno на [ECHILD].

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

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

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

2

Это должно заставить вас начать. Основное различие заключается в том, что я избавился от обработчика ребенка и добавил 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; 
} 

EDIT: Добавление назад в обработке сигнала не трудно с помощью waitpid() WNOHANG. Это так же просто, как перемещение материала waitpid() с вершины цикла в обработчик сигнала. Вы должны знать о двух вещах:

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

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

+0

Спасибо, но мне действительно нужно использовать обработчик сигнала, это часть упражнения. –

+0

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

+0

Избегание глобальных переменных хорошо, но общение с обработчиком сигналов - это один из тех случаев, необходимо. – dwc

0

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

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

    waitpid(pid, NULL, 0); 

    signal(SIGCHLD, childSignalHandler); 
} 

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

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

+1

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

+0

Но я до сих пор не вижу, как реализовать глобальную переменную для этого, а также избежать состояния гонки. То, как я это вижу, добавление глобальной переменной для обработки этой ситуации будет по-прежнему создавать условие гонки. Позаботьтесь о том, чтобы привести пример, когда он не вводит условия гонки? –

+0

Обратите внимание, что второй аргумент 'signal()' должен быть указателем на функцию обработчика сигнала или 'SIG_IGN' или' SIG_DFL'; 'NULL' не является допустимым вариантом, хотя' SIG_IGN' часто является указателем на нулевую функцию. Вероятно, вы должны захватить возвращаемое значение из первого 'сигнала()' и восстановить его во втором вызове. Если другие сигналы SIGCHLD прибывают, 'waitpid()' может вернуться раньше с EINTR. Использование 'sigaction()' вместо 'signal()' позволит вам контролировать, какие сигналы разрешены. –

2

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

if(!background) 
    pause(); 

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

+1

Что делать, если дочерний сигнал находится между проверкой '' background'' и вызовом '' pause() ''? Здесь есть состояние гонки. – jcoffland