Wie kann man den Kinderprozess nach dem Verlassen des Elternteils sterben lassen?

209

Angenommen, ich habe einen Prozess, der genau einen untergeordneten Prozess erzeugt. Wenn nun der übergeordnete Prozess aus irgendeinem Grund beendet wird (normalerweise oder abnormal, durch Töten, ^ C, Fehler behaupten oder irgendetwas anderes), möchte ich, dass der untergeordnete Prozess stirbt. Wie mache ich das richtig?


Einige ähnliche Fragen zum Stackoverflow:


Einige ähnliche Fragen zum Stackoverflow für Windows :

Paweł Hajdan
quelle

Antworten:

189

Das Kind kann den Kernel bitten, zu liefern SIGHUP(oder ein anderes Signal), wenn das Elternteil stirbt, indem es die Option PR_SET_PDEATHSIGin prctl()syscall wie folgt angibt :

prctl(PR_SET_PDEATHSIG, SIGHUP);

Siehe man 2 prctlfür Details.

Bearbeiten: Dies ist nur Linux

qrdl
quelle
5
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
16
Eine schlechte Antwort zu nennen ist nicht sehr nett - auch wenn es nicht um eine Rennbedingung geht. Siehe meine Antwort zur rennbedingten Verwendung prctl(). Übrigens ist die von Maxim verknüpfte Antwort falsch.
Maxschlepzig
4
Dies ist nur 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
2
@ Lothar Es wäre schön, einen Beweis zu sehen. man prctlsagt: Setzen Sie das Todessignal des übergeordneten Prozesses des aufrufenden Prozesses auf arg2 (entweder einen Signalwert im Bereich 1..maxsig oder 0 zum Löschen). Dies ist das Signal, das der aufrufende Prozess erhält, wenn sein übergeordnetes Element stirbt. Dieser Wert wird für das untergeordnete Element einer Verzweigung (2) und (seit Linux 2.4.36 / 2.6.23) gelöscht, wenn eine Binärdatei für die Set-User-ID oder die Set-Group-ID ausgeführt wird.
Qrdl
1
@maxschlepzig Danke für den neuen Link. Der vorherige Link scheint ungültig zu sein. Übrigens gibt es nach Jahren immer noch keine API, um Optionen auf der übergeordneten Seite festzulegen. Was für eine Schande.
Rox
68

Ich versuche, das gleiche Problem zu lösen, und da mein Programm unter OS X ausgeführt werden muss, hat die Nur-Linux-Lösung für mich nicht funktioniert.

Ich bin zu dem gleichen Schluss gekommen wie die anderen Personen auf dieser Seite - es gibt keine POSIX-kompatible Möglichkeit, ein Kind zu benachrichtigen, wenn ein Elternteil stirbt. Also habe ich das nächstbeste herausgefunden - die Kinderumfrage.

Wenn ein übergeordneter Prozess (aus irgendeinem Grund) stirbt, wird der übergeordnete Prozess des Kindes zu Prozess 1. Wenn das Kind einfach regelmäßig abfragt, kann es überprüfen, ob sein übergeordneter Prozess 1 ist. Wenn dies der Fall ist, sollte das Kind beendet werden.

Das ist nicht großartig, aber es funktioniert und ist einfacher als die an anderer Stelle auf dieser Seite vorgeschlagenen TCP-Socket- / Lockfile-Polling-Lösungen.

Schof
quelle
6
Hervorragende Lösung. Rufen Sie getppid () kontinuierlich auf, bis 1 zurückgegeben wird, und beenden Sie dann. Das ist gut und ich benutze es jetzt auch. Eine nicht Pollig-Lösung wäre allerdings schön. Danke Schof.
Neoneye
10
Nur zur Information, unter Solaris, wenn Sie sich in einer Zone befinden, wird das gettpid()nicht 1, sondern erhält das piddes Zonenplaners (Prozesses zsched).
Patrick Schlüter
4
Wenn sich jemand wundert, scheint in Android-Systemen die PID 0 (Prozess-System-PID) anstelle von 1 zu sein, wenn der Elternteil stirbt.
Rui Marques
2
Um eine robustere und plattformunabhängigere Methode zu erhalten, müssen Sie vor dem Verzweigen () einfach getpid () und wenn getppid () von child abweicht, beenden.
Sebastien
2
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
34

Ich habe dies in der Vergangenheit erreicht, indem ich den "ursprünglichen" Code im "Kind" und den "gespawnten" Code im "Elternteil" ausgeführt habe (das heißt: Sie kehren den üblichen Sinn des Tests danach um fork()). Dann fangen Sie SIGCHLD im "gespawnten" Code ein ...

Möglicherweise nicht in Ihrem Fall möglich, aber süß, wenn es funktioniert.

