Отслеживание смерти детского процесса - PullRequest
20 голосов
/ 04 марта 2010

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

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

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

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

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

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

Ответы [ 4 ]

26 голосов
/ 04 марта 2010

Обычно вы пишете обработчик для 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) с указанным идентификатором дочернего процесса и синхронно дождаться его смерти. Или используйте WNOHANG, чтобы проверить его состояние без блокировки.

7 голосов
/ 06 февраля 2016

Ни одно из решений до сих пор не предлагало подход без использования SIGCHLD в качестве запросов. Вот реализация альтернативного подхода с использованием poll , как указано в этого ответа (который также объясняет, почему вы должны избегать использования 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
2 голосов
/ 04 марта 2010

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

Попробуйте изменить обработчик 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 голосов
/ 25 июня 2010

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

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