Come far morire il processo figlio dopo l'uscita del genitore?


209

Supponiamo che io abbia un processo che genera esattamente un processo figlio. Ora, quando il processo genitore termina per qualsiasi motivo (normalmente o in modo anormale, uccidendo, ^ C, affermando il fallimento o qualsiasi altra cosa) voglio che il processo figlio muoia. Come farlo correttamente?


Qualche domanda simile su StackOverflow:


Qualche domanda simile su StackOverflow per Windows :

Risposte:


189

Il bambino può chiedere al kernel di fornire SIGHUP(o altro segnale) quando il genitore muore specificando l'opzione PR_SET_PDEATHSIGin prctl()syscall in questo modo:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Vedi man 2 prctlper i dettagli.

Modifica: questo è solo Linux


5
Questa è una soluzione scadente perché il genitore potrebbe essere già morto. Condizione di gara. Soluzione corretta: stackoverflow.com/a/17589555/412080
Maxim Egorushkin,

16
Chiamare una risposta scadente non è molto bello, anche se non affronta una condizione di gara. Vedi la mia risposta su come utilizzare prctl()in modo libero dalle condizioni di gara. A proposito, la risposta collegata da Maxim non è corretta.
maxschlepzig,

4
Questa è solo una risposta sbagliata. Invierà il segnale al processo figlio nel momento in cui il thread che ha chiamato fork muore, non quando il processo parent muore.
Lothar,