dmckee --- Ex-Moderator Kätzchen
quelle
Sehr schöne Lösung, danke! Die derzeit akzeptierte ist allgemeiner, aber Ihre ist portabler.
Paweł Hajdan
1
Das große Problem bei der Arbeit im übergeordneten Element besteht darin, dass Sie den übergeordneten Prozess ändern. Bei einem Server, der "für immer" ausgeführt werden muss, ist dies keine Option.
Alexis Wilke
29

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(0, pipes[0]); /* Use reader end as stdin */
    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 in einem Shell-Prozess mit aktivierter Jobsteuerung ausgeführt. Der untergeordnete Prozess wird im Hintergrund erzeugt. Die Shell wartet auf eine neue Zeile (oder einen EOF) und tötet dann das Kind.

Wenn der Elternteil stirbt - egal aus welchem ​​Grund - schließt er sein Rohrende. Die untergeordnete Shell erhält eine EOF aus dem Lesevorgang und beendet den untergeordneten Hintergrundprozess.

Phil Rutschman
quelle
2
Schön, aber fünf Systemaufrufe und ein in zehn Codezeilen erzeugter Sh lassen mich etwas skeptisch gegenüber diesen Codeleistungen sein.
Oleiade
+1. Sie können das dup2stdin vermeiden und übernehmen, indem Sie das read -uFlag verwenden, um aus einem bestimmten Dateideskriptor zu lesen. Ich habe setpgid(0, 0)dem Kind auch ein a hinzugefügt , um zu verhindern, dass es beim Drücken von ^ C im Terminal beendet wird.
Greg Hewgill
Die Argumentreihenfolge des dup2()Aufrufs ist falsch. Wenn Sie pipes[0]als Standard verwenden möchten, müssen Sie dup2(pipes[0], 0)statt schreiben dup2(0, pipes[0]). Hier schließt dup2(oldfd, newfd)der Anruf eine zuvor geöffnete neue Datei.
Maxschlepzig
@Oleiade, ich stimme zu, zumal der gespawnte sh nur eine weitere Gabelung macht, um den echten Kinderprozess auszuführen ...
maxschlepzig
16

Unter Linux können Sie ein untergeordnetes Todessignal im Kind installieren, z.

#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 durch das Speichern der übergeordneten Prozess-ID vor dem Fork und das Testen im untergeordneten Prozess prctl()eine Racebedingung zwischen prctl()und dem Beenden des Prozesses, der das untergeordnete Element aufgerufen hat, beseitigt wird .

Beachten Sie auch, dass das elterliche Todessignal des Kindes bei neu erstellten eigenen Kindern gelöscht wird. Es ist nicht betroffen von einem execve().

Dieser Test kann vereinfacht werden, wenn wir sicher sind, dass der Systemprozess, der für die Adoption aller Waisenkinder verantwortlich ist, PID 1 hat:

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 diesen Systemprozess zu verlassen initund PID 1 zu haben, ist jedoch nicht portabel.POSIX.1-2008 spezifiziert :

Die übergeordnete Prozess-ID aller vorhandenen untergeordneten Prozesse und Zombie-Prozesse des aufrufenden Prozesses wird auf die Prozess-ID eines implementierungsdefinierten Systemprozesses festgelegt. Das heißt, diese Prozesse sollen von einem speziellen Systemprozess geerbt werden.

Traditionell ist der Systemprozess, der alle Waisenkinder annimmt, PID 1, dh init - der Vorfahr aller Prozesse.

Auf modernen Systemen wie Linux oder FreeBSD könnte ein anderer Prozess diese Rolle spielen. Unter Linux kann ein Prozess beispielsweise aufrufen prctl(PR_SET_CHILD_SUBREAPER, 1), um sich als Systemprozess zu etablieren, der alle Waisenkinder eines seiner Nachkommen erbt (vgl. Ein Beispiel zu Fedora 25).

