2010-03-04 6 views
16

Как я могу отслеживать смерть дочернего процесса, не приводя родительский процесс до тех пор, пока дочерний процесс не будет убит?Отслеживание смерти дочернего процесса

Я пытаюсь сценарий клиент-сервер, где сервер принимает соединение от клиента и открывает новый процесс для каждого принимаемого им соединения.

Я игнорирую сигналы SIGCHLD, чтобы предотвратить создание зомби.

signal(SIGCHLD, SIG_IGN); 
while(1) 
{ 
    accept(); 
    clients++; 
    if(fork() ==0) 
    { 
    childfunction(); 
    clients--; 
    } 
    else 
    { 
    } 
} 

Проблема в приведенном выше сценарии является то, что, если дочерний процесс получает убит в функции childfunction(), глобальная переменная clients не получает убавления.

ПРИМЕЧАНИЕ: Я ищу решение без использования сигнала SIGCHLD ... Если возможно

+0

Вы можете что-то сделать в обработчике сигналов SIGCHLD – tristan

+0

Я уже упоминал ... Сигнал SIGCHLD игнорируется ..? – codingfreak

+7

+1 для безумно драматического титула и вступительного предложения. D: –

ответ

22

Обычно вы пишете обработчик SIGCHLD который вызывает waitpid() на Pid -1. Вы можете использовать возвращаемое значение для определения того, что умерли pid. Например:

void my_sigchld_handler(int sig) 
{ 
    pid_t p; 
    int status; 

    while ((p=waitpid(-1, &status, WNOHANG)) != -1) 
    { 
     /* Handle the death of pid p */ 
    } 
} 

/* It's better to use sigaction() over signal(). You won't run into the 
* issue where BSD signal() acts one way and Linux or SysV acts another. */ 

struct sigaction sa; 

memset(&sa, 0, sizeof(sa)); 
sa.sa_handler = my_sigchld_handler; 

sigaction(SIGCHLD, &sa, NULL); 

В качестве альтернативы вы можете вызвать waitpid(pid, &status, 0) с процессом ребенка ID указанного, и синхронно ждать его, чтобы умереть. Или используйте WNOHANG, чтобы проверить его статус без блокировки.

+0

Но в моем примере SIGCHLD IGNORED ?? – codingfreak

+1

@codingfreak И я предлагаю вам, возможно, захочет переоценить это. Вам не нужно игнорировать его, чтобы избежать зомби. Когда вы 'waitpid()' зомби "уходит. – asveikau

+0

@asveikau - Я пытаюсь создать процесс демона .. и использование обработчика SIGCHLD нарушает стабильность приложения. Все время родительский процесс должен ждать в обработчике SIGCHLD .. – codingfreak

2

Вы не хотите зомби. Если дочерний процесс умирает, а родитель все равно RUNNING, но никогда не выдает вызов для сбора статуса, система не освобождает ресурсы, связанные с дочерним элементом, а процесс zombie/defunct остается в таблице proc.

Попробуйте изменить SIGCHLD обработчик чем-то ближе к следующему:


void chld_handler(int sig) { 
    pid_t p; 
    int status; 

    /* loop as long as there are children to process */ 
    while (1) { 

     /* retrieve child process ID (if any) */ 
     p = waitpid(-1, &status, WNOHANG); 

     /* check for conditions causing the loop to terminate */ 
     if (p == -1) { 
      /* continue on interruption (EINTR) */ 
      if (errno == EINTR) { 
       continue; 
      } 
      /* break on anything else (EINVAL or ECHILD according to manpage) */ 
      break; 
     } 
     else if (p == 0) { 
      /* no more children to process, so break */ 
      break; 
     } 

     /* valid child process ID retrieved, process accordingly */ 
     ... 
    } 
} 

Вы можете дополнительно маска/блок дополнительных SIGCHLD сигналов во время выполнения обработчика сигнала, используя sigprocmask(). Заблокированная маска должна быть возвращена к исходному значению после завершения процедуры обработки сигнала.

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

+0

После внесенных изменений, как вы сказали .. Я не вижу никаких созданных зомби. Позвольте мне посмотреть, как это происходит с демоном сервера HTTP ... – codingfreak