2
@Lothar Sarebbe bello vedere una sorta di prova. man prctldice: Imposta il segnale di morte del processo genitore del processo chiamante su arg2 (o un valore del segnale nell'intervallo 1..maxsig o 0 per cancellare). Questo è il segnale che il processo di chiamata riceverà quando il suo genitore muore. Questo valore viene cancellato per il figlio di un fork (2) e (dal Linux 2.4.36 / 2.6.23) quando si esegue un binario set-user-ID o set-group-ID.
qrdl

1
@maxschlepzig Grazie per il nuovo link. Sembra che il link precedente non sia valido. A proposito, dopo anni, non ci sono ancora api per l'impostazione delle opzioni sul lato genitore. Che peccato.
rox,

68

Sto cercando di risolvere lo stesso problema e poiché il mio programma deve essere eseguito su OS X, la soluzione solo per Linux non ha funzionato per me.

Sono giunto alla stessa conclusione delle altre persone in questa pagina: non esiste un modo compatibile con POSIX per avvisare un bambino quando un genitore muore. Quindi ho raccolto la cosa migliore da fare: avere il sondaggio per bambini.

Quando un processo genitore muore (per qualsiasi motivo), il processo genitore del bambino diventa processo 1. Se il bambino esegue semplicemente il polling periodicamente, può verificare se il suo genitore è 1. Se lo è, il bambino dovrebbe uscire.

Questo non è eccezionale, ma funziona ed è più semplice delle soluzioni di polling TCP socket / lockfile suggerite altrove in questa pagina.


6
Soluzione eccellente Invocando continuamente getppid () fino a quando non restituisce 1 e quindi esce. Questo è buono e ora lo uso anche io. Una soluzione non-pollig sarebbe comunque carina. Grazie Schof.
neoneye,

10
Solo per informazioni, su Solaris se ci si trova in una zona, gettpid()non diventa 1 ma ottiene lo pidscheduler di zona (processo zsched).
Patrick Schlüter,

4
Se qualcuno si sta chiedendo, nei sistemi Android il pid sembra essere 0 (process System pid) anziché 1, quando il genitore muore.
Rui Marques,

2
Per avere un modo più robusto e indipendente dalla piattaforma di farlo, prima di fork () - ing, semplicemente getpid () e se getppid () da child è diverso, esci.
Sebastien,

2
Questo non funziona se non controlli il processo figlio. Ad esempio, sto lavorando a un comando che avvolge find (1) e voglio assicurarmi che il find venga ucciso se il wrapper muore per qualche motivo.
Lucretiel

34

Ho raggiunto questo obiettivo in passato eseguendo il codice "originale" nel "figlio" e il codice "generato" nel "genitore" (vale a dire: si inverte il senso normale del test dopo fork() ). Quindi intercetta SIGCHLD nel codice "generato" ...

Potrebbe non essere possibile nel tuo caso, ma carino quando funziona.


Ottima soluzione, grazie! Quello attualmente accettato è più generico, ma il tuo è più portatile.
Paweł Hajdan,

1
L'enorme problema nel fare il lavoro nel genitore è che stai cambiando il processo genitore. Nel caso di un server che deve funzionare "per sempre", questa non è un'opzione.
Alexis Wilke,

29

Se non riesci a modificare il processo figlio, puoi provare qualcosa di simile al seguente:

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 */

Questo esegue il figlio all'interno di un processo di shell con il controllo del lavoro abilitato. Il processo figlio viene generato in background. La shell attende una nuova linea (o un EOF), quindi uccide il bambino.

Quando il genitore muore, non importa quale sia la ragione, chiuderà la sua estremità del tubo. La shell figlio otterrà un EOF dalla lettura e procederà all'eliminazione del processo figlio in background.


2
Bello, ma cinque chiamate di sistema e uno sh generato in dieci righe di codici mi rende un po 'scettico su questo pezzo di prestazioni del codice.
Oleiade,

+1. È possibile evitare dup2e assumere lo stdin utilizzando il read -uflag per leggere da un descrittore di file specifico. Ho anche aggiunto a setpgid(0, 0)nel bambino per impedirne l'uscita quando si preme ^ C nel terminale.
Greg Hewgill,

L'ordine degli argomenti della dup2()chiamata è errato. Se vuoi usare pipes[0]come stdin devi scrivere dup2(pipes[0], 0)invece di dup2(0, pipes[0]). È dup2(oldfd, newfd)dove la chiamata chiude un newfd precedentemente aperto.
maxschlepzig,

@Oleiade, sono d'accordo, soprattutto perché lo sh generato genera solo un altro fork per eseguire il vero processo figlio ...
maxschlepzig

16

Sotto Linux, puoi installare un segnale di morte del genitore nel figlio, ad esempio:

#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 ...

Si noti che la memorizzazione dell'ID del processo padre prima del fork e il test nel figlio dopo prctl()elimina una condizione di competizione tra prctl()e l'uscita del processo che ha chiamato il figlio.

Si noti inoltre che il segnale di morte del genitore del bambino viene cancellato nei propri figli appena creati. Non è interessato da un execve().

Tale test può essere semplificato se siamo certi che il processo di sistema incaricato di adottare tutti gli orfani abbia 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 ...

Tuttavia, basarsi sul processo del sistema inite avere PID 1 non è portatile. POSIX.1-2008 specifica :

L'ID del processo padre di tutti i processi figlio esistenti e i processi zombie del processo chiamante devono essere impostati sull'ID processo di un processo di sistema definito dall'implementazione. Cioè, questi processi devono essere ereditati da un processo di sistema speciale.

Tradizionalmente, il processo di sistema che adotta tutti gli orfani è il PID 1, ovvero init, che è l'antenato di tutti i processi.

Su sistemi moderni come Linux o FreeBSD un altro processo potrebbe avere quel ruolo. Ad esempio, su Linux, un processo può chiamare prctl(PR_SET_CHILD_SUBREAPER, 1)per affermarsi come processo di sistema che eredita tutti gli orfani di uno dei suoi discendenti (cfr. Un esempio su Fedora 25).


Non capisco "Quel test può essere semplificato se siamo certi che il nonno è sempre il processo di init". Quando un processo genitore muore, un processo diventa un figlio del processo init (pid 1), non un figlio del nonno, giusto? Quindi il test sembra sempre corretto.
Johannes Schaub - litb,


interessante, grazie. anche se non riesco a vedere cosa ha a che fare con il nonno.
Johannes Schaub - litb

1
@ JohannesSchaub-litb, non puoi sempre presumere che il nonno di un processo sarà init(8)process ... l'unica cosa che puoi supporre è che quando un processo genitore muore, è che il suo id genitore cambierà. Questo in realtà accade una volta nella vita di un processo .... ed è quando il genitore del processo muore. C'è solo un'eccezione principale a questo, ed è per i init(8)bambini, ma tu sei protetto da questo, come init(8)mai exit(2)(panico del kernel in quel caso)
Luis Colorado

1
Sfortunatamente, se un figlio esegue il fork da un thread e quindi esce dal thread, il processo figlio otterrà SIGTERM.
rox,

14

Per completezza. Su macOS puoi usare kqueue:

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);
}