maxschlepzig
quelle
Ich verstehe nicht "Dieser Test kann vereinfacht werden, wenn wir sicher sind, dass der Großelternteil immer der Init-Prozess ist". Wenn ein übergeordneter Prozess stirbt, wird ein Prozess ein Kind des Init-Prozesses (PID 1), nicht ein Kind des Großelternteils, oder? Der Test scheint also immer korrekt zu sein.
Johannes Schaub - Litb
interessant, danke. obwohl ich nicht sehe, was es mit dem Großelternteil zu tun hat.
Johannes Schaub - Litb
1
@ JohannesSchaub-litb, Sie können nicht immer davon ausgehen, dass der Granparent eines Prozesses ein init(8)Prozess ist. Das einzige, was Sie annehmen können, ist, dass sich die übergeordnete ID ändert, wenn ein übergeordneter Prozess stirbt. Dies geschieht tatsächlich einmal im Leben eines Prozesses ... und dann, wenn der Elternteil des Prozesses stirbt. Es gibt nur eine Hauptausnahme und ist für init(8)Kinder, aber Sie sind davor geschützt, wie init(8)nie exit(2)(Kernel-Panik in diesem Fall)
Luis Colorado
1
Wenn ein untergeordnetes Element aus einem Thread herausgabelt und dann der Thread beendet wird, erhält der untergeordnete Prozess leider das SIGTERM.
Rox
14

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);
}
neoneye
quelle
Sie können dies mit einer etwas schöneren API tun, indem Sie Versandquellen mit DISPATCH_SOURCE_PROC und PROC_EXIT verwenden.
Russbischof
Aus irgendeinem Grund gerät mein Mac in Panik. Wenn Sie einen Prozess mit diesem Code ausführen, besteht eine Wahrscheinlichkeit von etwa 50%, dass er einfriert, was dazu führt, dass sich die Lüfter mit einer Geschwindigkeit drehen, die ich noch nie zuvor gehört habe (superschnell), und dann schaltet sich der Mac einfach aus. Seien Sie sehr vorsichtig mit diesem Code .
Qix - MONICA wurde
Unter meinem MacOS wird der untergeordnete Prozess nach dem Beenden des übergeordneten Prozesses automatisch beendet. Ich weiß nicht warum.
Yi Lin Liu
@YiLinLiu iirc Ich habe verwendet NSTaskoder posix spawn. Sehen Sie die startTaskFunktion in meinem Code hier: github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye
11

Hat der untergeordnete Prozess eine Pipe zum / vom übergeordneten Prozess? In diesem Fall erhalten Sie beim Schreiben ein SIGPIPE oder beim Lesen eine EOF - diese Bedingungen können erkannt werden.

MarkR
quelle
1
Ich fand, dass dies nicht zuverlässig geschah, zumindest unter OS X.
Schof
Ich bin hergekommen, um diese Antwort hinzuzufügen. Dies ist definitiv die Lösung, die ich für diesen Fall verwenden würde.
Dolda2000
Vorsichtshinweis: systemd deaktiviert SIGPIPEs standardmäßig in den von ihm verwalteten Diensten, Sie können jedoch weiterhin prüfen, ob das Rohr geschlossen ist. Siehe freedesktop.org/software/systemd/man/systemd.exec.html unter IgnoreSIGPIPE
jdizzle
11

Inspiriert von einer anderen Antwort hier, habe ich die folgende All-POSIX-Lösung entwickelt. Die allgemeine Idee besteht darin, einen Zwischenprozess zwischen dem Elternteil und dem Kind zu erstellen, der einen Zweck hat: Beachten Sie, wenn der Elternteil stirbt, und töten Sie das Kind explizit.

Diese Art von Lösung ist nützlich, wenn der Code im untergeordneten Element nicht geändert werden kann.

int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
    close(p[1]); // close write end of pipe
    setpgid(0, 0); // prevent ^C in parent from stopping this process
    child = fork();
    if (child == 0) {
        close(p[0]); // close read end of pipe (don't need it here)
        exec(...child process here...);
        exit(1);
    }
    read(p[0], 1); // returns when parent exits for any reason
    kill(child, 9);
    exit(1);
}

Bei dieser Methode gibt es zwei kleine Einschränkungen:

  • Wenn Sie den Zwischenprozess absichtlich beenden, wird das Kind nicht getötet, wenn der Elternteil stirbt.
  • Wenn das Kind vor dem Elternteil beendet wird, versucht der Zwischenprozess, die ursprüngliche untergeordnete PID zu beenden, was nun auf einen anderen Prozess verweisen könnte. (Dies könnte mit mehr Code im Zwischenprozess behoben werden.)

Abgesehen davon ist der eigentliche Code, den ich verwende, in Python. Hier ist es der Vollständigkeit halber:

def run(*args):
    (r, w) = os.pipe()
    child = os.fork()
    if child == 0:
        os.close(w)
        os.setpgid(0, 0)
        child = os.fork()
        if child == 0:
            os.close(r)
            os.execl(args[0], *args)
            os._exit(1)
        os.read(r, 1)
        os.kill(child, 9)
        os._exit(1)
    os.close(r)
Greg Hewgill
quelle
Beachten Sie, dass ich vor einiger Zeit unter IRIX ein Eltern / Kind-Schema verwendet habe, bei dem ich eine Pipe zwischen beiden hatte und das Lesen aus der Pipe ein SIGHUP erzeugte, wenn einer der beiden starb. Auf diese Weise habe ich meine Kinder getötet, ohne dass ein Zwischenprozess erforderlich war.
Alexis Wilke
Ich denke, Ihre zweite Einschränkung ist falsch. Die PID eines Kindes ist eine Ressource, die zu seinem Elternteil gehört, und sie kann nicht freigegeben / wiederverwendet werden, bis das Elternteil (der Zwischenprozess) darauf wartet (oder beendet wird und init darauf warten lässt).
R .. GitHub STOP HELPING ICE
7

