Les processus zombies

Au cours de leurs échanges avec le système et les programmes, les processus sont amenés à modifier leur état pour indiquer leur disponibilité. Ces changements sont le plus souvent dus à un besoin en ressources mémoire ou matérielle, à l’écriture de données ou encore à une attente (comme une action utilisateur).

Les états les plus connus sont l’état R (en cours d’exécution), S (en sommeil), T (stoppé) ou encore Z (zombie). Ce dernier est particulier car il désigne un processus qui, bien qu’ayant terminé son exécution, reste présent sur le système, en attente d’être pris en compte par son père.

Comment les processus zombies apparaissent ?

Quand un processus se termine normalement, le système désalloue les ressources qui lui avaient été attribuées (code, données, pile d’exécution) tout en conservant son bloc de contrôle. Le système va ensuite attribuer l’état TASK_ZOMBIE au processus fils, qui se traduira par l’état Z (Zombie) que l’on peut observer avec la commande ps. Le processus père sera ensuite prévenu, à l’aide du signal SIGCHLD, que son fils vient de finir sa tâche.

Les processus zombies contrôlés

Lorsque le système envoie le signal SIGCHLD au processus père, ce dernier va récupérer, à l’aide des primitives wait() ou waitpid(), le code de retour de son fils terminé. Le père cumulera alors les statistiques de son fils avec les siennes et supprimera son entrée de la table des processus, le fils pourra alors totalement être effacé du système.

En temps normal, l’état zombie d’un processus ne pose aucun problème sur le système tant que le programme a été pensé pour que le père puisse réceptionner l’état de ses fils terminés.

Les processus zombies errants

Si le processus père n’a pas été conçu pour réceptionner le code de retour de chaque processus fils qu’il crée, ces derniers resteront à l’état zombies pendant toute sa durée d’exécution, ce qui peut être problématique si le père engendre à intervalle régulier des fils et s’il n’a pas été conçu pour être arrêté (un serveur, une tâche de fond, …).

Sans cette prise en compte les processus fils zombies disposeront toujours d’un PID et occuperont la table des processus et resteront ainsi présent sur le système.

Comment se débarrasser des processus zombies ?

On ne peut pas… Ils sont déjà morts… La commande kill n’a aucun effet sur eux.

Le seul recours possible est de directement mettre un terme au processus père, avec par exemple la commande kill. Les processus fils zombies seront alors adoptés par init et ce dernier se chargera de les supprimer de la table des processus.

On pourrait penser, à tort, que les processus zombies ne sont pas gênant en soit puisqu’ils ne consomment aucune ressource et qu’ils ont terminés leur tâche. Le problème est que la quantité de processus qu’un système peut créer est limitée et un trop grand nombres de mauvaises synchronisations entre pères et fils entraînera à terme une saturation de la table des processus et bloquera tout le système qui ne pourra plus en créer de nouveaux.

Comment créer une invasion zombie ?

Si vous aimez The Walking Dead je vous propose un script en C qui permet d’étudier les processus Zombies. C’est un script très simple, conçu exprès pour facilement les observer avec l’aide de la commande ps -aux, à lancer dans un autre terminal à intervalles réguliers.

Script en C pour créer des processus zombies

Ce script permet de générer des processus zombies, il est possible, entre autres, de paramétrer le nombre de zombies à créer et leur durée de vie (temps d’attente).

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>

main()
{
int pid;
int i = 1;
int nbre = 5; //** Nombre de zombies à créer
int vcrea = 2; //** Vitesse de création des zombies, en seconde
int vdest = 5; //** Vitesse de destruction des zombies, en seconde
int tpsZ = 20; //** Temps d'attente pour observer les zombies, en seconde
int tpsP = 5; //** Temps d'attente pour observer le père, en seconde

printf ("------------------------------------------------------------------------\n");
printf ("-- Lancement de l'invasion --\n");
printf ("------------------------------------------------------------------------\n");

for (i; i <= nbre; i++)
{
pid = fork();

if (pid == 0) // processus fils
{
printf("* Zombie %d dit : Ceeeervau.....\n", i);
exit(1);
}
else // processus pere
{
sleep(vcrea);
}
}

printf ("------------------------------------------------------------------------\n" );
printf (" Vous avez %d processus zombies qui se baladent sur votre système. \n", i-1);
printf (" Ils vont errer pendant %d sec, vous pouvez les observer avec ps -aux. \n",tpsZ);
printf ("------------------------------------------------------------------------\n" );

// Temps d'attente, en seconde, pour observer les zombies
sleep(tpsZ);

// Le père fait ensuite appel à wait() et récupère ces fils terminés
for (i; i > 1; i--)
{
sleep(vdest);
wait(0);
printf("* Le zombie %d a disparu \n", i-1);
}

printf ("------------------------------------------------------------------------\n");
printf (" L'invasion zombie est terminé.\n" );
printf (" Le processus père est encore observable %d secondes.\n", tpsP );
printf ("------------------------------------------------------------------------\n");

// Le père reste encore x secondes pour l'observer
sleep(tpsP);


// Fin du script, le père se termine

}