Puoi farlo con un'API leggermente più carina, usando le fonti di spedizione con DISPATCH_SOURCE_PROC e PROC_EXIT.
russbishop

Per qualsiasi motivo, questo sta causando il panico sul mio Mac. L'esecuzione di un processo con questo codice ha una probabilità del 50% circa di congelamento, facendo girare le ventole a una velocità che non ho mai sentito prima (super veloce), quindi il mac si spegne. ESSERE MOLTO ATTENTI CON QUESTO CODICE .
Qix - MONICA È STATA MISTREATA

Sembra che sul mio macOS, il processo figlio esca automaticamente dopo l'uscita del genitore. Non so perché.
Yi Lin Liu,

@YiLinLiu iirc Ho usato NSTasko spawn posix. Vedi la startTaskfunzione nel mio codice qui: github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye,

11

Il processo figlio ha una pipe da / verso il processo parent? In tal caso, riceveresti un SIGPIPE se scrivi, o otterrai EOF durante la lettura: queste condizioni potrebbero essere rilevate.


1
Ho scoperto che questo non è accaduto in modo affidabile, almeno su OS X.
Schof

Sono venuto qui per aggiungere questa risposta. Questa è sicuramente la soluzione che userei per questo caso.
Dolda 2000,

punto di attenzione: systemd disabilita SIGPIPE per impostazione predefinita nei servizi che gestisce, ma è comunque possibile verificare la chiusura del tubo. Vedi freedesktop.org/software/systemd/man/systemd.exec.html sotto IgnoreSIGPIPE
jdizzle

11

Ispirato da un'altra risposta qui, ho trovato la seguente soluzione completamente POSIX. L'idea generale è quella di creare un processo intermedio tra genitore e figlio, che abbia uno scopo: notare quando il genitore muore e uccidere esplicitamente il figlio.

Questo tipo di soluzione è utile quando non è possibile modificare il codice nel figlio.

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);
}

Esistono due piccoli avvertimenti con questo metodo:

  • Se uccidi deliberatamente il processo intermedio, il bambino non verrà ucciso alla morte del genitore.
  • Se il bambino esce prima del genitore, il processo intermedio tenterà di eliminare il pid figlio originale, che ora potrebbe riferirsi a un processo diverso. (Questo potrebbe essere risolto con più codice nel processo intermedio.)

A parte questo, il vero codice che sto usando è in Python. Eccolo per completezza:

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)

Si noti che qualche tempo fa, in IRIX, ho usato uno schema genitore / figlio in cui avevo una pipe tra entrambe e la lettura della pipe generava un SIGHUP se uno dei due fosse morto. Era il modo in cui uccidevo i miei figli fork, senza la necessità di un processo intermedio.
Alexis Wilke,

Penso che il tuo secondo avvertimento sia sbagliato. Il pid di un figlio è una risorsa appartenente al suo genitore e non può essere liberato / riutilizzato fino a quando il genitore (il processo intermedio) lo attende (o termina e lascia che init lo attenda).
R .. GitHub FERMA DI AIUTARE ICE il

7

Non credo sia possibile garantire che utilizzando solo chiamate POSIX standard. Come la vita reale, una volta generato un bambino, ha una vita propria.

Lo è possibile che il processo padre per prendere più possibili eventi di terminazione, e tentare di uccidere il processo figlio, a quel punto, ma c'è sempre un po' che non può essere catturato.

Ad esempio, nessun processo può rilevare a SIGKILL . Quando il kernel gestisce questo segnale, ucciderà il processo specificato senza alcuna notifica a quel processo.

Estendere l'analogia - l'unico altro modo standard per farlo è che il bambino si suicidi quando scopre che non ha più un genitore.

Esiste un modo solo per Linux di farlo prctl(2)- vedi altre risposte.


6

Come altre persone hanno sottolineato, basarsi sul pid del genitore per diventare 1 quando il genitore esce non è portatile. Invece di attendere un ID processo genitore specifico, attendi che l'ID cambi:

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)
    ;

Aggiungi un micro-sleep come desiderato se non vuoi eseguire il polling alla massima velocità.

Questa opzione mi sembra più semplice che usare una pipa o fare affidamento sui segnali.


Sfortunatamente, quella soluzione non è solida. Cosa succede se il processo genitore muore prima di ottenere il valore iniziale? Il bambino non uscirà mai.
dgatwood,

