Журнал Системный Администратор, Январь 2004

Журнал Системный Администратор

Январь 2004

Цена: $4.5 US

  Подписаться

Зарегистриванные пользователи, пожалуйста следуйте этой ссылке

Версия для печати Вернуться к оглавлению

«Убиваем» зомби

Андрей Уваров

Практически в любой *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;

}

Способ избежать зомби в большинстве случаев зависит от ситуации. Важно помнить, что зомби нужны забота и внимание. И только тогда они не будут вас беспокоить.




Все права зарезервированы. Этот материал принадлежит или лицензирован компании PLARANA INC. Только для частного использования. Любое распространение запрещено без письменного разрешения PLARANA INC
Версия для печати Вернуться к оглавлению