Was passiert, wenn SIGKILL an einen Zombie-Prozess unter Linux gesendet wird?

10

Wenn unter Linux ein untergeordneter Prozess beendet wird und sein übergeordneter Prozess noch nicht darauf gewartet hat, wird er zu einem Zombie-Prozess. Der Exit-Code des Kindes wird im PID-Deskriptor gespeichert.

Wenn a SIGKILLan das Kind gesendet wird, sollte dies keine Auswirkung haben.

Bedeutet dies, dass der Exit-Code nicht durch den geändert wird, SIGKILLoder wird der Exit-Code geändert, um anzuzeigen, dass das Kind beendet wurde, weil es einen erhalten hat SIGKILL?

user137481
quelle

Antworten:

14

Um diese Frage zu beantworten, müssen Sie verstehen, wie Signale an einen Prozess gesendet werden und wie ein Prozess im Kernel vorhanden ist.

Jeder Prozess wird als task_structinnerhalb des Kernels dargestellt (die Definition befindet sich in der sched.hHeader-Datei und beginnt hier ). Diese Struktur enthält Informationen über den Prozess. zum Beispiel die pid. Die wichtige Information befindet sich in Zeile 1566, in der das zugehörige Signal gespeichert ist. Dies wird nur eingestellt, wenn ein Signal an den Prozess gesendet wird.

Ein toter Prozess oder ein Zombie-Prozess hat noch eine task_struct. Die Struktur bleibt bestehen, bis der übergeordnete Prozess (natürlich oder durch Adoption) wait()nach dem Empfang aufgerufen hat SIGCHLD, seinen untergeordneten Prozess zu ernten. Wenn ein Signal gesendet wird, signal_structwird das gesetzt. In diesem Fall spielt es keine Rolle, ob das Signal abfangbar ist oder nicht.

Signale werden jedes Mal ausgewertet, wenn der Prozess ausgeführt wird. Oder um genau zu sein, bevor der Prozess würde laufen. Der Prozess ist dann im TASK_RUNNINGZustand. Der Kernel führt die schedule()Routine aus, die den nächsten laufenden Prozess gemäß seinem Planungsalgorithmus bestimmt. Angenommen, dieser Prozess ist der nächste laufende Prozess, wird der Wert von signal_structausgewertet, unabhängig davon, ob ein Wartesignal verarbeitet werden muss oder nicht. Wenn ein Signalhandler manuell definiert wird (über signal()oder sigaction()), wird die registrierte Funktion ausgeführt, wenn nicht die Standardaktion des Signals ausgeführt wird. Die Standardaktion hängt vom gesendeten Signal ab.

Beispielsweise SIGSTOPändert der Standardhandler des Signals den Status des aktuellen Prozesses in TASK_STOPPEDund wird dann ausgeführt schedule(), um einen neuen auszuführenden Prozess auszuwählen. Hinweis, SIGSTOPist nicht fangbar (wie SIGKILL), daher gibt es keine Möglichkeit, einen manuellen Signalhandler zu registrieren. Im Falle eines nicht abfangbaren Signals wird die Standardaktion immer ausgeführt.


Zu Ihrer Frage:

Ein nicht mehr funktionierender oder toter Prozess wird vom Scheduler nie wieder als in diesem TASK_RUNNINGZustand befindlich eingestuft. Daher wird der Kernel niemals den Signalhandler (Standard oder definiert) für das entsprechende Signal ausführen, je nachdem, welches Signal es war. Daher exit_signalwird das nie wieder eingestellt. Das Signal wird durch Einstellen des signal_structEingangs task_structdes Prozesses an den Prozess "geliefert" , aber es passiert nichts anderes, 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 der übergeordnete Prozess jedoch seine untergeordneten Prozesse erntet wait(), ist der Exit-Code, den er erhält, derjenige, als der Prozess "anfänglich" gestorben ist. Es spielt keine Rolle, ob ein Signal darauf wartet, verarbeitet zu werden.

Chaos
quelle
Ja, aber gibt der Befehl killselbst 0 oder 1 zurück?
Anthony Rutledge
9

Ein Zombie-Prozess ist im Grunde schon tot. Das einzige ist, dass noch niemand seinen Tod bestätigt hat, sodass er weiterhin einen Eintrag in der Prozesstabelle sowie einen Steuerblock belegt (die Struktur, die der Linux-Kernel für jeden Thread in der Aktivität beibehält). Andere Ressourcen wie obligatorische Sperren für Dateien, gemeinsam genutzte Speichersegmente, Semaphoren usw. werden zurückgefordert.

Sie können sie nicht signalisieren, da niemand auf dieses Signal reagieren kann. Selbst schwerwiegende 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 gabelt und schläft, bevor ich auf sein Kind warte. Das Kind schläft nur ein wenig. Sie können das Kind töten, wenn es schläft oder kurz nach dem Verlassen, 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
lgeorget
quelle
Ich würde gerne die relevante Passage im Kernel-Quellcode für Sie finden, aber ich habe Mühe, sie zu finden ...
lgeorget
@ Igeorget Danke, aber das ist in Ordnung, ich muss den Kernel-Code nicht sehen.
user137481
Einfaches Ruby-Programm, das einen Prozess auslöst und das Kind sofort beendet ruby -e "loop while fork { exit! }"... imgur.com/SoRXErm
S.Goswami