@dgatwood, che vuoi dire?!? Il primo getpid()viene eseguito nel genitore prima di chiamare fork(). Se il genitore muore prima, il bambino non esiste. Ciò che può accadere è che il bambino vivi il genitore per un po '.
Alexis Wilke,

In questo esempio un po 'inventato, funziona, ma nel codice del mondo reale, fork è quasi invariabilmente seguito da exec, e il nuovo processo deve ricominciare chiedendo il suo PPID. Nel tempo tra quei due controlli, se il genitore se ne va, il bambino non ne avrebbe idea. Inoltre, è improbabile che tu abbia il controllo del codice padre e figlio (oppure potresti semplicemente passare il PPID come argomento). Quindi, come soluzione generale, tale approccio non funziona molto bene. E realisticamente, se un sistema operativo simile a UNIX uscisse senza che init fosse 1, si romperanno così tante cose che non riesco a immaginare che qualcuno lo faccia comunque.
Dgatwood,

pass parent pid è l'argomento della riga di comando quando si esegue exec per child.
Nish,

2
Il polling a tutta velocità è folle.
maxschlepzig,

4

Installa un gestore di trappole per catturare SIGINT, che interrompe il processo figlio se è ancora in vita, anche se altri poster hanno ragione che non catturerà SIGKILL.

Apri un file .lock con accesso esclusivo e fai in modo che il sondaggio figlio su di esso provi ad aprirlo - se l'apertura ha esito positivo, il processo figlio dovrebbe uscire


Oppure, il bambino potrebbe aprire il file di blocco in un thread separato, in modalità di blocco, nel qual caso questa potrebbe essere una soluzione piuttosto bella e pulita. Probabilmente ha alcuni limiti di portabilità.
Jean,

4

Questa soluzione ha funzionato per me:

  • Passa la pipe stdin al figlio - non devi scrivere alcun dato nel flusso.
  • Il bambino legge indefinitamente da stdin fino a EOF. Un EOF segnala che il genitore se n'è andato.
  • Questo è un modo infallibile e portatile per rilevare quando il genitore se ne è andato. Anche se il genitore si arresta in modo anomalo, il sistema operativo chiuderà la pipe.

Questo era per un processo di tipo operaio la cui esistenza aveva senso solo quando il genitore era vivo.


@SebastianJylanki Non ricordo se ci ho provato, ma probabilmente funziona perché i primitivi (flussi POSIX) sono abbastanza standard su tutti i sistemi operativi.
joonas.fi,

3

Penso che un modo rapido e sporco sia quello di creare una pipa tra figlio e genitore. Quando il genitore esce, i figli riceveranno un SIGPIPE.


SIGPIPE non viene inviato alla chiusura della pipe, viene inviato solo quando il bambino tenta di scrivergli.
Alcaro,

3

Alcuni manifesti hanno già menzionato pipe e kqueue. In effetti, puoi anche creare una coppia di socket di dominio Unix collegati tramite la socketpair()chiamata. Il tipo di socket dovrebbe essereSOCK_STREAM .

Supponiamo di avere i due descrittori di file socket fd1, fd2. Ora fork()per creare il processo figlio, che erediterà il fds. Nel genitore chiudi fd2 e nel figlio chiudi fd1. Ora ogni processo può poll()rimanere aperto fd sul proprio fine per l' POLLINevento. Fintanto che ciascuna parte non close()espone esplicitamente la sua fd durante la normale vita, puoi essere abbastanza sicuro che una POLLHUPbandiera dovrebbe indicare la fine dell'altra (non importa se pulita o no). Una volta informato di questo evento, il bambino può decidere cosa fare (ad es. Morire).

#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);
    }
}

Puoi provare a compilare il codice di prova del concetto sopra riportato ed eseguirlo in un terminale come ./a.out & . Hai circa 100 secondi per sperimentare l'uccisione del PID padre con vari segnali, o semplicemente uscirà. In entrambi i casi, dovresti visualizzare il messaggio "figlio: genitore riattaccato".

Rispetto al metodo che utilizza il SIGPIPEgestore, questo metodo non richiede di provare la write()chiamata.

Questo metodo è anche simmetrico , ovvero i processi possono utilizzare lo stesso canale per monitorare l'esistenza reciproca.

Questa soluzione chiama solo le funzioni POSIX. Ho provato questo in Linux e FreeBSD. Penso che dovrebbe funzionare su altri Unix ma non ho ancora testato.

