Почему у меня есть `wait()` для дочерних процессов?
Даже несмотря на то, что страница linux man wait 1 очень хорошо объясняет, что вам нужно wait()
для дочерних процессов, чтобы они не превращались в зомби, он не говорит, почему вообще.
Я планировал свою программу (которая является моей первой многопоточной, так что извините мою наивность) вокруг цикла for(;;)
ever, который запускает дочерние процессы, которые получают exec()
ed и обязательно завершатся самостоятельно.
Я не могу использовать wait(NULL)
, потому что это делает невозможным параллельное вычисление, поэтому мне, вероятно, придется добавить таблицу процессов, в которой хранятся дочерние элементы, и использовать waitpid
- не очень, но через какое-то время прошло - что является проблемой, поскольку время работы детей варьируется от нескольких микросекунд до нескольких минут. Если я использую waitpid
слишком рано, мой родительский процесс будет заблокирован, когда я его исповедую слишком поздно, я становлюсь перегруженным зомби и больше не могу fork()
, что не только плохо для моего процесса, но и может вызвать непредвиденные проблемы всей системы.
Мне, вероятно, придется программировать некоторую логику использования некоторого максимального количества детей и блокировать родителя при достижении этого числа, но это не обязательно, потому что большинство детей быстро заканчиваются. Другое решение, о котором я могу думать (создание двухуровневого родительского процесса, порождающего одновременных детей, которые в свою очередь одновременно появляются и wait
для внуков), слишком сложно для меня прямо сейчас. Возможно, я мог бы также найти неблокирующую функцию для проверки для детей и использовать waitpid
только после их завершения.
Тем не менее вопрос:
Почему Linux держит зомби вообще? Почему я должен ждать своих детей? Это для обеспечения дисциплины в отношении родительских процессов? В течение десятилетий использования Linux у меня никогда не было ничего полезного из процессов зомби, я не совсем понимаю полезность зомби как "функции".
Если ответ заключается в том, что родительские процессы должны иметь способ узнать, что случилось с их детьми, то ради бога нет оснований считать зомби нормальными процессами и запретить создание процессов без зомби только потому, что там слишком много зомби. В системе, которую я сейчас разрабатываю, я могу только создавать от 400 до 500 процессов, прежде чем все перестанет останавливаться (это плохо поддерживается CentOS-системой, работающей на самом дешевом VServer, который я мог найти, - но все же 400 зомби составляют менее нескольких килобайт информации )
Ответы
Ответ 1
Мне, вероятно, придется добавить таблицу процессов, в которой хранятся дочерние элементы и должны использовать waitpid - не очень, но через некоторое время прошло - что является проблемой, поскольку время работы детей варьируется от нескольких микросекунд до нескольких минут. Если я использую waitpid рано, мой родительский процесс будет заблокирован
Ознакомьтесь с документацией для waitpid
. Вы можете сказать waitpid
НЕ блокировать (т.е. Немедленно возвращаться, если нет детей, чтобы пожать) с помощью параметра WNOHANG
. Кроме того, вам не нужно давать waitpid
PID. Вы можете указать -1
, и он будет ждать любого дочернего элемента. Поэтому вызов waitpid
, как показано ниже, соответствует вашему ограничению без блокировки и ограничению без сохранения-pids:
waitpid( -1, &status, WNOHANG );
Если вы действительно не хотите правильно обрабатывать создание процесса, тогда вы можете передать ответственность за init
, дважды нажав, пожиная ребенка и передав exec
внуку:
pid_t temp_pid, child_pid;
temp_pid = fork();
if( temp_pid == 0 ){
child_pid = fork();
if( child_pid == 0 ){
// exec()
error( EXIT_FAILURE, errno, "failed to exec :(" );
} else if( child_pid < 0 ){
error( EXIT_FAILURE, errno, "failed to fork :(" );
}
exit( EXIT_SUCCESS );
} else if( temp_pid < 0 ){
error( EXIT_FAILURE, errno, "failed to fork :(" );
} else {
wait( temp_pid );
}
В приведенном выше фрагменте кода дочерний процесс выдает свой собственный дочерний элемент, сразу же существует, а затем сразу же извлекается родителем. Внук сирота, принятый init
, и будет получен автоматически.
Почему Linux держит зомби вообще? Почему я должен ждать своего дети? Это для обеспечения дисциплины в отношении родительских процессов? В десятилетия использования Linux у меня никогда не было ничего полезного из зомби процессов, я не совсем понимаю полезность зомби как "функции". Если ответ заключается в том, что родительские процессы должны иметь способ узнать что случилось с их детьми, тогда ради бога нет причина считать зомби обычными процессами и запретить создание не зомби-процессы только потому, что слишком много зомби.
Как еще вы предлагаете, можно эффективно получить код выхода процесса? Проблема в том, что отображение кода PID <= > exit (et al.) Должно быть один к одному. Если ядро выпустило PID процесса, как только оно выйдет, будет получено или нет, а затем новый процесс наследует тот же PID и выйдет, как бы вы справились с сохранением двух кодов для одного PID? Как бы заинтересованный процесс извлекал код выхода для первого процесса? Не предполагайте, что никто не заботится о кодах выхода просто потому, что вы этого не делаете. Вы считаете, что это неприятность/ошибка, считаются полезными и чистыми.
В системе, которую я сейчас разрабатываю, я могу создавать только 400-500 перед тем, как все перестанет останавливаться (это плохо поддерживается Система CentOS, работающая на самом дешевом VServer, который я мог найти - но все же 400 зомби меньше, чем несколько килобайт информации)
Нечто похожее на то, чтобы сделать широко распространенное поведение ядра козлом отпущения для явно разочарований в плохо поддерживаемой/дешевой системе, кажется неправильным.
Как правило, максимальное количество процессов ограничено только вашей памятью. Вы можете увидеть свой предел с помощью:
cat /proc/sys/kernel/threads-max
Ответ 2
Ваше рассуждение обратное: ядро держит зомби, потому что они сохраняют состояние, которое вы можете получить с помощью wait()
и связанных системных вызовов.
Правильный способ обработки асинхронного дочернего завершения состоит в том, чтобы иметь обработчик SIGCHLD
, который выполняет wait()
для очистки дочерних процессов.
Ответ 3
Когда программа выходит из системы, она возвращает код возврата в ядро. Процесс зомби - это просто место для хранения кода возврата, пока родитель не сможет его получить. Вызов wait()
позволяет ядру знать, что код возврата для этого pid больше не нужен, и зомби удаляется.
Ответ 4
Хотя сохранение мертвого pid в таблице процессов в основном для обеспечения его последующего кода для родителя,
Мне приходится жаловаться на то, что там есть дизайн bad (но уже стал историческим и неизменным).
1. Нельзя предварительно объявить, что i_don_care_status_of( pid )
В ОС Windows у нас есть close( processHandle )
, чтобы добиться этого эффекта.
HANDLE aProcessHandle = CreateProcess(.....);
CloseHandle( aProcessHandle )
Чтобы преодолеть это, существуют некоторые не совершенные методы (из Wiki):
В современных UNIX-подобных системах (которые соответствуют спецификации SUSv3 в этом отношении) применяется следующий специальный случай: если родитель явно игнорирует SIGCHLD, установив свой обработчик на SIG_IGN (а не просто игнорируя сигнал по умолчанию) или флаг SA_NOCLDWAIT, вся информация о статусе дочернего выхода будет удалена, и никакие процессы зомби не будут оставлены. [1]
2. Нет обработки опорных счетчиков pid.
Когда процесс мертв, если нет ссылки на pid, ядро может немедленно удалить его.
3. Не удается получить код выхода несвязанного pid
Только родитель может получить код выхода pid, это смешно.
Нет надежного способа ждать несвязанного pid.
(Использование NETLINK + PROC_CONNECTOR может прослушивать событие выхода любого асинхронного pid).
В Windows это можно сделать с помощью WaitForSingleObject
HANDLE aProcessHandle = OpenProcess( pid... );
WaitForSingleObject(aProcessHandle, ...);
Эти недостатки, по-видимому, существуют, но дизайн Unix/Linux очень прост, поэтому мы должны его обнажить.
Ответ 5
Чтобы предоставить вам "exitcode" процесса, система должна сохранить для вас "базу данных процесса". Такая база данных с только кодом выхода называется "зомби". Вы можете использовать отдельный процесс, который будет периодически запрашивать "процессы зомби" для их "exitcode", тем самым эффективно освобождая эту память. То же самое можно сказать о Windows и других операционных системах. Linux здесь не особо особенный. Вам не нужно ждать процесса, просто спросите его "код выхода" после завершения процесса.