Ich glaube nicht, dass es möglich ist, zu garantieren, dass nur Standard-POSIX-Aufrufe verwendet werden. Wie im wirklichen Leben hat ein Kind, sobald es geboren ist, ein Eigenleben.

Es ist möglich, dass der übergeordnete Prozess die meisten möglichen Beendigungsereignisse abfängt und versucht, den untergeordneten Prozess zu diesem Zeitpunkt zu beenden, aber es gibt immer einige, die nicht abgefangen werden können.

Zum Beispiel kann kein Prozess a fangen SIGKILL. Wenn der Kernel dieses Signal verarbeitet, wird der angegebene Prozess ohne Benachrichtigung an diesen Prozess abgebrochen.

Um die Analogie zu erweitern - die einzige andere Standardmethode besteht darin, dass das Kind Selbstmord begeht, wenn es feststellt, dass es keinen Elternteil mehr hat.

Es gibt nur eine Linux-Möglichkeit, dies zu tun prctl(2)- siehe andere Antworten.

Alnitak
quelle
6

Wie andere Leute bereits betont haben, ist es nicht portabel, sich darauf zu verlassen, dass die Eltern-PID 1 wird, wenn die Eltern aussteigen. Anstatt auf eine bestimmte übergeordnete Prozess-ID zu warten, warten Sie einfach, bis sich die ID ändert:

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;

Fügen Sie nach Wunsch einen Mikroschlaf hinzu, wenn Sie nicht mit voller Geschwindigkeit abfragen möchten.

Diese Option erscheint mir einfacher als die Verwendung einer Pipe oder die Verwendung von Signalen.

user2168915
quelle
Leider ist diese Lösung nicht robust. Was ist, wenn der übergeordnete Prozess stirbt, bevor Sie den Anfangswert erhalten? Das Kind wird niemals gehen.
Dgatwood
@dgatwood, was meinst du?!? Der erste getpid()Vorgang wird vor dem Anruf im übergeordneten Element ausgeführt fork(). Wenn der Elternteil vorher stirbt, existiert das Kind nicht. Was passieren kann, ist, dass das Kind eine Weile bei den Eltern lebt.
Alexis Wilke
In diesem etwas erfundenen Beispiel funktioniert es, aber im realen Code folgt auf Fork fast immer exec, und der neue Prozess muss von vorne beginnen, indem nach seiner PPID gefragt wird. In der Zeit zwischen diesen beiden Kontrollen hätte das Kind keine Ahnung, wenn der Elternteil weggeht. Außerdem ist es unwahrscheinlich, dass Sie die Kontrolle über den übergeordneten und den untergeordneten Code haben (oder Sie können die PPID einfach als Argument übergeben). Als allgemeine Lösung funktioniert dieser Ansatz also nicht sehr gut. Und realistisch gesehen, wenn ein UNIX-ähnliches Betriebssystem herauskommen würde, ohne dass init 1 ist, würde so viel kaputt gehen, dass ich mir sowieso nicht vorstellen kann, dass es jemand tut.
Dgatwood
pass parent pid ist ein Befehlszeilenargument, wenn exec for child ausgeführt wird.
Nish
2
Abfragen mit voller Geschwindigkeit ist verrückt.
Maxschlepzig
4

Installieren Sie einen Trap-Handler , um SIGINT zu fangen, der Ihren untergeordneten Prozess abbricht, wenn er noch lebt, obwohl andere Poster korrekt sind, dass er SIGKILL nicht fängt.

Öffnen Sie eine .lock-Datei mit exklusivem Zugriff und lassen Sie die untergeordnete Umfrage versuchen, sie zu öffnen. Wenn das Öffnen erfolgreich ist, sollte der untergeordnete Prozess beendet werden

Ana Betts
quelle
Oder das Kind könnte die Sperrdatei in einem separaten Thread im Blockierungsmodus öffnen. In diesem Fall könnte dies eine ziemlich nette und saubere Lösung sein. Wahrscheinlich hat es jedoch einige Portabilitätsbeschränkungen.
Jean
4

Diese Lösung hat bei mir funktioniert:

  • Übergeben Sie stdin pipe an child - Sie müssen keine Daten in den Stream schreiben.
  • Kind liest auf unbestimmte Zeit von stdin bis EOF. Ein EOF signalisiert, dass der Elternteil gegangen ist.
  • Dies ist eine kinderleichte und tragbare Methode, um festzustellen, wann die Eltern gegangen sind. Selbst wenn das übergeordnete Element abstürzt, schließt das Betriebssystem die Pipe.

Dies war für einen arbeiterartigen Prozess gedacht, dessen Existenz nur zu Lebzeiten der Eltern Sinn machte.

