vendredi 2 mai 2014

Unix process: To zombie or not to zombie?

 

 Dialogue imaginaire:

  • Robert:  "Au secours, un processus zombie!"
  • Arturo:  "T'es fou! Je ne vois qu'un crochetage!"
  • Jean-Baptiste:  "Crochetage, tu parles... Jardinage sauvage, oui."

Au fait!

fa$ ps -p 11025 -f 
UID      PID   PPID  C STIME TTY   TIME     CMD 
francois 11025 11024 0 16:33 pts/2 00:00:00 [arturo] 
fa$

Donc? Ce processus (sur un système Ubuntu) est-il un zombie ou non ? On ne parle pas ici de l'utilisateur mais bien du processus. Il arrive donc qu'en présence d'une trace comme celle montrée ci-dessus, Robert s'exclame : "Un zombie !"

Zombie

Un processus zombie est un processus qui a terminé son exécution via exit() ou des moyens plus violents, mais dont le père n'a pas encore ouvert le faire-part de décès: en bref, le père n'a pas invoqué l'appel wait(). Il y d'autres moyens à la disposition du processus père.

Usine à zombie

Voici un petit programme qui crée entre 1 et 16 zombies, puis attend 3 minutes avant de disparaître. Les zombies disparaîtront aussi, parce que leur père ayant cessé son existence en ignorant la leur, les zombies orphelins, se retrouveront rattachés au processus 1 (init) qui les fera passer de zombie à inexistant.

fa$ cat zombie_factory.c
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    int pid;
    unsigned long nb = 1;

    if (argc >= 2) {
        nb = strtol(argv[1], NULL, 10);
        if (nb > 16) nb = 16;
    }
    for(; nb > 0; nb--) {
        pid = fork();
        if (pid == -1) {
          perror("Cannot fork");
          exit(1);
        }
        if (pid == 0) exit(0);
    }
    sleep(180);
    return 0;
}

Robert aurait raison?

Si on lance ce processus et que l'on examine le résultat d'un ps:

fa$ ./zombie_factory 4 &
[2] 11284

fa$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
francois 11284  2132  0 11:39 pts/2    00:00:00 ./zombie_factory 4
francois 11285 11284  0 11:39 pts/2    00:00:00 [zombie_factory] <defunct>
francois 11286 11284  0 11:39 pts/2    00:00:00 [zombie_factory] <defunct>
francois 11287 11284  0 11:39 pts/2    00:00:00 [zombie_factory] <defunct>
francois 11288 11284  0 11:39 pts/2    00:00:00 [zombie_factory] <defunct>
francois 11289  2132  0 11:39 pts/2    00:00:00 ps -f

fa$

Ah, oui les processus décédés ont bien leur nom de commande entre crochets. mais ils sont aussi marqués comme <defunct>. Notre premier processus [arturo] n'avait pas la mention <defunct>. Erreur ou oubli de la morgue?

Zombie or not Zombie?

Si on ajoute l'indicateur -l à la commande ps, on obtient alors aussi l'état (status) du processus, en deuxième colonne. L'état de notre processus est "S" : dormant (sleeping).

fa$ ps -p 11025 -fl
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 S francois 11025 11024  0  80   0 -   501 hrtime 11:49 pts/2    00:00:00 [arturo]
fa$

L'état d'un processus zombie est normalement "Z". Vérifions:

fa$ ./zombie_factory &
[2] 11321

F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
1 Z francois 11322 11321  0  80   0 -     0 exit   12:02 pts/2    00:00:00 [zombie_factory] <defunct>
fa$

Donc...

Robert a tort

Notre processus [arturo] n'est pas en état "Z" mais en état "S". Il ne s'agit donc pas d'un zombie. Désolé Robert. Mais pourquoi son nom apparaît-il entre crochets?  RTFM !

Extrait de la page manuel ps
...
Modifications to the arguments may be shown. 
... Sometimes the process args will be
unavailable; when this happens, ps will instead print
the executable name in brackets
 

Entrée en scène d'Arturo

Donc, si on modifie les arguments, ou si les arguments sont inaccessibles... les modifications apportées seront visibles, ou le nom du processus sera affiché entre crochet.

