Das Symptom
In ps oder top steht ein Prozess, der sich nicht verabschieden will. Die Zustandsspalte
zeigt ein Z, hinter dem Namen steht <defunct>:
PID PPID STAT COMMAND
4712 4711 Z python3
Der Reflex ist bekannt: kill -9 auf die PID. Dann noch einmal. Dann mit sudo. kill beschwert sich nicht einmal, aber der Eintrag bleibt stehen, als wäre nichts gewesen. Spätestens jetzt wächst der Verdacht, der Prozess hänge irgendwo im Kernel fest, fresse heimlich Speicher oder das System sei grundsätzlich kaputt, und der Neustart des ganzen Servers rückt näher.
Die „Layer 8“-Wahrheit
Das Missverständnis steckt im Wort. Ein Zombie ist kein Untoter, der sich gegen Signale wehrt, sondern ein Toter, der noch nicht abgeholt wurde. wait(2) beschreibt den Zustand nüchtern: Ein Kindprozess, der sich beendet hat, auf den aber noch kein wait ausgeführt wurde, wird zum Zombie. „The kernel maintains a minimal set of information about the zombie process (PID, termination status, resource usage information) in order to allow the parent to later perform a wait to obtain information about the child.“ ps(1) sagt es in einer Zeile: „terminated but not reaped by its parent“.
Damit ist auch klar, warum kill nichts ausrichtet. SIGKILL beendet einen laufenden Prozess; dieser hier läuft nicht mehr. kill(2) behandelt ihn trotzdem als existierend und hält bei ESRCH ausdrücklich fest, dass ein existierender Prozess „might be a zombie, a process that has terminated execution“. Deshalb kommt nicht einmal eine Fehlermeldung. Das Signal wird angenommen, und es passiert genau nichts, weil niemand mehr da ist, der es ausführen könnte.
Der Schlüssel liegt eine Ebene höher, beim Elternprozess. Holt er den Status mit wait ab, verschwindet der Zombie. Beendet er sich, übernimmt init die Waisen: „init(1) automatically performs a wait to remove the zombies.“ Wer also aufräumen will, schaut auf die PPID statt auf die PID und kümmert sich um das Programm, das seine Kinder liegen lässt. Auch ein Neustart des ganzen Servers beseitigt die Zombies nur deshalb, weil er nebenbei ihren Elternprozess beendet, und alles andere gleich mit. Denselben Effekt hat ein gezielter Neustart des einen Programms.
Harmlos im Sinne von egal sind Zombies trotzdem nicht. Solange kein wait erfolgt, belegt jeder einen Platz in der Prozesstabelle, und wait(2) warnt, dass sich bei voller Tabelle keine neuen Prozesse mehr starten lassen. Ein einzelner Zombie ist eine Kuriosität. Eine wachsende Zahl unter demselben Elternprozess ist ein Programmierfehler, und der wird weder durch kill -9 noch durch mehr Geduld behoben, sondern im Code des Elternprogramms.