joonas.fi
quelle
@SebastianJylanki Ich erinnere mich nicht, ob ich es versucht habe, aber es funktioniert wahrscheinlich, weil die Grundelemente (POSIX-Streams) unter allen Betriebssystemen ziemlich Standard sind.
joonas.fi
3

Ich denke, ein schneller und schmutziger Weg besteht darin, eine Verbindung zwischen Kind und Eltern herzustellen. Wenn Eltern gehen, erhalten Kinder ein ZEICHEN.

Yefei
quelle
SIGPIPE wird nicht beim Schließen der Pipe gesendet, sondern nur, wenn das Kind versucht, darauf zu schreiben.
Alcaro
3

Einige Plakate haben bereits Pfeifen und erwähnt kqueue. Tatsächlich können Sie durch den Aufruf auch ein Paar verbundener Unix-Domain-Sockets erstellen socketpair(). Der Sockeltyp sollte seinSOCK_STREAM .

Angenommen, Sie haben die beiden Socket-Dateideskriptoren fd1, fd2. Nun , fork()das Kind Prozess zu schaffen, die die fds erben. Im Elternteil schließen Sie fd2 und im Kind schließen Sie fd1. Jetzt kann jeder Prozess poll()das verbleibende offene fd an seinem eigenen Ende für das POLLINEreignis. Solange jede Seite close()während der normalen Lebensdauer nicht explizit ihren fd hat, können Sie ziemlich sicher sein, dass ein POLLHUPFlag die Beendigung des anderen anzeigt (egal ob sauber oder nicht). Nach Benachrichtigung über dieses Ereignis kann das Kind entscheiden, was zu tun ist (z. B. um zu sterben).

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}

Sie können versuchen, den obigen Proof-of-Concept-Code zu kompilieren und in einem Terminal wie auszuführen ./a.out & . Sie haben ungefähr 100 Sekunden Zeit, um mit dem Abschalten der übergeordneten PID durch verschiedene Signale zu experimentieren, oder sie wird einfach beendet. In beiden Fällen sollte die Meldung "Kind: Eltern aufgelegt" angezeigt werden.

Im Vergleich zur Methode mit dem SIGPIPEHandler muss für diese Methode der write()Aufruf nicht ausgeführt werden.

Diese Methode ist auch symmetrisch , dh die Prozesse können denselben Kanal verwenden, um die Existenz des anderen zu überwachen.

Diese Lösung ruft nur die POSIX-Funktionen auf. Ich habe dies unter Linux und FreeBSD versucht. Ich denke, es sollte unter anderen Unixen funktionieren, aber ich habe es nicht wirklich getestet.

Siehe auch:

  • unix(7)von Linux - man - Seiten, unix(4)für FreeBSD, poll(2), socketpair(2), socket(7)auf Linux.
Cong Ma
quelle
Sehr cool, ich frage mich wirklich, ob dies irgendwelche Zuverlässigkeitsprobleme hat. Haben Sie dies in der Produktion getestet? Mit verschiedenen Apps?
Aktau
@Aktau, ich habe das Python-Äquivalent dieses Tricks in einem Linux-Programm verwendet. Ich brauchte es, weil die Arbeitslogik des Kindes darin besteht, "nach dem Verlassen des Elternteils nach besten Kräften und dann auch nach dem Aufräumen eine Bereinigung nach besten Kräften durchzuführen". Bei anderen Plattformen bin ich mir jedoch nicht sicher. Das C-Snippet funktioniert unter Linux und FreeBSD, aber das ist alles, was ich weiß ... Es gibt auch Fälle, in denen Sie vorsichtig sein sollten, z Rennbedingung).
Cong Ma
@Aktau - Dies wird absolut zuverlässig sein.
Omnifarious
1

Unter POSIX , die exit(), _exit()und _Exit()werden Funktionen definiert:

  • Wenn der Prozess ein steuernder Prozess ist, muss das SIGHUP-Signal an jeden Prozess in der Vordergrundprozessgruppe des zum aufrufenden Prozess gehörenden steuernden Terminals gesendet werden.

Wenn Sie also festlegen, dass der übergeordnete Prozess ein steuernder Prozess für seine Prozessgruppe ist, sollte das untergeordnete Element beim Beenden des übergeordneten Prozesses ein SIGHUP-Signal erhalten. Ich bin mir nicht sicher, ob das passiert, wenn die Eltern abstürzen, aber ich denke, das passiert. Sicherlich sollte es für Fälle ohne Absturz gut funktionieren.

Beachten Sie, dass Sie möglicherweise viel Kleingedrucktes lesen müssen - einschließlich des Abschnitts Basisdefinitionen (Definitionen) sowie der Systemdienstinformationen für exit()und setsid()und setpgrp()-, um ein vollständiges Bild zu erhalten. (Würde ich auch!)