Commençons par essayer de rendre les arguments inaccessibles... Ben, il n'y a qu'à pas en passer lors de l'exec. Ce n'est après tout qu'une convention de mettre le nom de la commande en argv[0] et non une obligation. Mettons un peu de désordre dans les conventions.
J´ai fantaisie de mett´ dans notre vie Un p´tit grain de fantaisie! Youpi, Youpi´ (B. Lapointe)
fa$ cat arturo.c
 

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char** argv)
{
    int pid;
    int res;
    if (argc >= 1) {
        pid = fork();
        if (pid == -1) {
            perror("Cannot fork");
            exit(1);
        }
        if (pid == 0) {
            res = execve(argv[0], NULL, NULL);
            perror("Cannot exec!");
        }
        sleep(1);
        system("ps -fl");
        kill(pid, SIGKILL);
        return 0;
    }
    sleep(180);
    return 0;
}

Le processus ci-dessus va créer un fils qui va faire exec du même programme, mais sans recevoir aucun argument.

fa$ ./arturo
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 S francois 11473  2132  0  80   0 -   501 wait   16:39 pts/2    00:00:00 ./arturo
0 S francois 11474 11473  0  80   0 -   501 hrtime 16:39 pts/2    00:00:00 [arturo]

fa$

Donc, quand les arguments ne sont pas disponibles (dans la pile utilisateur) notre processus apparaît effectivement avec son nom "crocheté", mais n'est en aucun cas un zombie.

Note: Le programme ci-dessus ne fonctionne pas sur Solaris (sur la version de Solaris sur laquelle j'ai essayée) : l'appel exec renvoie EFAULT. Il faut absolument passer comme argv un tableau contenant un pointeur NULL. Ce n'est apparemment pas le cas pour le troisième paramètre de execenvp...
Comprend qui peut ou comprend qui veut  (B. Lapointe)

Le Jardinier sauvage

Puisque les arguments sont dans la pile utilisateur, que se passe-t-il si volontairement ou par mégarde, on vient piétiner ces plate-bandes?

Essayons!
fa$ cat arturo.c
 

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char** argv)
{
    int pid;
    int res;
    if (argc >= 1) {
        pid = fork();
        if (pid == -1) {
            perror("Cannot fork");
            exit(1);
        }
        if (pid == 0) {
            res = execve(argv[0], NULL, NULL);
            perror("Cannot exec!");
        }

        int lg = strlen(argv[0]) / 2;
        for(; lg >= 0; lg--) argv[0][lg] = '\0';
        argv[0] = NULL;

        sleep(1);
        system("ps -fl");
        kill(pid, SIGKILL);
        return 0;
    }
    sleep(180);
    return 0;
}

Dans notre programme précédent, dans le cas du père, on efface avec des caractères nuls, la moitié de la chaîne argv[0].  Et surcroît de sauvagerie, on met le pointeur argv[0] à NULL.

Action.

fa$ ./arturo
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 S francois 11485  2132  0  80   0 -   501 wait   16:56 pts/2    00:00:00      uro
0 S francois 11486 11485  0  80   0 -   501 hrtime 16:56 pts/2    00:00:00 [arturo]

fa$

Euh? Le père voit son nom de commande tronqué. C'est normal. on vient de labourer joyeusement la chaîne de caractères. Mais il semblerait que les caractères nuls insérés soient remplacés par des espaces.

En fait, cela semble dépendre des versions de la commande ps.
  • La version procps-ng version 3.3.3 avec laquelle j'ai effectuée ces essais montre le nom tronqué.
  • La version procps version 3.2.8 donne le résultat ci-dessous. Hop, invoquons Harry Houdini, un processus anonyme!
    • Bonsoir, je m'appelle "       " 
    • Bonsoir "    "
    • J'ai habitude de jardiner sauvagement les données dans ma pile.
fa$ ./arturo
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 S francois 17178  3131  0  80   0 -   459 wait   17:03 pts/0    00:00:00          <=== Là il n'y a rien!
0 S francois 17179 17178  0  80   0 -   459 hrtime 17:03 pts/0    00:00:00 [arturo]

fa$

Sur AIX le processus anonyme apparaît avec le nom passé en premier argument de exec.

Faut-il une conclusion?

To zombie or not to zombie, là n'est plus la question.
Pour se débarrasser des zombies, une requête sue le web vous donnera des tonnes de réponses. Des bonnes, des mauvaises, des étranges.  Soyez "écolos" faîtes le tri.

Générique

 Par ordre d'entrée en scène:
  • Robert Neville
  • Arturo Brachetti
  • Jean-Batpiste de La Quintinie
  • Harry Houdini