La primitive fork() permet de créer un processus fils, le PID du processus fils est envoyé au père et la valeur 0 est envoyé au processus enfant. La primitive exit() quand à elle met fin à un processus et le système va désallouer les ressources auparavant attribuées, sauf l’entrée dans la table des processus. Enfin, l’appel à wait() permet au processus père d’attendre et de récupérer la terminaison de son fil via le signal SIGCHLD envoyé par le système lors de la terminaison d’un processus.

Compilation

Pour compiler la source du script, intitulé ici ZombieInvader.c, vous pouvez utiliser cette commande :

# gcc -o ZombieInvader ZombieInvader.c

Pour le lancer à partir d’un terminal :

# ./ZombieInvader

Vous devriez voir ceci :

--                     Lancement de l'invasion                        --
------------------------------------------------------------------------
* Zombie 1 dit : Ceeeervau.....
* Zombie 2 dit : Ceeeervau.....
* Zombie 3 dit : Ceeeervau.....
* Zombie 4 dit : Ceeeervau.....
* Zombie 5 dit : Ceeeervau.....
------------------------------------------------------------------------
Vous avez 5 processus zombies qui se baladent sur votre système.
Ils vont errer pendant 20 sec, vous pouvez les observer avec ps -aux.
------------------------------------------------------------------------

Après 20 sec, le script fera appel à wait() et les processus zombies seront supprimés.

Observation avec ps

En lançant, dans un autre terminal, la commande ps -aux au cours de l’exécution du script, on peut observer le statut Z des 5 processus enfants ainsi que l’annotation <defunct> en fin de ligne.

# ps -aux
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
[...]
0 1000 4974 3738 20 0 4200 672 hrtime S+ pts/0 0:00 ./ZombieInvader
1 1000 4975 4974 20 0 0 0 - Z+ pts/0 0:00 [ZombieInvader] <defunct>
1 1000 4976 4974 20 0 0 0 - Z+ pts/0 0:00 [ZombieInvader] <defunct>
1 1000 4977 4974 20 0 0 0 - Z+ pts/0 0:00 [ZombieInvader] <defunct>
1 1000 4978 4974 20 0 0 0 - Z+ pts/0 0:00 [ZombieInvader] <defunct>
1 1000 4979 4974 20 0 0 0 - Z+ pts/0 0:00 [ZombieInvader] <defunct>
[…]

Il est aussi possible avec les options -axjf de voir les processus de façon arborescente :

# ps -axjf
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
[...]
1611 3728 1363 1363 ? -1 Rl 1000 0:54 \_ gnome-terminal
3728 3737 1363 1363 ? -1 S 1000 0:00 \_ gnome-pty-helper
3728 3738 3738 3738 pts/0 4974 Ss 1000 0:00 \_ bash
3738 4974 4974 3738 pts/0 4974 S+ 1000 0:00 | \_ ./ZombieInvader
4974 4975 4974 3738 pts/0 4974 Z+ 1000 0:00 | \_ [ZombieInvader] <defunct>
4974 4976 4974 3738 pts/0 4974 Z+ 1000 0:00 | \_ [ZombieInvader] <defunct>
4974 4977 4974 3738 pts/0 4974 Z+ 1000 0:00 | \_ [ZombieInvader] <defunct>
4974 4978 4974 3738 pts/0 4974 Z+ 1000 0:00 | \_ [ZombieInvader] <defunct>
4974 4979 4974 3738 pts/0 4974 Z+ 1000 0:00 | \_ [ZombieInvader] <defunct>
3728 3755 3755 3755 pts/12 4373 Ss 1000 0:00 \_ bash
3755 4373 4373 3755 pts/12 4373 S+ 1000 0:01 | \_ vim ZombieInvader.c
3728 3880 3880 3880 pts/19 4980 Ss 1000 0:00 \_ bash
3880 4980 4980 3880 pts/19 4980 R+ 1000 0:00 | \_ ps -axjf
[…]

Ces deux commandes peuvent être lancées à tous moments et plusieurs fois lors de l’exécution du script. On remarquera aussi que les processus fils ont bien tous le même PPID (ici 4974), soit le PID le leur père.

> Partager <