Уничтожение дочерних процессов из родительского обработчика сигналов зависает

#process #posix

#процесс #posix

Вопрос:

У меня проблема в теме процессов posix, и я не могу обойти.

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

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

  1. если родитель не выполняет функцию waitpid() для остановки дочернего процесса, сигнал, похоже, никогда не отправляется (зомби продолжают работать)
  2. если родительский элемент ожидает после каждого kill(), отправленного дочернему элементу, он просто зависает там, и дочерний элемент, похоже, игнорирует сигнал

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

 signal_handler( signal )
    foreach child in children
        kill( child, signal )
        waitpid( child, status )

    // Releasing system resources, etc.
    clean_up()

    // Restore signal handlers.
    set_signal_handlers_to_default()

    // Send back the expected "I exited after XY signal" to the parent by
    // executing the default signal handler again.
    kill( getpid(), signal )
 

В этой реализации выполнение останавливается на идентификаторе ожидания. Если я удалю идентификатор ожидания, дочерние процессы продолжат работать.

Я предполагаю, что, если обработчик сигналов не завершен, сигналы, отправленные с него, не отправляются дочерним процессам. Но почему они не отправляются, если я опускаю wait?

Заранее большое спасибо!

Комментарии:

1. Похоже, дочерние процессы застряли в обработчике сигналов. Какое значение children в дочерних процессах? Что cleanup делать?

2. childen в дочерних элементах пусто, если они являются листьями (в противном случае содержат их дочерние процессы). Сразу после разветвления я очищаю этот массив в дочернем элементе. cleanup является viod (пустым) для отладки.

3. Из любопытства, каково расположение SIGCHLD?

4. В том же коде есть еще одно ожидание, выполняемое независимо от обработчика сигнала, которое ожидает остановки всех дочерних процессов (а не только одного pid), но для настройки SIGCHLD нет обработчика сигналов.

5. Скорее всего, если до сих пор никто не придумал убедительного объяснения — я, конечно, нет — вам придется опубликовать некоторый код. Можете ли вы сократить код до наименьшего образца, который воссоздает проблему?

Ответ №1:

То, что вы описываете, должно работать, и это действительно так, со следующим тестовым примером:

 #include <stdio.h>
#include <unistd.h>
#include <signal.h>

#define NCHILDREN 3
pid_t child [NCHILDREN];

struct sigaction sa, old;

static void
handler (int ignore)
{
  int i;

  /* Kill the children.  */
  for (i = 0; i < NCHILDREN;   i)
    {
      if (child [i] > 0)
        {
          kill (child [i], SIGUSR1);
          waitpid (child [i], 0, 0);
        }
    }

  /* Restore the default handler.  */
  sigaction (SIGUSR1, amp;old, 0);

  /* Kill self.  */
  kill (getpid (), SIGUSR1);
}

int
main ()
{
  int i;

  /* Install the signal handler.  */
  sa.sa_handler = handler;
  sigemptyset (amp;sa.sa_mask);
  sa.sa_flags = 0;
  sigaction (SIGUSR1, amp;sa, amp;old);

  /* Spawn the children.  */
  for (i = 0; i < NCHILDREN;   i)
    {
      if ((child [i] = fork ()) == 0)
        {
          /* Each of the children: clear the array, wait for a signal
             and exit.  */
          while (i >= 0)
            child [i--] = -1;
          pause ();
          return 0;
        }
    }

  /* Wait to be interrupted by a signal.  */
  pause ();
  return 0;
}
 

Если вы видите, что родительский элемент зависает waitpid , это означает, что дочерний элемент не вышел. Попробуйте подключиться с помощью отладчика, чтобы увидеть, где заблокирован дочерний процесс, или, что проще, запустите программу strace(1) . Как вы очищаете свой pid-массив? Убедитесь, что дочерние процессы не пытаются выполнить вызов waitpid с параметром pid, равным <= 0. Убедитесь, что дочерние процессы не блокируют или игнорируют сигнал.

Комментарии:

1. Это сработало и для меня. Вам даже не нужен вызов waitpid() . Причина, по которой вызов waitpid() zombie все еще выполняется, заключается в том, что дочерний процесс уже заблокирован. Это объясняет, почему основной процесс зависает, когда установлен waitpid() .