Guarda anche:

  • unix(7)di pagine man di Linux, unix(4)FreeBSD, poll(2), socketpair(2), socket(7)su Linux.

Molto bello, mi chiedo davvero se questo abbia qualche problema di affidabilità. Hai provato questo in produzione? Con diverse app?
Aktau,

@Aktau, ho usato l'equivalente Python di questo trucco in un programma Linux. Ne avevo bisogno perché la logica di lavoro del bambino è "fare la pulizia del massimo sforzo dopo che il genitore esce e poi esce anche". Tuttavia, non sono davvero sicuro di altre piattaforme. Lo snippet C funziona su Linux e FreeBSD ma questo è tutto quello che so ... Inoltre, ci sono casi in cui dovresti stare attento, come il genitore che biforca di nuovo, o il genitore che rinuncia al fd prima di uscire veramente (creando così una finestra temporale per condizione di gara).
Cong Ma

@Aktau - Questo sarà completamente affidabile.
Onnipotente il

1

Sotto POSIX , i exit(), _exit()e le _Exit()funzioni sono definite a:

  • Se il processo è un processo di controllo, il segnale SIGHUP deve essere inviato a ciascun processo nel gruppo di processi in primo piano del terminale di controllo appartenente al processo di chiamata.

Pertanto, se si prevede che il processo padre sia un processo di controllo per il relativo gruppo di processi, il figlio dovrebbe ricevere un segnale SIGHUP quando il padre esce. Non sono assolutamente sicuro che ciò accada quando il genitore si blocca, ma penso che lo faccia. Certamente, per i casi non crash, dovrebbe funzionare bene.

Si noti che potrebbe essere necessario leggere un bel po 'di stampa fine, inclusa la sezione Definizioni di base (Definizioni), nonché le informazioni sui Servizi di sistema per exit()e setsid()e setpgrp()- per ottenere il quadro completo. (Anch'io!)


3
Hmm. La documentazione è vaga e contraddittoria su questo, ma sembra che il processo genitore debba essere il processo principale per la sessione, non solo il gruppo di processi. Il processo principale per la sessione era sempre il login, e il fatto che il mio processo prendesse il posto del processo principale per una nuova sessione era al di là delle mie capacità al momento.
Schof

2
SIGHUP viene effettivamente inviato ai processi figlio solo se il processo di uscita è una shell di accesso. opengroup.org/onlinepubs/009695399/functions/exit.html "La conclusione di un processo non termina direttamente i suoi figli. L'invio di un segnale SIGHUP come descritto di seguito termina indirettamente i bambini / in alcune circostanze /."
Rob K,

1
@Rob: corretto - è anche quello che dice la citazione che ho dato: che solo in alcune circostanze il processo figlio ottiene un SIGHUP. Ed è rigorosamente una semplificazione eccessiva dire che è solo una shell di login che invia SIGHUP, anche se questo è il caso più comune. Se un processo con più figli si imposta come processo di controllo per sé e per i suoi figli, allora il SIGHUP (convenientemente) verrà inviato ai suoi figli quando il padrone muore. OTOH, i processi raramente portano a così tanti problemi - quindi ho più nit-picking che sollevare un cavillo davvero significativo.
Jonathan Leffler,

2
Mi sono preso gioco di lui per un paio d'ore e non sono riuscito a farlo funzionare. Avrebbe gestito bene un caso in cui ho un demone con alcuni bambini che devono morire tutti quando il genitore esce.
Rob K,

1

Se invii un segnale al pid 0, usando ad esempio

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

quel segnale viene inviato all'intero gruppo di processo, uccidendo così efficacemente il bambino.

Puoi provarlo facilmente con qualcosa come:

(cat && kill 0) | python

Se poi premi ^ D, vedrai il testo "Terminated"come un'indicazione che l'interprete Python è stato effettivamente ucciso, invece di essere appena uscito a causa della chiusura di stdin.


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" stampa felicemente 4 invece di Terminato
Kamil Szot

1

Nel caso in cui sia rilevante per chiunque, quando ho generato istanze JVM in processi figlio biforcuti da C ++, l'unico modo in cui ho potuto far terminare correttamente le istanze JVM dopo che il processo genitore è stato completato è stato quello di fare quanto segue. Speriamo che qualcuno possa fornire un feedback nei commenti se questo non è il modo migliore per farlo.

