Андрей Уваров
Практически в любой *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\n");
}else{
printf("I'm a
child\n");
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\n");
//остановим
выполнение родителя до ввода символа
getchar();
}else{
printf("I'm a
child\n");
}
return 0;
}
Откомпилируем и выполним текущий пример. Для того
чтобы «увидеть в глаза» нашего зомби, получим список процессов:
ps ax
[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 <defunct>]
18864 pts/5
R 0:00 ps ax
[dashin@dashin zombies]$
Значение поля 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
[dashin@dashin zombies]$
Разобравшись с тем, что представляют собой зомби,
стоит ознакомиться с некоторыми способами устранения создаваемой зомби
проблемы.
Один из самых простых способов «убить» зомби –
это «убить» их родителя. Если в системе «умирает» какой-либо процесс, то
специальный демон 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;
}
Способ избежать зомби в большинстве случаев
зависит от ситуации. Важно помнить, что зомби нужны забота и внимание. И только
тогда они не будут вас беспокоить.