Рубрика:
Программирование /
Анализ данных
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
АНДРЕЙ УВАРОВ
«Убиваем» зомби
Практически в любой *nix-подобной операционной системе существует такое понятие, как «зомби». В качестве примера возмём Linux (2.4.20). Зомби – это процесс, завершивший своё выполнение, но не удалённый. Зомби практически не занимают никаких ресурсов, но поскольку они являются процессами, то занимают место в proc. Как известно, количество процессов в системе ограничено, и если текущее количество процессов максимально, то операционная система отказывает нам в создании новых процессов, мотивируя это временным отсутствием ресурсов. Таким образом и рождается проблема с зомби: их возникает так много, что в системе больше не могут создаваться новые процессы. Но чаще зомби встречаются поодиночке. С зомби сталкивался практически каждый программист, а число программ, в которых были или есть проблемы с зомби, настолько велико, что перечислить их все не представляется возможным. Вот только некоторые из них: lynx, xchat, links, stunnel, galeon, xinetd.
Процесс становится зомби тогда, когда он уже завершился, а в его родительском процессе не была вызвана функция wait. Функции wait, wait3, wait4 и waitpid предназначены для получения родительским процессом кода завершения его потомка. В случае если потомок уже завершился, все системные ресурсы, занимаемые процессом, будут освобождены, а функция немедленно возвратит значение pid потомка.
Для начала попробуем просто создать процесс-потомок:
#include <unistd.h> #include <stdio.h> #include <sys/types.h>
int main(){ // С этого места начинает своё выполнение потомок pid_t chld_PID= fork(); // Если chld_PID == 0, то текущий процесс – потомок if(chld_PID!= 0){ printf("I"m a parent "); }else{ printf("I"m a child "); return 0; }
Вызовом fork создаётся копия текущего процесса. Выполнение нового процесса начинается с того места, где был произведён вызов fork. В случае благополучного создания нового процесса родителю fork возвращает pid потомка, а потомку возвращается ноль (значение 0 не является pid самого потомка, в Linux не существует процессов с pid, равным 0, для получения идентификатора текущего процесса необходимо использовать getpid). Это необходимо для того, чтобы процесс мог определить, является ли он родителем или потомком. В случае ошибки новый процесс не создаётся и возвращается -1.
Разобравшись с вызовом fork, попробуем создать одного зомби:
#include <unistd.h> #include <stdio.h> #include <sys/types.h>
int main(){ pid_t chld_PID= fork(); if(chld_PID!= 0){ printf("I"m a parent "); // Остановим выполнение родителя до ввода символа getchar(); }else{ printf("I"m a child "); } return 0; }
Откомпилируем и выполним текущий пример. Для того чтобы «увидеть в глаза» нашего зомби, получим список процессов:
[dashin@dashin zombies]$ ps ax
PID TTY STAT TIME COMMAND 1 ? S 0:05 init ............................................... 18730 pts/4 S 0:00 bash -rcfile .bashrc 18767 pts/4 S 0:00 ./one_zomb 18768 pts/4 Z 0:00 [one_zomb ] 18864 pts/5 R 0:00 ps ax
Значение поля STAT, равное Z, означает, что данный процесс и есть наш зомби. Но поодиночке зомби не страшны, поэтому модифицируем предыдущий пример:
#include <sys/types.h> #include <unistd.h> #include <stdio.h>
int main(){ pid_t our_child; while(our_child != -1){ our_child= fork(); if(our_child == 0){ return 0; } } getchar(); return 0; }
Итак, откомпилировав и выполнив текущий пример, мы получим максимальное количество процессов в системе. Если попробуем выполнить команду ps, то получим сообщение о невозможности выполнения нашей команды по известной нам причине (команда ps взята для примера, вызов практически любой команды будет завершён подобным образом).
[dashin@dashin zombies]$ ps ax
bash: fork: Resource temporarily unavailable
Разобравшись с тем, что представляют собой зомби, стоит ознакомиться с некоторыми способами устранения создаваемой зомби проблемы.
Один из самых простых способов «убить» зомби – это «убить» их родителя. Если в системе «умирает» какой-либо процесс, то специальный демон init наследует всех потомков умершего процесса и удаляет их, если они уже завершили своё выполнение. Но у нас может не хватать прав на удаление родителя. Возможна ситуация, когда родитель выполняет какие-то необходимые нам действия, и, удалив его, можно потерять данные. Может быть множество причин, препятствующих этому способу. И нам останется только по очереди убивать всех зомби.
Очевидно, что лучше предотвратить зомби, нежели с ними бороться. Одним из решений является использование вышеупомянутой функции wait.
#include <sys/types.h> #include <unistd.h> #include <stdio.h> int main(){ pid_t our_child; our_child= fork(); if(our_child == 0){ return 0; } sleep(10); wait(); getchar(); return 0; }
Для наглядности этого примера выполним команду:
top -d 1
и параллельно выполним предварительно откомпилированный пример (см. рис.1).
Рисунок 1. Видимо, не только мы умеем порождать зомби
Как и ожидалось, наш зомби, просуществовав около 10 секунд, будет удалён.
Учитывая то, что если дочерний процесс прерван или остановлен, он шлёт своему родителю сигнал SIGCHLD, тогда можно сделать у родителя обработчик этого сигнала и в нём вызывать wait.
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <signal.h> void killchld(){ wait(); } int main(){ pid_t our_child; int i; signal(SIGCHLD, killchld); for(i=1;i< 0xFF;i++){ our_child= fork(); if(our_child == 0){ return 0; } } getchar(); return 0; }
В данном примере мы устанавливаем обработчик сигнала SIGCHLD вызовом signal. В качестве первого параметра signal имеет сигнал (точнее сказать – номер сигнала), который мы собираемся обрабатывать, а второй параметр – имя нашей функции-обработчика. Но здесь есть одна небольшая тонкость – если в качестве второго параметра установим SIG_IGN, то наши зомби будут умирать, но как сказано в man: «POSIX (3.3.1.3) не определяет, что случается при SIGCHLD, который установлен в SIG_IGN». То есть если у нас следующий пример будет работать в Linux – не значит, что он будет работать в BSD. Следовательно, таким способом пользоваться не рекомендуется.
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <signal.h> int main(){ pid_t our_child; signal(SIGCHLD, SIG_IGN); // Так делать не стоит int i; for(i=1;i< 0xFF;i++){ our_child= fork(); if(our_child == 0){ return 0; } } getchar(); return 0; }
Способ избежать зомби в большинстве случаев зависит от ситуации. Важно помнить, что зомби нужны забота и внимание. И только тогда они не будут вас беспокоить.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|