1) Richiamare prctl(PR_SET_PDEATHSIG, SIGHUP)il processo figlio biforcuto come suggerito prima di avviare l'app Java tramite execv, e

2) Aggiungi un hook di arresto all'applicazione Java che esegue il polling fino a quando il suo PID padre è uguale a 1, quindi fai un duro Runtime.getRuntime().halt(0). Il polling viene eseguito avviando una shell separata che esegue il pscomando (Vedi: Come posso trovare il mio PID in Java o JRuby su Linux? ).

EDIT 130118:

Sembra che non fosse una soluzione solida. Faccio ancora fatica a capire le sfumature di ciò che sta succedendo, ma a volte stavo ancora ottenendo processi JVM orfani quando eseguivo queste applicazioni in sessioni schermo / SSH.

Invece di eseguire il polling per il PPID nell'app Java, ho semplicemente avuto l'hook dell'arresto per eseguire la pulizia seguito da una brusca interruzione come sopra. Quindi mi sono assicurato di invocare waitpidl'app padre C ++ sul processo figlio generato quando era il momento di terminare tutto. Questa sembra essere una soluzione più solida, in quanto il processo figlio ne assicura la chiusura, mentre il genitore utilizza riferimenti esistenti per assicurarsi che i suoi figli terminino. Confronta questo con la soluzione precedente che aveva interrotto il processo genitore ogni volta che lo desiderava e che i bambini cercavano di capire se erano rimasti orfani prima di terminare.


1
L' PID equals 1attesa non è valida. Il nuovo genitore potrebbe essere un altro PID. Dovresti verificare se cambia dal genitore originale (getpid () prima del fork ()) al nuovo genitore (getppid () nel figlio non uguale a getpid () quando chiamato prima del fork ()).
Alexis Wilke,

1

Un altro modo per fare ciò che è specifico di Linux è quello di creare il genitore in un nuovo spazio dei nomi PID. Sarà quindi PID 1 in quello spazio dei nomi, e quando esce da esso tutti i suoi figli verranno immediatamente uccisi SIGKILL.

Sfortunatamente, per creare un nuovo spazio dei nomi PID devi avere CAP_SYS_ADMIN. Ma questo metodo è molto efficace e non richiede cambiamenti reali per il genitore o i figli oltre l'avvio iniziale del genitore.

Vedi clone (2) , pid_namespaces (7) e unshare (2) .


Devo modificare in un altro modo. È possibile utilizzare prctl per fare in modo che un processo funga da processo di init per tutti i suoi figli e nipoti, pronipoti, ecc ...
Onnipotente il

0

Se il genitore muore, il PPID degli orfani cambia in 1 - devi solo controllare il tuo PPID. In un certo senso, questo è il polling, menzionato sopra. ecco un pezzo di shell per questo:

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

0

Ho trovato 2 soluzioni, entrambe non perfette.

1. Uccidi tutti i bambini uccidendo (-pid) quando riceve il segnale SIGTERM.
Ovviamente, questa soluzione non può gestire "kill -9", ma funziona nella maggior parte dei casi e molto semplice perché non ha bisogno di ricordare tutti i processi figlio.


    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();
      });
    }

Allo stesso modo, puoi installare il gestore 'exit' come sopra se chiami process.exit da qualche parte. Nota: Ctrl + C e l'arresto improvviso sono stati elaborati automaticamente dal sistema operativo per eliminare il gruppo di processi, quindi non più qui.

2.Utilizzare chjj / pty.js per generare il processo con il terminale di controllo collegato.
Quando uccidi il processo corrente anche uccidendo -9, anche tutti i processi figlio verranno automaticamente uccisi (dal sistema operativo?). Immagino che, poiché il processo corrente contiene un altro lato del terminale, quindi se il processo corrente muore, il processo figlio otterrà SIGPIPE, quindi muore.


    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(.....);
    */

0

Sono riuscito a realizzare una soluzione portatile e senza polling con 3 processi abusando del controllo del terminale e delle sessioni. Questa è masturbazione mentale, ma funziona.