Jonathan Leffler
quelle
3
Hmm. Die Dokumentation ist diesbezüglich vage und widersprüchlich, aber es scheint, dass der übergeordnete Prozess der Hauptprozess für die Sitzung sein muss, nicht nur die Prozessgruppe. Der Hauptprozess für die Sitzung war immer die Anmeldung, und es lag im Moment außerhalb meiner Fähigkeiten, meinen Prozess als Hauptprozess für eine neue Sitzung zu übernehmen.
Schof
2
SIGHUP wird effektiv nur an untergeordnete Prozesse gesendet, wenn der beendende Prozess eine Anmeldeshell ist. opengroup.org/onlinepubs/009695399/functions/exit.html "Die Beendigung eines Prozesses beendet seine Kinder nicht direkt. Das Senden eines SIGHUP-Signals, wie unten beschrieben, beendet Kinder indirekt / unter bestimmten Umständen /."
Rob K
1
@Rob: Richtig - das sagt auch das Zitat, das ich gegeben habe: Nur unter bestimmten Umständen erhält der untergeordnete Prozess ein SIGHUP. Und es ist streng genommen eine Vereinfachung zu sagen, dass es nur eine Login-Shell ist, die SIGHUP sendet, obwohl dies der häufigste Fall ist. Wenn sich ein Prozess mit mehreren untergeordneten Elementen als Steuerungsprozess für sich selbst und seine untergeordneten Elemente einrichtet, wird das SIGHUP (bequemerweise) an seine untergeordneten Elemente gesendet, wenn der Master stirbt. OTOH, Prozesse sind selten so problematisch - daher bin ich eher ein Trottel als ein wirklich bedeutendes Problem.
Jonathan Leffler
2
Ich habe ein paar Stunden damit herumgespielt und konnte es nicht zum Laufen bringen. Es hätte einen Fall gut gehandhabt, in dem ich einen Dämon mit einigen Kindern habe, die alle sterben müssen, wenn die Eltern gehen.
Rob K
1

Wenn Sie beispielsweise ein Signal an die PID 0 senden

kill(0, 2); /* SIGINT */

Dieses Signal wird an die gesamte Prozessgruppe gesendet, wodurch das Kind effektiv getötet wird.

Sie können es einfach mit etwas wie testen:

(cat && kill 0) | python

Wenn Sie dann ^ D drücken, wird der Text "Terminated"als Hinweis darauf angezeigt, dass der Python-Interpreter tatsächlich getötet wurde, anstatt nur beendet zu werden, weil stdin geschlossen wurde.

Thorbiörn Fritzon
quelle
1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" druckt glücklich 4 statt beendet
Kamil Szot
1

Falls es für andere relevant ist, wenn ich JVM-Instanzen in gegabelten untergeordneten Prozessen aus C ++ spawne, war die einzige Möglichkeit, die JVM-Instanzen nach Abschluss des übergeordneten Prozesses ordnungsgemäß zu beenden, die folgenden Schritte. Hoffentlich kann jemand in den Kommentaren Feedback geben, wenn dies nicht der beste Weg ist, dies zu tun.

1) Rufen Sie prctl(PR_SET_PDEATHSIG, SIGHUP)den gegabelten untergeordneten Prozess wie vorgeschlagen auf, bevor Sie die Java-App über execvund starten

2) Fügen Sie der Java-Anwendung einen Shutdown-Hook hinzu, der abfragt, bis die übergeordnete PID gleich 1 ist, und führen Sie dann eine harte Aktion durch Runtime.getRuntime().halt(0). Die Abfrage erfolgt durch Starten einer separaten Shell, in der der psBefehl ausgeführt wird (siehe: Wie finde ich meine PID in Java oder JRuby unter Linux? ).

EDIT 130118:

Es scheint, dass dies keine robuste Lösung war. Ich habe immer noch Probleme, die Nuancen der Vorgänge zu verstehen, aber manchmal bekam ich immer noch verwaiste JVM-Prozesse, wenn diese Anwendungen in Bildschirm- / SSH-Sitzungen ausgeführt wurden.

Anstatt die PPID in der Java-App abzufragen, ließ ich einfach den Shutdown-Hook eine Bereinigung durchführen, gefolgt von einem harten Stopp wie oben. Dann habe ich sichergestellt, dass waitpidin der übergeordneten C ++ - App der erzeugte untergeordnete Prozess aufgerufen wird, wenn es Zeit war, alles zu beenden. Dies scheint eine robustere Lösung zu sein, da der untergeordnete Prozess sicherstellt, dass er beendet wird, während der übergeordnete Prozess vorhandene Verweise verwendet, um sicherzustellen, dass seine untergeordneten Prozesse beendet werden. Vergleichen Sie dies mit der vorherigen Lösung, bei der der übergeordnete Prozess jederzeit beendet wurde und die Kinder versuchen, herauszufinden, ob sie vor dem Beenden verwaist waren.