+0

Я не ожидаю серьезных проблем с использованием этого кода в вашем демоне. – jschmier

0

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

4

Ни одно из решений пока не предлагает подход без использования SIGCHLD в качестве запросов. Вот реализация альтернативного подхода с использованием poll, как указано в this answer (что также объясняет, почему следует избегать использования SIGCHLD в подобных ситуациях):

Убедитесь, что труба в/из каждого дочернего процесса вы создаете , Это может быть либо stdin/stdout/stderr, либо просто дополнительный фиктивный fd. Когда дочерний процесс завершается, его конец будет закрыт, и ваш основной цикл событий обнаружит активность в этом дескрипторе файла. Из-за того, что он закрыт, вы узнаете, что дочерний процесс умер, и назовите waitpid, чтобы пожинать зомби.

(Примечание: я опустил некоторые лучшие практики, как проверка ошибок и очистка дескрипторов файлов для краткости)

/** 
* Specifies the maximum number of clients to keep track of. 
*/ 
#define MAX_CLIENT_COUNT 1000 

/** 
* Tracks clients by storing their process IDs and pipe file descriptors. 
*/ 
struct process_table { 
    pid_t clientpids[MAX_CLIENT_COUNT]; 
    struct pollfd clientfds[MAX_CLIENT_COUNT]; 
} PT; 

/** 
* Initializes the process table. -1 means the entry in the table is available. 
*/ 
void initialize_table() { 
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
     PT.clientfds[i].fd = -1; 
    } 
} 

/** 
* Returns the index of the next available entry in the process table. 
*/ 
int get_next_available_entry() { 
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
     if (PT.clientfds[i].fd == -1) { 
      return i; 
     } 
    } 
    return -1; 
} 

/** 
* Adds information about a new client to the process table. 
*/ 
void add_process_to_table(int i, pid_t pid, int fd) { 
    PT.clientpids[i] = pid; 
    PT.clientfds[i].fd = fd; 
} 

/** 
* Removes information about a client from the process table. 
*/ 
void remove_process_from_table(int i) { 
    PT.clientfds[i].fd = -1; 
} 

/** 
* Cleans up any dead child processes from the process table. 
*/ 
void reap_zombie_processes() { 
    int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0); 

    if (p > 0) { 
     for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
      /* Has the pipe closed? */ 
      if ((PT.clientfds[i].revents & POLLHUP) != 0) { 
       // printf("[%d] done\n", PT.clientpids[i]); 
       waitpid(PT.clientpids[i], NULL, 0); 
       remove_process_from_table(i); 
      } 
     } 
    } 
} 

/** 
* Simulates waiting for a new client to connect. 
*/ 
void accept() { 
    sleep((rand() % 4) + 1); 
} 

/** 
* Simulates useful work being done by the child process, then exiting. 
*/ 
void childfunction() { 
    sleep((rand() % 10) + 1); 
    exit(0); 
} 

/** 
* Main program 
*/ 
int main() { 
    /* Initialize the process table */ 
    initialize_table(); 

    while (1) { 
     accept(); 

     /* Create the pipe */ 
     int p[2]; 
     pipe(p); 

     /* Fork off a child process. */ 
     pid_t cpid = fork(); 

     if (cpid == 0) { 
      /* Child process */ 
      close(p[0]); 
      childfunction(); 
     } 
     else { 
      /* Parent process */ 
      close(p[1]); 
      int i = get_next_available_entry(); 
      add_process_to_table(i, cpid, p[0]); 
      // printf("[%d] started\n", cpid); 
      reap_zombie_processes(); 
     } 
    } 

    return 0; 
} 

А вот пример вывода запуска программы с printf заявления незакомментированным:

[31066] started 
[31067] started 
[31068] started 
[31069] started 
[31066] done 
[31070] started 
[31067] done 
[31068] done 
[31071] started 
[31069] done 
[31072] started 
[31070] done 
[31073] started 
[31074] started 
[31072] done 
[31075] started 
[31071] done 
[31074] done 
[31081] started 
[31075] done 
Смежные вопросы