Il trucco è:

  • il processo A è avviato
  • il processo A crea una pipe P (e non legge mai da essa)
  • process A forks in process B
  • il processo B crea una nuova sessione
  • il processo B alloca un terminale virtuale per quella nuova sessione
  • il processo B installa il gestore SIGCHLD per morire quando il bambino esce
  • il processo B imposta un gestore SIGPIPE
  • il processo B si trasforma nel processo C
  • il processo C fa tutto ciò di cui ha bisogno (es. exec () s il binario non modificato o esegue qualsiasi logica)
  • il processo B scrive sulla pipe P (e blocca in quel modo)
  • process A wait () s sul processo B ed esce quando muore

Quel modo:

  • se il processo A muore: il processo B ottiene un SIGPIPE e muore
  • se il processo B muore: il processo A wait () ritorna e muore, il processo C ottiene un SIGHUP (perché quando il leader della sessione di una sessione con un terminale collegato muore, tutti i processi nel gruppo di processi in primo piano ottengono un SIGHUP)
  • se il processo C muore: il processo B ottiene un SIGCHLD e muore, quindi il processo A muore

carenze:

  • il processo C non è in grado di gestire SIGHUP
  • il processo C verrà eseguito in una sessione diversa
  • il processo C non può utilizzare l'API gruppo sessione / processo perché interromperà la fragile configurazione
  • creare un terminale per ogni operazione del genere non è la migliore idea di sempre

0

Anche se sono passati 7 anni, ho appena incontrato questo problema mentre sto eseguendo l'applicazione SpringBoot che deve avviare webpack-dev-server durante lo sviluppo e deve ucciderlo quando si arresta il processo di back-end.

Provo a usare Runtime.getRuntime().addShutdownHookma ha funzionato su Windows 10 ma non su Windows 7.

L'ho modificato per utilizzare un thread dedicato che attende la chiusura del processo o per il InterruptedExceptionquale sembra funzionare correttamente su entrambe le versioni di Windows.

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();
}

0

Storicamente, da UNIX v7, il sistema di processo ha rilevato l'orfanotrofia dei processi controllando l'id padre di un processo. Come ho detto, storicamente, il init(8)processo di sistema è un processo speciale per una sola ragione: non può morire. Non può morire perché l'algoritmo del kernel per gestire l'assegnazione di un nuovo ID del processo genitore dipende da questo fatto. quando un processo esegue la sua exit(2)chiamata (tramite una chiamata di sistema del processo o da un'attività esterna come invio di un segnale o simili) il kernel riassegna tutti i figli di questo processo all'ID del processo init come ID del processo padre. Questo porta al test più semplice e al modo più portatile per sapere se un processo è diventato orfano. Basta controllare il risultato del processo, quindi il processo è diventato orfano prima della chiamata di sistema.getppid(2) chiamata di sistema e se è l'ID di processo diinit(2)

Da questo approccio emergono due problemi che possono portare a problemi:

  • in primo luogo, abbiamo la possibilità di modificare il initprocesso in qualsiasi processo utente, quindi Come possiamo assicurare che il processo init sarà sempre genitore di tutti i processi orfani? Bene, nel exitcodice di chiamata di sistema c'è un controllo esplicito per vedere se il processo che esegue la chiamata è il processo di init (il processo con pid uguale a 1) e se è così, il panico del kernel (non dovrebbe più essere in grado di mantenere la gerarchia dei processi), pertanto non è consentito al processo init di effettuare una exit(2)chiamata.
  • secondo, c'è una condizione di competizione nel test di base esposto sopra. L'id del processo di Init è considerato storicamente essere 1, ma ciò non è garantito dall'approccio POSIX, che afferma (come esposto in altre risposte) che solo l'id di processo di un sistema è riservato a tale scopo. Quasi nessuna implementazione di posix lo fa e si può presumere nei sistemi derivati ​​unix originali che avere 1come risposta alla getppid(2)chiamata di sistema è sufficiente supporre che il processo sia orfano. Un altro modo per verificare è quello di fare getppid(2)subito il fork e confrontare quel valore con il risultato di una nuova chiamata. Questo semplicemente non funziona in tutti i casi, poiché entrambe le chiamate non sono atomiche insieme e il processo padre può morire dopo fork(2)e prima della prima getppid(2)chiamata di sistema. L' parent id only changes once, when its parent does anuscita del processo (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) `, ma si può presumere che questi processi non abbiano nemmeno un genitore (tranne quando si sostituisce in un sistema il processo init)

-1

Ho passato il pid genitore usando l'ambiente al figlio, quindi ho periodicamente verificato se esiste / proc / $ ppid dal figlio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.