jasterm007
quelle
1
Das PID equals 1Warten ist ungültig. Das neue übergeordnete Element könnte eine andere PID sein. Sie sollten überprüfen, ob es vom ursprünglichen übergeordneten Element (getpid () vor dem fork ()) zum neuen übergeordneten Element (getppid () im untergeordneten Element (getppid ()) wechselt, wenn es vor dem fork () aufgerufen wird.
Alexis Wilke
1

Eine andere Linux-spezifische Möglichkeit besteht darin, das übergeordnete Element in einem neuen PID-Namespace zu erstellen. Es wird dann PID 1 in diesem Namespace sein, und wenn es beendet wird, werden alle seine Kinder sofort mit getötet SIGKILL.

Leider müssen Sie haben, um einen neuen PID-Namespace zu erstellen CAP_SYS_ADMIN. Diese Methode ist jedoch sehr effektiv und erfordert keine wirklichen Änderungen an den Eltern oder den Kindern über den ersten Start des Elternteils hinaus.

Siehe Klon (2) , pid_namespaces (7) und Unshare (2) .

Allgegenwärtig
quelle
Ich muss auf andere Weise bearbeiten. Es ist möglich, prctl zu verwenden, um einen Prozess als Init-Prozess für alle Kinder und Enkelkinder sowie für Urenkel usw. zu verwenden.
Omnifarious
0

Wenn ein Elternteil stirbt, ändert sich die PPID von Waisenkindern auf 1 - Sie müssen nur Ihre eigene PPID überprüfen. In gewisser Weise handelt es sich um eine Umfrage, die oben erwähnt wurde. Hier ist das Muschelstück dafür:

check_parent () {
      parent=`ps -f|awk '$2=='$PID'{print $3 }'`
      echo "parent:$parent"
      let parent=$parent+0
      if [[ $parent -eq 1 ]]; then
        echo "parent is dead, exiting"
        exit;
      fi
}


PID=$$
cnt=0
while [[ 1 = 1 ]]; do
  check_parent
  ... something
done
alex K.
quelle
0

Ich habe 2 Lösungen gefunden, beide nicht perfekt.

1. Töten Sie alle Kinder durch Töten (-pid), wenn Sie das SIGTERM-Signal erhalten.
Offensichtlich kann diese Lösung "kill -9" nicht verarbeiten, funktioniert jedoch in den meisten Fällen und ist sehr einfach, da nicht alle untergeordneten Prozesse gespeichert werden müssen.


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }

Auf die gleiche Weise können Sie den 'exit'-Handler wie oben beschrieben installieren, wenn Sie process.exit irgendwo aufrufen. Hinweis: Strg + C und plötzlicher Absturz wurden vom Betriebssystem automatisch verarbeitet, um die Prozessgruppe zu beenden, daher hier nicht mehr.

2.Verwenden Sie chjj / pty.js , um Ihren Prozess mit angeschlossenem Steuerungsterminal zu erzeugen .
Wenn Sie den aktuellen Prozess ohnehin mit -9 beenden, werden auch alle untergeordneten Prozesse automatisch beendet (vom Betriebssystem?). Ich vermute, dass der aktuelle Prozess SIGPIPE erhält, wenn der aktuelle Prozess eine andere Seite des Terminals hält. Wenn der aktuelle Prozess stirbt, stirbt er.


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */
osexp2003
quelle
0

Ich habe es geschafft, eine tragbare Lösung ohne Abfrage mit drei Prozessen zu erstellen, indem ich die Terminalsteuerung und Sitzungen missbraucht habe. Das ist mentale Masturbation, funktioniert aber.

Der Trick ist:

  • Prozess A wird gestartet
  • Prozess A erstellt eine Pipe P (und liest nie daraus)
  • Prozess A teilt sich in Prozess B.
  • Prozess B erstellt eine neue Sitzung
  • Prozess B weist dieser neuen Sitzung ein virtuelles Terminal zu
  • Prozess B installiert den SIGCHLD-Handler, um zu sterben, wenn das Kind beendet wird
  • Prozess B setzt einen SIGPIPE-Handler
  • Prozess B teilt sich in Prozess C.
  • Prozess C macht alles, was er braucht (zB exec () ist die unveränderte Binärdatei oder führt jede Logik aus)
  • Prozess B schreibt in Pipe P (und blockiert auf diese Weise)
  • Prozess A wait () s auf Prozess B und wird beendet, wenn er stirbt

Dieser Weg:

  • Wenn Prozess A stirbt: Prozess B erhält ein SIGPIPE und stirbt
  • Wenn Prozess B stirbt: wait () von Prozess A kehrt zurück und stirbt, Prozess C erhält ein SIGHUP (denn wenn der Sitzungsleiter einer Sitzung mit angeschlossenem Terminal stirbt, erhalten alle Prozesse in der Prozessgruppe im Vordergrund ein SIGHUP)
  • Wenn Prozess C stirbt: Prozess B erhält eine SIGCHLD und stirbt, also stirbt Prozess A.

