Um diese Frage zu beantworten, müssen Sie verstehen, wie Signale an einen Prozess gesendet werden und wie ein Prozess im Kernel existiert.
Jeder Prozess wird als task_struct
dargestellt innerhalb des Kernels (die Definition befindet sich in sched.h
Header-Datei und beginnt hier). Diese Struktur enthält Informationen über den Prozess; zum Beispiel die pid. Die wichtigen Informationen befinden sich in Zeile 1566, wo das zugehörige Signal gespeichert ist. Dies wird nur gesetzt, wenn ein Signal an den Prozess gesendet wird.
Ein toter Prozess oder ein Zombie-Prozess hat immer noch einen task_struct
. Die Struktur bleibt bestehen, bis der übergeordnete Prozess (natürlich oder durch Adoption) wait()
aufgerufen hat nach Erhalt von SIGCHLD
um seinen untergeordneten Prozess zu ernten. Wenn ein Signal gesendet wird, wird der signal_struct
eingestellt ist. Dabei spielt es keine Rolle, ob das Signal fangbar ist oder nicht.
Signale werden jedes Mal ausgewertet, wenn der Prozess läuft. Genauer gesagt vorher der Prozess würde Lauf. Der Vorgang befindet sich dann im TASK_RUNNING
Zustand. Der Kernel führt den schedule()
aus Routine, die gemäß ihrem Scheduling-Algorithmus den nächsten laufenden Prozess ermittelt. Angenommen, dieser Prozess ist der nächste laufende Prozess, der Wert von signal_struct
ausgewertet, ob ein zu behandelndes Wartesignal vorliegt oder nicht. Wenn ein Signalhandler manuell definiert wird (über signal()
oder sigaction()
), wird die registrierte Funktion ausgeführt, wenn nicht die Standardaktion des Signals wird ausgeführt. Die Standardaktion hängt vom gesendeten Signal ab.
Zum Beispiel die SIGSTOP
Der Standard-Handler von Signal ändert den Status des aktuellen Prozesses auf TASK_STOPPED
und führen Sie dann schedule()
aus um einen neuen auszuführenden Prozess auszuwählen. Achtung, SIGSTOP
ist nicht abfangbar (wie SIGKILL
), daher gibt es keine Möglichkeit, einen manuellen Signalhandler zu registrieren. Im Falle eines unfangbaren Signals wird immer die Standardaktion ausgeführt.
Zu Ihrer Frage:
Ein nicht mehr funktionierender oder toter Prozess wird vom Scheduler niemals als in TASK_RUNNING
bestimmt Zustand wieder. Daher wird der Kernel niemals den Signal-Handler (Standard oder definiert) für das entsprechende Signal ausführen, welches Signal auch immer es war. Daher die exit_signal
wird nie wieder eingestellt. Durch Setzen des signal_struct
wird das Signal an den Prozess "übergeben". in task_struct
des Prozesses, aber es passiert nichts weiter, da der Prozess nie wieder ausgeführt wird. Es muss kein Code ausgeführt werden, alles, was vom Prozess übrig bleibt, ist diese Prozessstruktur.
Wenn jedoch der Elternprozess seine Kinder bis wait()
erntet , der Exit-Code, den es erhält, ist derjenige, als der Prozess "ursprünglich" gestorben ist. Es spielt keine Rolle, ob ein Signal darauf wartet, bearbeitet zu werden.
Ein Zombie-Prozess ist im Grunde schon tot. Die einzige Sache ist, dass noch niemand seinen Tod bestätigt hat, also belegt er weiterhin einen Eintrag in der Prozesstabelle sowie einen Kontrollblock (die Struktur, die der Linux-Kernel für jeden aktiven Thread beibehält). Andere Ressourcen wie obligatorische Sperren für Dateien, Shared-Memory-Segmente, Semaphore usw. werden zurückgefordert.
Sie können ihnen kein Signal geben, weil niemand auf dieses Signal reagieren kann. Selbst fatale Signale wie KILL sind nutzlos, da der Prozess seine Ausführung bereits beendet hat. Sie können es selbst versuchen:
#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;
}
Hier starte ich einen Prozess, der sich verzweigt und schläft, bevor er auf sein Kind wartet. Das Kind tut nichts, außer ein wenig zu schlafen. Sie können das Kind töten, wenn es schläft oder kurz nachdem es den Raum verlassen hat, um den Unterschied zu sehen:
$ 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