Wie kann man einen untergeordneten Prozess sterben lassen, nachdem der übergeordnete Prozess beendet wurde?
Lesezeit: 9 Minuten
Pawel Hajdan
Angenommen, ich habe einen Prozess, der genau einen untergeordneten Prozess hervorbringt. Wenn nun der übergeordnete Prozess aus irgendeinem Grund beendet wird (normal oder abnormal, durch kill, ^C, Assertion Failure oder irgendetwas anderes), möchte ich, dass der untergeordnete Prozess stirbt. Wie macht man das richtig?
Eine ähnliche Frage zu Stackoverflow:
(früher gefragt) Wie kann ich bewirken, dass ein untergeordneter Prozess beendet wird, wenn der Elternprozess beendet wird?
(später gefragt) Werden untergeordnete Prozesse, die mit fork() erstellt wurden, automatisch beendet, wenn der übergeordnete Prozess beendet wird?
Eine ähnliche Frage zu Stackoverflow for Windows:
Wie zerstöre ich automatisch untergeordnete Prozesse in Windows?
Untergeordneten Prozess beenden, wenn Elternprozess beendet wird
qrdl
Das Kind kann den Kernel bitten, zu liefern SIGHUP (oder ein anderes Signal), wenn ein Elternteil stirbt, indem die Option angegeben wird PR_SET_PDEATHSIG in prctl() Systemaufruf wie folgt:
prctl(PR_SET_PDEATHSIG, SIGHUP);
Sehen man 2 prctl für Details.
Bearbeiten: Dies ist nur Linux
Dies ist eine schlechte Lösung, da der Elternteil möglicherweise bereits gestorben ist. Rennbedingung. Richtige Lösung: stackoverflow.com/a/17589555/412080
– Maxim Egorushkin
22. Dezember 2015 um 17:49 Uhr
Eine Antwort als schlecht zu bezeichnen, ist nicht sehr nett – auch wenn sie keine Race-Condition anspricht. Siehe meine Antwort zur Verwendung prctl() in einer Race-Condition-freien Weise. Übrigens ist die von Maxim verlinkte Antwort falsch.
– maxschlepzig
29. April 2016 um 18:36 Uhr
Das ist einfach eine falsche Antwort. Es sendet das Signal an den untergeordneten Prozess zu dem Zeitpunkt, an dem der Thread, der Fork aufgerufen hat, stirbt, nicht wenn der übergeordnete Prozess stirbt.
– Lothar
10. Dezember 2016 um 1:01 Uhr
@Lothar Es wäre schön, eine Art Beweis zu sehen. man prctl sagt: Setze das Todessignal des übergeordneten Prozesses des aufrufenden Prozesses auf arg2 (entweder ein Signalwert im Bereich 1..maxsig oder 0 zum Löschen). Dies ist das Signal, das der aufrufende Prozess erhält, wenn sein Elternprozess stirbt. Dieser Wert wird für das Kind eines fork(2) und (seit Linux 2.4.36 / 2.6.23) gelöscht, wenn eine set-user-ID- oder set-group-ID-Binärdatei ausgeführt wird.
– qrdl
10. Dezember 2016 um 19:46 Uhr
@maxschlepzig Danke für den neuen Link. Der vorherige Link scheint ungültig zu sein. Übrigens gibt es nach Jahren immer noch keine API zum Einstellen von Optionen auf der übergeordneten Seite. Was für eine Schande.
– Rox
27. April 2017 um 3:47 Uhr
Ich versuche, das gleiche Problem zu lösen, und da mein Programm unter OS X laufen muss, hat die reine Linux-Lösung für mich nicht funktioniert.
Ich bin zu dem gleichen Schluss gekommen wie die anderen Leute auf dieser Seite – es gibt keine POSIX-kompatible Möglichkeit, ein Kind zu benachrichtigen, wenn ein Elternteil stirbt. Also habe ich mir das Nächstbeste ausgedacht – die Kinderumfrage.
Wenn ein Elternprozess stirbt (aus irgendeinem Grund), wird der Elternprozess des Kindes zu Prozess 1. Wenn das Kind einfach periodisch abfragt, kann es überprüfen, ob sein Elternprozess 1 ist. Wenn dies der Fall ist, sollte das Kind beendet werden.
Das ist nicht großartig, aber es funktioniert, und es ist einfacher als die TCP-Socket/Lockfile-Polling-Lösungen, die an anderer Stelle auf dieser Seite vorgeschlagen werden.
Hervorragende Lösung. Fortlaufendes Aufrufen von getppid(), bis es 1 zurückgibt und dann beendet wird. Das ist gut und ich benutze es jetzt auch. Eine Non-Pollig-Lösung wäre aber schön. Danke Schof.
– Neonauge
11. April 2010 um 11:58 Uhr
Nur zur Info, auf Solaris, wenn du in einer Zone bist, die gettpid() wird nicht 1, sondern bekommt die pid des Zonenplaners (process zsched).
– Patrick Schlüter
14. Oktober 2010 um 13:32 Uhr
Wenn sich jemand wundert, scheint die PID in Android-Systemen 0 (Prozess-System-PID) anstelle von 1 zu sein, wenn der Elternteil stirbt.
– Rui Marken
2. Oktober 2012 um 17:38 Uhr
Um es robuster und plattformunabhängiger zu machen, vor dem Fork()-ing einfach getpid() und wenn getppid() von child anders ist, beenden.
– Sebastian
10. August 2017 um 13:42 Uhr
Dies funktioniert nicht, wenn Sie den untergeordneten Prozess nicht steuern. Zum Beispiel arbeite ich an einem Befehl, der find(1) umschließt, und ich möchte sicherstellen, dass der find beendet wird, wenn der Wrapper aus irgendeinem Grund stirbt.
– Lucretiel
19. Juni 2018 um 2:03 Uhr
Ich habe dies in der Vergangenheit erreicht, indem ich den “Original”-Code im “Kind” und den “erzeugten” Code im “Eltern” ausgeführt habe (das heißt: Sie kehren den üblichen Sinn des Tests danach um fork()). Fangen Sie dann SIGCHLD im “erzeugten” Code ein …
In Ihrem Fall möglicherweise nicht möglich, aber nett, wenn es funktioniert.
Das große Problem bei der Arbeit im Elternprozess besteht darin, dass Sie den Elternprozess ändern. Bei einem Server, der „ewig“ laufen muss, ist das keine Option.
– Alexis Wilke
24. März 2015 um 4:34 Uhr
maxschlepzig
Unter Linux können Sie ein Eltern-Todessignal im Kind installieren, z. B.:
#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h> // perror()
// ...
pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
; // continue parent execution
} else {
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1) { perror(0); exit(1); }
// test in case the original parent exited just
// before the prctl() call
if (getppid() != ppid_before_fork)
exit(1);
// continue child execution ...
Beachten Sie, dass die übergeordnete Prozess-ID vor dem Fork gespeichert und danach im untergeordneten Prozess getestet wird prctl() eliminiert eine Racebedingung zwischen prctl() und der Ausgang des Prozesses, der das Kind aufgerufen hat.
Beachten Sie auch, dass das Eltern-Todessignal des Kindes in neu erstellten eigenen Kindern gelöscht wird. Es wird nicht von einem beeinflusst execve().
Dieser Test kann vereinfacht werden, wenn wir sicher sind, dass der Systemprozess, der für die Annahme aller verantwortlich ist, verantwortlich ist Waisen hat PID 1:
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
; // continue parent execution
} else {
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1) { perror(0); exit(1); }
// test in case the original parent exited just
// before the prctl() call
if (getppid() == 1)
exit(1);
// continue child execution ...
Sich auf dieses Systemprozesswesen verlassen init und PID 1 zu haben, ist jedoch nicht portabel. POSIX.1-2008 spezifiziert:
Die Elternprozess-ID aller existierenden Kindprozesse und Zombie-Prozesse des aufrufenden Prozesses soll auf die Prozess-ID eines implementierungsdefinierten Systemprozesses gesetzt werden. Das heißt, diese Prozesse sollen von einem speziellen Systemprozess geerbt werden.
Traditionell ist der Systemprozess, der alle Waisen übernimmt, PID 1, dh init – der Vorfahre aller Prozesse.
Auf modernen Systemen wie Linux oder FreeBSD ein anderer Prozess könnte diese Rolle übernehmen. Unter Linux kann beispielsweise ein Prozess aufrufen prctl(PR_SET_CHILD_SUBREAPER, 1) sich selbst als Systemprozess zu etablieren, der alle Waisen seiner Nachkommen erbt (vgl. an Beispiel auf Fedora 25).
Phil Rutschmann
Wenn Sie den untergeordneten Prozess nicht ändern können, können Sie Folgendes versuchen:
int pipes[2];
pipe(pipes)
if (fork() == 0) {
close(pipes[1]); /* Close the writer end in the child*/
dup2(pipes[0], STDIN_FILENO); /* Use reader end as stdin (fixed per maxschlepzig */
exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}
close(pipes[0]); /* Close the reader end in the parent */
Dadurch wird das untergeordnete Element innerhalb eines Shell-Prozesses mit aktivierter Jobsteuerung ausgeführt. Der untergeordnete Prozess wird im Hintergrund gestartet. Die Shell wartet auf einen Zeilenumbruch (oder ein EOF) und beendet dann das Kind.
Wenn der Elternteil stirbt – egal aus welchem Grund – wird er sein Ende der Pfeife schließen. Die Kind-Shell erhält ein EOF von dem Lesevorgang und fährt fort, den im Hintergrund befindlichen Kindprozess zu beenden.
Schön, aber fünf Systemaufrufe und ein sh in zehn Codezeilen lassen mich etwas skeptisch gegenüber der Leistung dieses Stücks Code werden.
– Oleiade
17. Januar 2014 um 9:53 Uhr
+1. Sie können die vermeiden dup2 und die Übernahme von stdin mit der read -u Flag zum Lesen aus einem bestimmten Dateideskriptor. Ich habe auch eine hinzugefügt setpgid(0, 0) im Kind, um zu verhindern, dass es beendet wird, wenn ^C im Terminal gedrückt wird.
– Greg Hewgill
1. Mai 2014 um 0:09 Uhr
Die Argumentreihenfolge der dup2() Anruf ist falsch. Wenn Sie verwenden möchten pipes[0] als stdin muss man schreiben dup2(pipes[0], 0) Anstatt von dup2(0, pipes[0]). es istdup2(oldfd, newfd) wobei der Aufruf ein zuvor geöffnetes newfd schließt.
– maxschlepzig
30. April 2016 um 8:23 Uhr
@Oleiade, ich stimme zu, zumal das gespawnte sh nur eine weitere Abzweigung macht, um den echten untergeordneten Prozess auszuführen …
– maxschlepzig
30. April 2016 um 8:26 Uhr
Nach dem Anruf bei dup2()sollten Sie schließen pipes[0] zu.
– Jonathan Leffler
28. September um 16:34 Uhr
Der Vollständigkeit halber. Unter macOS können Sie kqueue verwenden:
void noteProcDeath(
CFFileDescriptorRef fdref,
CFOptionFlags callBackTypes,
void* info)
{
// LOG_DEBUG(@"noteProcDeath... ");
struct kevent kev;
int fd = CFFileDescriptorGetNativeDescriptor(fdref);
kevent(fd, NULL, 0, &kev, 1, NULL);
// take action on death of process here
unsigned int dead_pid = (unsigned int)kev.ident;
CFFileDescriptorInvalidate(fdref);
CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example
int our_pid = getpid();
// when our parent dies we die as well..
LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
exit(EXIT_SUCCESS);
}
void suicide_if_we_become_a_zombie(int parent_pid) {
// int parent_pid = getppid();
// int our_pid = getpid();
// LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);
int fd = kqueue();
struct kevent kev;
EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
kevent(fd, &kev, 1, NULL, 0, NULL);
CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
Schön, aber fünf Systemaufrufe und ein sh in zehn Codezeilen lassen mich etwas skeptisch gegenüber der Leistung dieses Stücks Code werden.
– Oleiade
17. Januar 2014 um 9:53 Uhr
+1. Sie können die vermeiden dup2 und die Übernahme von stdin mit der read -u Flag zum Lesen aus einem bestimmten Dateideskriptor. Ich habe auch eine hinzugefügt setpgid(0, 0) im Kind, um zu verhindern, dass es beendet wird, wenn ^C im Terminal gedrückt wird.
– Greg Hewgill
1. Mai 2014 um 0:09 Uhr
Die Argumentreihenfolge der dup2() Anruf ist falsch. Wenn Sie verwenden möchten pipes[0] als stdin muss man schreiben dup2(pipes[0], 0) Anstatt von dup2(0, pipes[0]). es istdup2(oldfd, newfd) wobei der Aufruf ein zuvor geöffnetes newfd schließt.
– maxschlepzig
30. April 2016 um 8:23 Uhr
@Oleiade, ich stimme zu, zumal das gespawnte sh nur eine weitere Abzweigung macht, um den echten untergeordneten Prozess auszuführen …
– maxschlepzig
30. April 2016 um 8:26 Uhr
Nach dem Anruf bei dup2()sollten Sie schließen pipes[0] zu.
– Jonathan Leffler
28. September um 16:34 Uhr
MarkR
Hat der untergeordnete Prozess eine Pipe zum/vom übergeordneten Prozess? Wenn dies der Fall ist, erhalten Sie beim Schreiben ein SIGPIPE oder beim Lesen EOF – diese Bedingungen könnten erkannt werden.
Ich fand, dass dies zumindest unter OS X nicht zuverlässig geschah.
– Schof
10. Januar 2010 um 1:31 Uhr
Vorsichtshinweis: systemd deaktiviert standardmäßig SIGPIPEs in Diensten, die es verwaltet, aber Sie können immer noch nach dem Schließen der Pipe suchen. Sehen freedesktop.org/software/systemd/man/systemd.exec.html unter SIGPIPE ignorieren
– jdizzle
2. Juni 2020 um 13:44 Uhr
14261300cookie-checkWie kann man einen untergeordneten Prozess sterben lassen, nachdem der übergeordnete Prozess beendet wurde?yes