Per rispondere a questa domanda, devi capire come i segnali vengono inviati a un processo e come esiste un processo nel kernel.
Ogni processo è rappresentato come task_struct
all'interno del kernel (la definizione è nel sched.h
file di intestazione e inizia qui). Quella struttura contiene informazioni sul processo; per esempio il pid. L'informazione importante si trova nella riga 1566 dove è memorizzato il segnale associato. Questo è impostato solo se viene inviato un segnale al processo.
Un processo morto o un processo zombie ha ancora un task_struct
. La struttura rimane, finché il processo genitore (naturale o per adozione) non ha chiamato wait()
dopo aver ricevuto SIGCHLD
raccogliere il suo processo figlio. Quando viene inviato un segnale, il signal_struct
è impostato. Non importa se il segnale è intercettabile o meno, in questo caso.
I segnali vengono valutati ogni volta che il processo viene eseguito. O per essere esatti, prima il processo lo farebbe correre. Il processo è quindi nel TASK_RUNNING
stato. Il kernel esegue il schedule()
routine che determina il prossimo processo in esecuzione in base al suo algoritmo di pianificazione. Supponendo che questo processo sia il prossimo processo in esecuzione, il valore di signal_struct
viene valutato, se c'è un segnale in attesa da gestire o meno. Se un gestore di segnale viene definito manualmente (tramite signal()
o sigaction()
), viene eseguita la funzione registrata, se non l'azione predefinita del segnale viene eseguito. L'azione predefinita dipende dal segnale inviato.
Ad esempio, il SIGSTOP
il gestore predefinito di signal cambierà lo stato del processo corrente in TASK_STOPPED
e poi esegui schedule()
per selezionare un nuovo processo da eseguire. Avviso, SIGSTOP
non è catturabile (come SIGKILL
), pertanto non è possibile registrare un gestore di segnale manuale. In caso di segnale non rilevabile, verrà sempre eseguita l'azione predefinita.
Alla tua domanda:
Un processo defunto o morto non verrà mai determinato dallo scheduler come nel TASK_RUNNING
stato di nuovo. Pertanto il kernel non eseguirà mai il gestore di segnale (predefinito o definito) per il segnale corrispondente, qualunque sia il segnale. Pertanto il exit_signal
non sarà mai più fissato. Il segnale viene "consegnato" al processo impostando il signal_struct
in task_struct
del processo, ma non accadrà nient'altro, perché il processo non verrà mai più eseguito. Non c'è codice da eseguire, tutto ciò che rimane del processo è quella struttura del processo.
Tuttavia, se il processo genitore miete i suoi figli entro wait()
, il codice di uscita che riceve è quello quando il processo "inizialmente" è morto. Non importa se c'è un segnale in attesa di essere gestito.
Un processo di zombi è praticamente già morto. L'unica cosa è che nessuno ha ancora riconosciuto la sua morte, quindi continua a occupare una voce nella tabella dei processi e un blocco di controllo (la struttura che il kernel di Linux mantiene per ogni thread in attività). Altre risorse come blocchi obbligatori sui file, segmenti di memoria condivisa, semafori, ecc. vengono recuperate.
Non puoi segnalarli perché nessuno può agire su questo segnale. Anche i segnali fatali come KILL sono inutili poiché il processo ha già terminato la sua esecuzione. Puoi provare tu stesso:
#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;
}
Qui inizio un processo che si biforca e dorme prima di aspettare il suo bambino. Il bambino non fa altro che dormire un po'. Puoi uccidere il bambino mentre dorme o subito dopo che è uscito per vedere la differenza:
$ 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