Mängel:

  • Prozess C kann SIGHUP nicht verarbeiten
  • Prozess C wird in einer anderen Sitzung ausgeführt
  • Prozess C kann die Sitzungs- / Prozessgruppen-API nicht verwenden, da dadurch das spröde Setup unterbrochen wird
  • Es ist nicht die beste Idee, für jede solche Operation ein Terminal zu erstellen
Marek Dopiera
quelle
0

Obwohl 7 Jahre vergangen sind, bin ich gerade auf dieses Problem gestoßen, als ich die SpringBoot-Anwendung ausführe, die den Webpack-Dev-Server während der Entwicklung starten und beenden muss, wenn der Backend-Prozess stoppt.

Ich versuche zu verwenden, Runtime.getRuntime().addShutdownHookaber es funktionierte unter Windows 10, aber nicht unter Windows 7.

Ich habe es so geändert, dass ein dedizierter Thread verwendet wird, der darauf wartet, dass der Prozess beendet wird, oder InterruptedExceptionder auf beiden Windows-Versionen ordnungsgemäß zu funktionieren scheint.

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}
Ido Ran
quelle
0

In der Vergangenheit hat das Prozesssystem unter UNIX v7 die Verwaistung von Prozessen durch Überprüfen der übergeordneten ID eines Prozesses erkannt. Wie ich bereits sagte, ist der init(8)Systemprozess historisch gesehen nur aus einem Grund ein besonderer Prozess: Er kann nicht sterben. Es kann nicht sterben, da der Kernel-Algorithmus zum Zuweisen einer neuen übergeordneten Prozess-ID von dieser Tatsache abhängt. Wenn ein Prozess seinen exit(2)Aufruf ausführt (mittels eines Prozesssystemaufrufs oder einer externen Task als Signal oder dergleichen), weist der Kernel allen untergeordneten Elementen dieses Prozesses die ID des Init-Prozesses als übergeordnete Prozess-ID zu. Dies führt zu dem einfachsten Test und der portabelsten Methode, um festzustellen, ob ein Prozess verwaist ist. Überprüfen Sie einfach das Ergebnis des getppid(2)Systemaufrufs und ob es sich um die Prozess-ID des handeltinit(2) Prozess dann wurde der Prozess vor dem Systemaufruf verwaist.

Aus diesem Ansatz ergeben sich zwei Probleme, die zu Problemen führen können:

  • Erstens haben wir die Möglichkeit, den initProzess in einen beliebigen Benutzerprozess zu ändern. Wie können wir also sicherstellen, dass der Init-Prozess immer übergeordnet zu allen verwaisten Prozessen ist? Nun, im exitSystemaufrufcode wird explizit geprüft, ob der Prozess, der den Aufruf ausführt, der Init-Prozess ist (der Prozess mit einer PID von 1), und wenn dies der Fall ist, gerät der Kernel in Panik (er sollte nicht mehr gewartet werden können die Prozesshierarchie), so dass der Init-Prozess keinen exit(2)Aufruf ausführen darf .
  • Zweitens gibt es eine Rennbedingung in dem oben gezeigten Basistest. Die ID des anfänglichen Prozesses wird historisch angenommen, dies 1wird jedoch durch den POSIX-Ansatz nicht gerechtfertigt, der besagt (wie in einer anderen Antwort angegeben), dass nur die Prozess-ID eines Systems für diesen Zweck reserviert ist. Fast keine Posix-Implementierung tut dies, und Sie können in ursprünglichen, von Unix abgeleiteten Systemen davon ausgehen, dass eine 1Antwort auf einen getppid(2)Systemaufruf ausreicht, um anzunehmen, dass der Prozess verwaist ist. Eine andere Möglichkeit, dies zu überprüfen, besteht darin, getppid(2)direkt nach der Abzweigung einen Wert zu erstellen und diesen Wert mit dem Ergebnis eines neuen Aufrufs zu vergleichen. Dies funktioniert einfach nicht in allen Fällen, da beide Aufrufe nicht zusammen atomar sind und der übergeordnete Prozess nach dem fork(2)und vor dem ersten getppid(2)Systemaufruf abbrechen kann. Der Prozess beendet parent id only changes once, when its parent does an(2) call, so this should be enough to check if thegetppid (2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children ofinit (8) `, aber Sie können davon ausgehen, dass diese Prozesse auch keine übergeordneten Prozesse haben (außer wenn Sie den init-Prozess in einem System ersetzen).
Luis Colorado
quelle
-1

Ich habe die übergeordnete PID mithilfe der Umgebung an das untergeordnete Element übergeben und dann regelmäßig überprüft, ob / proc / $ ppid vom untergeordneten Element vorhanden ist.

Sergei Kirjanov
quelle