Wie kann man einen untergeordneten Prozess sterben lassen, nachdem der übergeordnete Prozess beendet wurde?

Lesezeit: 9 Minuten

Benutzeravatar von Paweł Hajdan
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

Benutzeravatar von qrdl
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

Benutzeravatar von maxschlepzig
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).

Benutzeravatar von Phil Rutschman
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

Benutzeravatar von MarkR
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


1426130cookie-checkWie kann man einen untergeordneten Prozess sterben lassen, nachdem der übergeordnete Prozess beendet wurde?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy