Ответ 1
TL;DR: вы в настоящее время полагаетесь на неуказанное поведение signal
(2); используйте sigaction
(осторожно).
Во-первых, SIGCHLD
странно. На странице руководства для sigaction
;
POSIX.1-1990 запрещает установку действия для
SIGCHLD
наSIG_IGN
. POSIX.1-2001 позволяет эту возможность, так что игнорированиеSIGCHLD
может быть использовано для предотвращения создания зомби (см.wait
(2)). Тем не менее, исторические поведения BSD и System V для игнорированияSIGCHLD
различаются, поэтому единственный полностью переносимый метод обеспечения того, чтобы прекращенные дети не стали зомби, - это поймать сигналSIGCHLD
и выполнитьwait
(2) или аналогично.
И вот бит с wait
(2) справочная страница:
POSIX.1-2001 указывает, что если для параметра
SIGCHLD
установлено значениеSIG_IGN
или флагSA_NOCLDWAIT
установлен дляSIGCHLD
(см.sigaction
(2)), то дети, которые завершают работу, не станут зомби, а вызовwait()
илиwaitpid()
будет заблокирован до тех пор, пока все дети не будут завершены, а затем с ошибкой, установленным наECHILD
. (Исходный стандарт POSIX оставил поведение параметраSIGCHLD
доSIG_IGN
неуказанным. Обратите внимание, что даже если расположение по умолчаниюSIGCHLD
равно "игнорировать", явное указание расположения наSIG_IGN
приводит к различной обработке процесса зомби дети.) Linux 2.6 соответствует этой спецификации. Однако в Linux 2.4 (и ранее) нет: если вызовwait()
илиwaitpid()
выполняется, когдаSIGCHLD
игнорируется, вызов ведет себя так, как если быSIGCHLD
не игнорировались, то есть вызов блокирует до тех пор, пока следующий ребенок не завершится, а затем вернет идентификатор процесса и статус этого дочернего элемента.
Обратите внимание, что если обработка сигнала ведет себя как SIG_IGN
, то (под Linux 2.6+) вы увидите поведение, которое вы видите, т.е. wait()
вернет -1
и ECHLD
потому что ребенок будет автоматически получен.
Во-вторых, обработка сигналов с помощью pthreads
(который, как я думаю, вы используете здесь), печально тяжела. Способ, которым он предназначался для работы (как я уверен, вы знаете), заключается в том, что направленные сигналы направляются в произвольный поток в процессе, у которого сигнал разомкнут. Но в то время как потоки имеют собственную сигнальную маску, существует обработчик действия с широким спектром действий.
Объединяя эти две вещи, я думаю, что вы столкнулись с проблемой, с которой я столкнулся раньше. У меня возникли проблемы с обработкой SIGCHLD
для работы с signal()
(что достаточно справедливо, поскольку оно было устаревшим до pthreads), которые были исправлены, переместившись на sigaction
и тщательно настроив маски потока. Мой вывод в то время состоял в том, что библиотека C подражала (с sigaction
) тем, что я говорил ему делать с signal()
, но сработал pthreads
.
Обратите внимание, что в настоящее время вы полагаетесь на неуказанное поведение. На странице руководства signal(2)
:
Эффекты
signal()
в многопоточном процессе не определены.
Здесь я рекомендую вам:
- Переместитесь на
sigaction()
иpthread_sigmask()
. Явно настроить обработку всех сигналов, о которых вы заботитесь (даже если вы считаете, что текущее значение по умолчанию), даже если они установлены наSIG_IGN
илиSIG_DFL
. Я блокирую сигналы, пока я это делаю (возможно, избыток осторожности, но я где-то копировал пример).
Вот что я делаю (примерно):
sigset_t set;
struct sigaction sa;
/* block all signals */
sigfillset (&set);
pthread_sigmask (SIG_BLOCK, &set, NULL);
/* Set up the structure to specify the new action. */
memset (&sa, 0, sizeof (struct sigaction));
sa.sa_handler = handlesignal; /* signal handler for INT, TERM, HUP, USR1, USR2 */
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGINT, &sa, NULL);
sigaction (SIGTERM, &sa, NULL);
sigaction (SIGHUP, &sa, NULL);
sigaction (SIGUSR1, &sa, NULL);
sigaction (SIGUSR2, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL); /* I don't care about SIGPIPE */
sa.sa_handler = SIG_DFL;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGCHLD, &sa, NULL); /* I want SIGCHLD to be handled by SIG_DFL */
pthread_sigmask (SIG_UNBLOCK, &set, NULL);
-
По возможности установите все ваши обработчики сигналов и маски и т.д. перед любыми операциями
pthread
. По возможности не меняйте обработчики и маски сигналов (вам может понадобиться сделать это до и после вызововfork()
). -
Если вам нужен обработчик сигнала для
SIGCHLD
(вместо того, чтобы полагаться наSIG_DFL
), если возможно, пусть он будет получен любым потоком и будет использовать метод self-pipe или аналогичный для предупреждения основного программа. -
Если у вас есть потоки, которые выполняют/не обрабатывают определенные сигналы, попробуйте ограничить себя
pthread_sigmask
в соответствующем потоке, а неsig*
. -
На всякий случай, когда вы запускаете headlong в следующую проблему, с которой я столкнулся, убедитесь, что после того, как у вас есть
fork()
'd, вы снова настроили обработку сигнала с нуля (у ребенка), а не полагаетесь на что-либо вы можете наследовать родительский процесс. Если есть что-то хуже, чем сигналы, смешанные с pthread, он сигнализирует смешать с pthread с помощьюfork()
.
Примечание. Я не могу полностью объяснить, почему работает изменение (1), но оно зафиксировало то, что похоже на очень похожую проблему для меня, и в конце концов полагалось на то, что раньше было "неопределенным". Это ближе всего к вашей "гипотезе 2", но я думаю, что это действительно неполная эмуляция устаревших сигнальных функций (в частности, эмулирование ранее совершенного поведения signal()
, что и заставило его заменить на sigaction()
в первую очередь), но это это просто предположение).
Кстати, я предлагаю вам использовать wait4()
или (поскольку вы не используете rusage
) waitpid()
, а не wait3()
, поэтому вы можете указать определенный PID для ожидания. Если у вас есть что-то другое, что генерирует детей (у меня была библиотека), вы можете в конечном итоге ожидать неправильного. Тем не менее, я не думаю, что здесь происходит.