Que se passe-t-il lors de l'envoi de SIGKILL vers un processus Zombie sous Linux?

10

Sous Linux, lorsqu'un processus enfant se termine et que son parent n'a pas encore attendu, il devient un processus zombie. Le code de sortie de l'enfant est stocké dans le descripteur pid.

Si un SIGKILLmessage est envoyé à l'enfant, il n'y a aucun effet supposé.

Est-ce à dire que le code de sortie ne sera pas modifié par le SIGKILLou le code de sortie sera-t-il modifié pour indiquer que l'enfant est sorti parce qu'il a reçu un SIGKILL?

user137481
la source

Réponses:

14

Pour répondre à cette question, vous devez comprendre comment les signaux sont envoyés à un processus et comment un processus existe dans le noyau.

Chaque processus est représenté comme un task_structà l'intérieur du noyau (la définition est dans le sched.hfichier d' en- tête et commence ici ). Cette structure contient des informations sur le processus; par exemple le pid. Les informations importantes se trouvent sur la ligne 1566 où le signal associé est stocké. Ce paramètre n'est défini que si un signal est envoyé au processus.

Un processus mort ou un processus zombie a toujours un task_struct. La structure reste, jusqu'à ce que le processus parent (naturel ou par adoption) ait appelé wait()après la réception SIGCHLDpour récolter son processus enfant. Lorsqu'un signal est envoyé, le signal_structest réglé. Peu importe que le signal soit capturable ou non, dans ce cas.

Les signaux sont évalués à chaque fois que le processus s'exécute. Ou pour être exact, avant que le processus ne s'exécute. Le processus est alors en l' TASK_RUNNINGétat. Le noyau exécute la schedule()routine qui détermine le processus suivant en fonction de son algorithme de planification. En supposant que ce processus est le prochain processus en cours, la valeur de signal_structest évaluée, qu'il y ait ou non un signal en attente à traiter. Si un gestionnaire de signal est défini manuellement (via signal()ou sigaction()), la fonction enregistrée est exécutée, sinon l' action par défaut du signal est exécutée. L'action par défaut dépend du signal envoyé.

Par exemple, le SIGSTOPgestionnaire par défaut du signal changera l'état du processus actuel en TASK_STOPPEDpuis s'exécutera schedule()pour sélectionner un nouveau processus à exécuter. Remarquez, SIGSTOPn'est pas capturable (comme SIGKILL), donc il n'y a aucune possibilité d'enregistrer un gestionnaire de signal manuel. En cas de signal inaccessible, l'action par défaut sera toujours exécutée.


À votre question:

Un processus défunt ou mort ne sera jamais déterminé par l'ordonnanceur comme étant à TASK_RUNNINGnouveau dans l' état. Ainsi, le noyau n'exécutera jamais le gestionnaire de signal (par défaut ou défini) pour le signal correspondant, quel que soit le signal. Par conséquent, le exit_signalne sera plus jamais réglé. Le signal est « livré » au processus en définissant le signal_structdans task_structdu processus, mais rien d' autre qui va se passer, parce que le processus ne fonctionnera jamais à nouveau. Il n'y a pas de code à exécuter, tout ce qui reste du processus est cette structure de processus.

Cependant, si le processus parent recueille ses enfants wait(), le code de sortie qu'il reçoit est celui lorsque le processus est "initialement" mort. Peu importe s'il y a un signal en attente de traitement.

le chaos
la source
Oui, mais la commande killelle-même renvoie-t-elle 0 ou 1?
Anthony Rutledge
9

Un processus zombie est fondamentalement déjà mort. La seule chose est que personne n'a encore reconnu sa mort, alors il continue d'occuper une entrée dans la table de processus ainsi qu'un bloc de contrôle (la structure que le noyau Linux maintient pour chaque thread en activité). D'autres ressources comme les verrous obligatoires sur les fichiers, les segments de mémoire partagée, les sémaphores, etc. sont récupérés.

Vous ne pouvez pas les signaler car personne ne peut agir sur ce signal. Même des signaux fatals comme KILL sont inutiles car le processus a déjà terminé son exécution. Vous pouvez vous essayer:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
    pid_t pid = fork();

    if (pid == -1)
        exit(-1);

    if (pid > 0) {
        //parent
        printf("[parent]: I'm the parent, the pid of my child is %i\n"
            "I'll start waiting for it in 10 seconds.\n", pid);
        sleep(10);
        int status;
        wait(&status);

        if (WIFSIGNALED(status)) {
            printf("[parent]: My child has died from a signal: %i\n", WTERMSIG(status));
        } else if (WIFEXITED(status)) {
            printf("[parent]: My child has died from natural death\n");
        } else {
            printf("[parent]: I don't know what happened to my child\n");
        }
    } else {
        //child
        printf("[child]: I'm dying soon, try to kill me.\n");
        sleep(5);
        printf("[child]: Dying now!\n");
    }

    return 0;
}

Ici, je lance un processus qui bifurque et dort avant d'attendre son enfant. L'enfant ne fait que dormir un peu. Vous pouvez tuer l'enfant quand il dort ou juste après sa sortie pour voir la différence:

$ make zombie 
cc     zombie.c   -o zombie

$ ./zombie    
[parent]: I'm the parent, the pid of my child is 16693
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
# Here, I did "kill -15 16693" in another console
[parent]: My child has died from a signal: 15

$ ./zombie
[parent]: I'm the parent, the pid of my child is 16717
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
[child]: Dying now!
# Here, I did "kill -15 16717" in another console
[parent]: My child has died from natural death
lgeorget
la source
Je voudrais trouver le passage pertinent dans le code source du noyau pour vous mais j'ai du mal à le trouver ...
lgeorget
@Igeorget Merci mais ça va, je n'ai pas besoin de voir le code du noyau.
user137481
Programme Ruby simple qui accélère un processus, et l'enfant quitte immédiatement ruby -e "loop while fork { exit! }"... imgur.com/SoRXErm
S.Goswami