Почему я должен `wait ()` для дочерних процессов? - PullRequest
15 голосов
/ 29 декабря 2011

Несмотря на то, что man-страница linux для ожидания 1 очень хорошо объясняет, что вам нужно wait() для дочерних процессов, чтобы они не превратились в зомби, это вообще не говорит, почему.

Я планировал свою программу (которая является моей первой многопоточной программой, так что извините за мою наивность) вокруг цикла for(;;), который запускает дочерние процессы, которые exec() удаляются и обязательно завершаются самостоятельно.

Я не могу использовать wait(NULL), потому что это делает невозможным параллельное вычисление, поэтому мне, вероятно, придется добавить таблицу процессов, в которой хранятся дочерние pids, и мне придется использовать waitpid - не обязательно, но по прошествии некоторого времени - что является проблемой, потому что время работы детей варьируется от нескольких микросекунд до нескольких минут. Если я использую waitpid слишком рано, мой родительский процесс будет заблокирован, когда я использую его слишком поздно, я перегружен зомби и больше не могу fork(), что не только плохо для моего процесса, но может вызвать неожиданные проблемы на вся система.

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

Тем не менее вопрос:

Почему Linux вообще держит зомби? Почему я должен ждать своих детей? Это для обеспечения дисциплины на родительских процессах? За десятилетия использования Linux у меня никогда не было ничего полезного из процессов зомби, я не совсем понимаю, насколько полезна зомби как «особенность».

Если ответ заключается в том, что у родительских процессов должен быть способ выяснить, что случилось с их детьми, то, ради бога, нет никаких оснований считать зомби нормальными процессами и запрещать создание не зомби-процессов только потому, что слишком много зомби. В системе, которую я сейчас разрабатываю, я могу порождать от 400 до 500 процессов, пока все не остановится (это плохо обслуживаемая система CentOS, работающая на самом дешевом VServer, который я смог найти - но все же 400 зомби занимают менее нескольких кБ информации )

Ответы [ 5 ]

11 голосов
/ 29 декабря 2011

Мне, вероятно, придется добавить таблицу процессов, в которой хранятся дочерние pids. и придется использовать waitpid - не обязательно, но через некоторое время прошло - что является проблемой, потому что время работы детей варьируется от нескольких микросекунд до нескольких минут. Если я тоже использую waitpid рано, мой родительский процесс будет заблокирован

Ознакомьтесь с документацией для waitpid. Вы можете указать waitpid НЕ блокировать (т.е. немедленно возвращаться, если нет детей, чтобы пожинать), используя опцию WNOHANG. Более того, вам не нужно давать waitpid PID. Вы можете указать -1, и он будет ждать любого ребенка. Таким образом, вызов waitpid, как показано ниже, соответствует ограничениям без блокировки и ограничения без сохранения:

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 <=> (и др.) Должно быть одно к одному. Если ядро ​​выпустило PID процесса, как только он выйдет, пожнет или нет, а затем новый процесс наследует тот же PID и выйдет, как бы вы справились с хранением двух кодов для одного PID? Как заинтересованный процесс может получить код выхода для первого процесса? Не думайте, что никто не заботится о кодах выхода просто потому, что вы этого не делаете. То, что вы считаете неприятностью / ошибкой, широко считается полезным и чистым.

В системе, которую я сейчас разрабатываю, я могу вызвать только 400-500 обрабатывает, прежде чем все останавливается (это плохо поддерживается Система CentOS работает на самом дешевом VServer, который я смог найти - но все же 400 зомби - это информация размером не более нескольких килобайт)

Что-то в том, чтобы сделать общепринятым поведение ядра козлом отпущения за то, что явно вызывает разочарование по поводу плохо обслуживаемой / дешевой системы, кажется неправильным.

Как правило, ваше максимальное количество процессов ограничено только вашей памятью. Вы можете увидеть свой лимит с помощью:

cat /proc/sys/kernel/threads-max
4 голосов
/ 29 декабря 2011

Ваши рассуждения обратны: ядро ​​хранит зомби, потому что они хранят состояние, которое вы можете получить с помощью wait() и связанных системных вызовов.

Правильный способ обработки асинхронного дочернего завершения - иметь обработчик SIGCHLD, который выполняет wait() для очистки дочерних процессов.

1 голос
/ 23 июня 2017

Хотя сохранение мертвого pid в таблице процессов в основном предназначено для предоставления кода завершения его родителю позже,

Я должен жаловаться, что там есть какой-то плохой дизайн (но уже ставший историейи неизменяемый).

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 очень прост, поэтому нам придется обнажить его.

1 голос
/ 29 декабря 2011

Когда программа завершает работу, она возвращает код возврата в ядро.Зомби-процесс - это просто место для хранения кода возврата, пока родитель не сможет его получить.Вызов wait() позволяет ядру узнать, что код возврата для этого pid больше не нужен, и зомби удален.

0 голосов
/ 29 декабря 2011

Чтобы предоставить вам «код выхода» процесса, система должна сохранить для вас «базу данных процесса». Такая база данных с просто кодом выхода называется «зомби». Вы можете использовать отдельный процесс, который будет периодически запрашивать у «процессов зомби» их «код выхода», тем самым эффективно освобождая эту память. То же самое будет справедливо для Windows и других операционных систем. Linux здесь не особенный. Вам не нужно ждать процесса, просто спросите его «код выхода» после завершения процесса.

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