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é par un task_struct
à l'intérieur du noyau (la définition est dans le sched.h
fichier d'en-tête et commence ici). Cette structure contient des informations sur le processus; par exemple le pid. Les informations importantes se trouvent à la ligne 1566 où le signal associé est stocké. Ceci 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 avoir reçu SIGCHLD
pour récolter son processus enfant. Lorsqu'un signal est envoyé, le signal_struct
est défini. Peu importe si le signal est captable ou non, dans ce cas.
Les signaux sont évalués à chaque fois que le processus s'exécute. Ou pour être exact, avant le processus serait Cours. Le processus est alors dans le TASK_RUNNING
Etat. Le noyau exécute le schedule()
routine qui détermine le prochain processus en cours d'exécution en fonction de son algorithme d'ordonnancement. En supposant que ce processus est le prochain processus en cours d'exécution, la valeur de signal_struct
est évalué, 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é. L'action par défaut dépend du signal envoyé.
Par exemple, le SIGSTOP
le gestionnaire par défaut du signal changera l'état du processus actuel en TASK_STOPPED
puis exécutez schedule()
pour sélectionner un nouveau processus à exécuter. Remarquez, SIGSTOP
n'est pas attrapable (comme SIGKILL
), il n'est donc pas possible d'enregistrer un gestionnaire de signal manuel. En cas de signal non attrapable, l'action par défaut sera toujours exécutée.
À votre question :
Un processus défunt ou mort ne sera jamais déterminé par le planificateur comme étant dans le TASK_RUNNING
déclarer à nouveau. 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. Donc le exit_signal
ne sera plus jamais fixé. Le signal est "livré" au processus en définissant le signal_struct
en task_struct
du processus, mais rien d'autre ne se produira, car le processus ne s'exécutera plus jamais. 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 récupère ses enfants par 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.
Un processus zombie est fondamentalement déjà mort. La seule chose est que personne n'a encore reconnu sa mort, donc il continue d'occuper une entrée dans la table des processus ainsi qu'un bloc de contrôle (la structure que le noyau Linux maintient pour chaque thread en activité). D'autres ressources telles que les verrous obligatoires sur les fichiers, les segments de mémoire partagée, les sémaphores, etc. sont récupérées.
Vous ne pouvez pas les signaler car personne ne peut agir sur ce signal. Même les signaux fatals comme KILL sont inutiles puisque le processus a déjà terminé son exécution. Vous pouvez essayer vous-même :
#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 démarre un processus qui bifurque et dort avant d'attendre son enfant. L'enfant ne fait que dormir un peu. Vous pouvez tuer l'enfant pendant qu'il dort ou juste après qu'il soit sorti 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