Comment faire mourir le processus de l'enfant après la sortie des parents?


209

Supposons que j'ai un processus qui engendre exactement un processus enfant. Maintenant, lorsque le processus parent se termine pour une raison quelconque (normalement ou anormalement, par kill, ^ C, affirmer l'échec ou toute autre chose), je veux que le processus enfant meure. Comment faire ça correctement?


Une question similaire sur stackoverflow:


Une question similaire sur stackoverflow pour Windows :

Réponses:


189

L'enfant peut demander au noyau de fournir SIGHUP(ou un autre signal) lorsque le parent décède en spécifiant l'option PR_SET_PDEATHSIGdans prctl()syscall comme ceci:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Voir man 2 prctlpour plus de détails.

Edit: Ceci est uniquement Linux


5
C'est une mauvaise solution car le parent est peut-être déjà décédé. Condition de course. Solution correcte: stackoverflow.com/a/17589555/412080
Maxim Egorushkin

16
Appeler une réponse médiocre n'est pas très agréable - même si cela ne répond pas à une condition de concurrence. Voir ma réponse sur la façon de l'utiliser prctl()sans condition de course. Btw, la réponse liée par Maxim est incorrecte.
maxschlepzig

4
C'est juste une mauvaise réponse. Il enverra le signal au processus enfant au moment où le thread qui a appelé fork meurt, pas quand le processus parent meurt.
Lothar

2
@Lothar Ce serait bien de voir une sorte de preuve. man prctldit: Définissez le signal de mort du processus parent du processus appelant sur arg2 (soit une valeur de signal dans la plage 1..maxsig, soit 0 pour effacer). C'est le signal que le processus appelant recevra lorsque son parent décède. Cette valeur est effacée pour l'enfant d'un fork (2) et (depuis Linux 2.4.36 / 2.6.23) lors de l'exécution d'un binaire set-user-ID ou set-group-ID.
qrdl

1
@maxschlepzig Merci pour le nouveau lien. Il semble que le lien précédent ne soit pas valide. Soit dit en passant, après des années, il n'y a toujours pas d'api pour définir les options du côté parent. Quel dommage.
rox

68

J'essaie de résoudre le même problème, et puisque mon programme doit fonctionner sur OS X, la solution Linux uniquement ne fonctionnait pas pour moi.

Je suis arrivé à la même conclusion que les autres personnes sur cette page - il n'y a pas de moyen compatible POSIX d'avertir un enfant quand un parent décède. J'ai donc jeté un coup d'œil à la prochaine meilleure chose - avoir le sondage des enfants.

Lorsqu'un processus parent meurt (pour une raison quelconque), le processus parent de l'enfant devient le processus 1. Si l'enfant interroge simplement périodiquement, il peut vérifier si son parent est 1. Si tel est le cas, l'enfant doit quitter.

Ce n'est pas génial, mais cela fonctionne, et c'est plus facile que les solutions d'interrogation de socket / fichier de verrouillage TCP suggérées ailleurs sur cette page.


6
Excellente solution. Appel continu de getppid () jusqu'à ce qu'il renvoie 1, puis quitte. C'est bien et je l'utilise maintenant aussi. Une solution non pollig serait bien cependant. Merci Schof.
neoneye

10
Juste pour info, sur Solaris si vous êtes dans une zone, le gettpid()ne devient pas 1 mais obtient le pidplanificateur de zone (processus zsched).
Patrick Schlüter du

4
Si quelqu'un se demande, dans les systèmes Android, le pid semble être 0 (traiter le système pid) au lieu de 1, lorsque le parent décède.
Rui Marques

2
Pour avoir une manière plus robuste et indépendante de la plateforme de le faire, avant fork () - ing, il suffit de getpid () et si getppid () de child est différent, quittez.
Sebastien

2
Cela ne fonctionne pas si vous ne contrôlez pas le processus enfant. Par exemple, je travaille sur une commande qui encapsule find (1), et je veux m'assurer que la recherche est supprimée si l'encapsuleur meurt pour une raison quelconque.
Lucretiel

34

J'ai réalisé cela dans le passé en exécutant le code "original" dans "l'enfant" et le code "engendré" dans le "parent" (c'est-à-dire: vous inversez le sens habituel du test après fork()). Puis piéger SIGCHLD dans le code "engendré" ...

Peut-être pas possible dans votre cas, mais mignon quand cela fonctionne.


Très belle solution, merci! Celui actuellement accepté est plus générique, mais le vôtre est plus portable.
Paweł Hajdan,

1
L'énorme problème avec le travail chez le parent est que vous changez le processus parent. Dans le cas d'un serveur qui doit fonctionner "pour toujours", ce n'est pas une option.
Alexis Wilke

29

Si vous ne parvenez pas à modifier le processus enfant, vous pouvez essayer quelque chose comme ceci:

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

Cela exécute l'enfant à partir d'un processus shell avec le contrôle des travaux activé. Le processus enfant est généré en arrière-plan. Le shell attend une nouvelle ligne (ou un EOF) puis tue l'enfant.

Lorsque le parent décède - quelle qu'en soit la raison - il ferme son extrémité du tuyau. Le shell enfant obtiendra un EOF de la lecture et procédera à la suppression du processus enfant en arrière-plan.


2
Bien, mais cinq appels système, et un sh généré en dix lignes de codes me laisse un peu sceptique quant à ces performances de code.
Oleiade

+1. Vous pouvez éviter dup2et reprendre stdin en utilisant l' read -uindicateur pour lire à partir d'un descripteur de fichier spécifique. J'ai également ajouté un setpgid(0, 0)dans l'enfant pour l'empêcher de sortir en appuyant sur ^ C dans le terminal.
Greg Hewgill

L'ordre des arguments de l' dup2()appel est incorrect. Si vous souhaitez utiliser pipes[0]comme stdin, vous devez écrire à la dup2(pipes[0], 0)place de dup2(0, pipes[0]). C'est dup2(oldfd, newfd)là que l'appel ferme un newfd précédemment ouvert.
maxschlepzig

@Oleiade, je suis d'accord, d'autant plus que le sh engendré ne fait qu'un autre fork pour exécuter le vrai processus enfant ...
maxschlepzig

16

Sous Linux, vous pouvez installer un signal de mort parentale chez l'enfant, par exemple:

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

Notez que le stockage de l'ID de processus parent avant le fork et le test dans l'enfant après prctl()élimine une condition de concurrence entre prctl()et la sortie du processus qui a appelé l'enfant.

Notez également que le signal de décès des parents de l'enfant est effacé chez les enfants nouvellement créés. Il n'est pas affecté par un execve().

Ce test peut être simplifié si nous sommes certains que le processus système chargé de l'adoption de tous les orphelins a le 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 ...

S'appuyer sur ce processus système étant initet ayant PID 1 n'est pas portable, cependant. POSIX.1-2008 spécifie :

L'ID de processus parent de tous les processus enfants et processus zombies existants du processus appelant doit être défini sur l'ID de processus d'un processus système défini par l'implémentation. Autrement dit, ces processus doivent être hérités par un processus système spécial.

Traditionnellement, le processus système adoptant tous les orphelins est PID 1, c'est-à-dire init - qui est l'ancêtre de tous les processus.

Sur les systèmes modernes comme Linux ou FreeBSD, un autre processus pourrait avoir ce rôle. Par exemple, sous Linux, un processus peut appeler prctl(PR_SET_CHILD_SUBREAPER, 1)pour s'établir comme processus système qui hérite de tous les orphelins de l'un de ses descendants (cf. un exemple sur Fedora 25).


Je ne comprends pas "Ce test peut être simplifié si nous sommes certains que le grand-parent est toujours le processus init". Quand un processus parent meurt, un processus devient un enfant du processus init (pid 1), pas un enfant des grands-parents, non? Le test semble donc toujours correct.
Johannes Schaub - litb


intéressant, merci. cependant, je ne vois pas ce que cela a à voir avec les grands-parents.
Johannes Schaub - litb

1
@ JohannesSchaub-litb, vous ne pouvez pas toujours supposer que le granparent d'un processus sera un init(8)processus .... la seule chose que vous pouvez supposer est que lorsqu'un processus parent meurt, c'est que son ID parent changera. Cela se produit en fait une fois dans la vie d'un processus ... et c'est lorsque le parent du processus décède. Il n'y a qu'une seule exception principale à cela, et c'est pour les init(8)enfants, mais vous êtes protégé contre cela, comme init(8)jamais exit(2)(panique du noyau dans ce cas)
Luis Colorado

1
Malheureusement, si un enfant bifurque à partir d'un thread, puis quitte le thread, le processus enfant obtiendra le SIGTERM.
rox

14

Par souci d'exhaustivité. Sur macOS, vous pouvez utiliser 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);
}

Vous pouvez le faire avec une API légèrement plus agréable, en utilisant des sources de répartition avec DISPATCH_SOURCE_PROC et PROC_EXIT.
russbishop

Pour une raison quelconque, cela fait paniquer mon Mac. L'exécution d'un processus avec ce code a 50% de chances de geler, ce qui fait tourner les fans à un rythme que je ne les ai jamais entendu (super rapide), puis le mac s'arrête. SOYEZ TRÈS PRUDENT AVEC CE CODE .
Qix - MONICA A ÉTÉ BRUÉE

Il semble que sur mon macOS, le processus enfant se ferme automatiquement après la sortie du parent. Je ne sais pas pourquoi.
Yi Lin Liu

@YiLinLiu iirc J'ai utilisé NSTaskou spawn posix. Voir la startTaskfonction dans mon code ici: github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye

11

Le processus enfant a-t-il un canal vers / depuis le processus parent? Si c'est le cas, vous recevrez un SIGPIPE si vous écrivez, ou vous obtiendrez EOF lors de la lecture - ces conditions pourraient être détectées.


1
J'ai trouvé que cela ne s'était pas produit de manière fiable, au moins sur OS X.
Schof

Je suis venu ici pour ajouter cette réponse. C'est certainement la solution que j'utiliserais pour ce cas.
Dolda2000

point de prudence: systemd désactive SIGPIPE par défaut dans les services qu'il gère, mais vous pouvez toujours vérifier la fermeture du tuyau. Voir freedesktop.org/software/systemd/man/systemd.exec.html sous IgnoreSIGPIPE
jdizzle

11

Inspiré par une autre réponse ici, j'ai trouvé la solution tout-POSIX suivante. L'idée générale est de créer un processus intermédiaire entre le parent et l'enfant, qui a un seul but: remarquer quand le parent meurt et tuer explicitement l'enfant.

Ce type de solution est utile lorsque le code de l'enfant ne peut pas être modifié.

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

Il y a deux petites mises en garde avec cette méthode:

  • Si vous tuez délibérément le processus intermédiaire, l'enfant ne sera pas tué lorsque le parent décède.
  • Si l'enfant quitte avant le parent, le processus intermédiaire essaiera de tuer le pid enfant d'origine, qui pourrait maintenant faire référence à un processus différent. (Cela pourrait être corrigé avec plus de code dans le processus intermédiaire.)

En passant, le code que j'utilise est en Python. Le voici pour être complet:

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)

Notez qu'il y a quelque temps, sous IRIX, j'ai utilisé un schéma parent / enfant où j'avais un tuyau entre les deux et la lecture du tuyau générait un SIGHUP si l'un des deux mourait. C'était la façon dont j'avais l'habitude de tuer mes enfants de fork (), sans avoir besoin d'un processus intermédiaire.
Alexis Wilke

Je pense que votre deuxième mise en garde est erronée. Le pid d'un enfant est une ressource appartenant à son parent et il ne peut pas être libéré / réutilisé jusqu'à ce que le parent (le processus intermédiaire) l'attende (ou se termine et laisse init l'attendre).
R .. GitHub STOP HELPING ICE

7

Je ne pense pas qu'il soit possible de garantir que l'utilisation uniquement des appels POSIX standard. Comme dans la vraie vie, une fois qu'un enfant est né, il a sa propre vie.

Il est possible que le processus parent intercepte la plupart des événements de terminaison possibles et tente de tuer le processus enfant à ce stade, mais il y en a toujours qui ne peuvent pas être interceptés.

Par exemple, aucun processus ne peut attraper a SIGKILL. Lorsque le noyau gère ce signal, il va tuer le processus spécifié sans aucune notification à ce processus.

Pour étendre l'analogie - la seule autre façon standard de le faire est de se suicider lorsque l'enfant découvre qu'il n'a plus de parent.

Il existe une façon de faire uniquement avec Linux prctl(2)- voir d'autres réponses.


6

Comme d'autres personnes l'ont souligné, s'appuyer sur le pid parent pour devenir 1 lorsque le parent sort n'est pas portable. Au lieu d'attendre un ID de processus parent spécifique, attendez simplement que l'ID change:

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

Ajoutez un micro-sommeil comme vous le souhaitez si vous ne souhaitez pas interroger à pleine vitesse.

Cette option me semble plus simple que d'utiliser un tuyau ou de s'appuyer sur des signaux.


Malheureusement, cette solution n'est pas robuste. Que se passe-t-il si le processus parent meurt avant d'obtenir la valeur initiale? L'enfant ne sortira jamais.
dgatwood

@dgatwood, que voulez-vous dire?!? Le premier getpid()se fait dans le parent avant d'appeler fork(). Si le parent décède avant, l'enfant n'existe pas. Ce qui peut arriver, c'est que l'enfant vit son parent pendant un certain temps.
Alexis Wilke

Dans cet exemple quelque peu artificiel, cela fonctionne, mais dans le code du monde réel, fork est presque invariablement suivi par exec, et le nouveau processus doit recommencer en demandant son PPID. Entre ces deux contrôles, si le parent s'en va, l'enfant n'en aurait aucune idée. En outre, il est peu probable que vous ayez le contrôle sur le code parent et le code enfant (ou bien vous pourriez simplement passer le PPID comme argument). Donc, en tant que solution générale, cette approche ne fonctionne pas très bien. Et de façon réaliste, si un système d'exploitation de type UNIX sortait sans que l'init soit 1, tant de choses se briseraient que je ne peux pas imaginer que quelqu'un le fasse de toute façon.
dgatwood

pass parent pid est l'argument de la ligne de commande lors de l'exécution de exec pour enfant.
Nish

2
Le vote à pleine vitesse est fou.
maxschlepzig

4

Installez un gestionnaire de pièges pour attraper SIGINT, ce qui tue votre processus enfant s'il est encore en vie, bien que d'autres affiches soient correctes pour qu'il n'attrape pas SIGKILL.

Ouvrez un .lockfile avec un accès exclusif et demandez au sondage enfant d'essayer de l'ouvrir - si l'ouverture réussit, le processus enfant devrait quitter


Ou, l'enfant pourrait ouvrir le fichier de verrouillage dans un thread séparé, en mode blocage, auquel cas cela pourrait être une solution assez agréable et propre. Cependant, il a probablement des limites de portabilité.
Jean

4

Cette solution a fonctionné pour moi:

  • Passez le canal stdin à l'enfant - vous n'avez pas à écrire de données dans le flux.
  • L'enfant lit indéfiniment de stdin jusqu'à EOF. Un EOF signale que le parent est parti.
  • C'est un moyen infaillible et portable pour détecter quand le parent est parti. Même si le parent plante, le système d'exploitation fermera le tuyau.

Il s'agissait d'un processus de type travailleur dont l'existence n'avait de sens que lorsque le parent était vivant.


@SebastianJylanki Je ne me souviens pas si j'ai essayé, mais cela fonctionne probablement parce que les primitives (flux POSIX) sont assez standard sur tous les systèmes d'exploitation.
joonas.fi

3

Je pense qu'un moyen rapide et sale est de créer un tuyau entre l'enfant et le parent. À la sortie du parent, les enfants recevront un SIGPIPE.


SIGPIPE n'est pas envoyé à la fermeture du tuyau, il n'est envoyé que lorsque l'enfant essaie d'y écrire.
Alcaro

3

Certaines affiches ont déjà mentionné les pipes et kqueue. En fait, vous pouvez également créer une paire de sockets de domaine Unix connectés par l' socketpair()appel. Le type de socket doit êtreSOCK_STREAM .

Supposons que vous ayez les deux descripteurs de fichier socket fd1, fd2. Maintenant, fork()pour créer le processus enfant, qui héritera des fds. Dans le parent, vous fermez fd2 et chez l'enfant, vous fermez fd1. Maintenant, chaque processus peut poll()ouvrir le fd restant de sa propre extrémité pour l' POLLINévénement. Tant que chaque côté n'a pas explicitement close()son fd pendant la durée de vie normale, vous pouvez être assez sûr qu'un POLLHUPindicateur doit indiquer la terminaison de l'autre (que ce soit propre ou non). Dès notification de cet événement, l'enfant peut décider quoi faire (par exemple mourir).

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

Vous pouvez essayer de compiler le code de validation de principe ci-dessus et l'exécuter dans un terminal comme ./a.out & . Vous avez environ 100 secondes pour expérimenter la destruction du PID parent par divers signaux, ou il se fermera simplement. Dans les deux cas, vous devriez voir le message "enfant: parent raccroché".

Par rapport à la méthode utilisant le SIGPIPEgestionnaire, cette méthode ne nécessite pas d'essayer l' write()appel.

Cette méthode est également symétrique , c'est-à-dire que les processus peuvent utiliser le même canal pour surveiller leur existence mutuelle.

Cette solution appelle uniquement les fonctions POSIX. J'ai essayé cela sous Linux et FreeBSD. Je pense que cela devrait fonctionner sur d'autres Unix, mais je n'ai pas vraiment testé.

Voir également:

  • unix(7)des pages de manuel Linux, unix(4)FreeBSD, poll(2), socketpair(2), socket(7)sous Linux.

Très cool, je me demande vraiment si cela a des problèmes de fiabilité. L'avez-vous testé en production? Avec différentes applications?
Aktau

@ Aktau, j'ai utilisé l'équivalent Python de cette astuce dans un programme Linux. J'en avais besoin parce que la logique de travail de l'enfant est de "faire le meilleur nettoyage possible après la sortie des parents puis de sortir aussi". Cependant, je ne suis vraiment pas sûr des autres plates-formes. L'extrait C fonctionne sur Linux et FreeBSD mais c'est tout ce que je sais ... De plus, il y a des cas où vous devez être prudent, comme le parent bifurquant à nouveau, ou le parent abandonnant le fd avant de vraiment quitter (créant ainsi une fenêtre de temps pour condition de course).
Cong Ma

@Aktau - Ce sera complètement fiable.
Omnifarious

1

Sous Posix , les exit(), _exit()et les _Exit()fonctions sont définies à:

  • Si le processus est un processus de contrôle, le signal SIGHUP doit être envoyé à chaque processus du groupe de processus de premier plan du terminal de contrôle appartenant au processus appelant.

Ainsi, si vous vous arrêtez pour que le processus parent soit un processus de contrôle pour son groupe de processus, l'enfant doit recevoir un signal SIGHUP lorsque le parent quitte. Je ne suis pas absolument sûr que cela se produise lorsque le parent se bloque, mais je pense que oui. Certes, pour les cas non-crash, cela devrait fonctionner correctement.

Notez que vous devrez peut-être lire beaucoup de petits caractères - y compris la section Définitions de base (définitions), ainsi que les informations sur les services système pour exit()et setsid()et setpgrp()- pour obtenir l'image complète. (Moi aussi!)


3
Hmm. La documentation est vague et contradictoire à ce sujet, mais il semble que le processus parent doit être le processus principal de la session, pas seulement le groupe de processus. Le processus principal de la session était toujours la connexion, et obtenir que mon processus prenne le relais en tant que processus principal d'une nouvelle session dépassait mes capacités pour le moment.
Schof

2
SIGHUP n'est effectivement envoyé aux processus enfants que si le processus sortant est un shell de connexion. opengroup.org/onlinepubs/009695399/functions/exit.html "La fin d'un processus ne met pas fin directement à ses enfants. L'envoi d'un signal SIGHUP comme décrit ci-dessous met indirectement fin aux enfants / dans certaines circonstances /."
Rob K

1
@Rob: correct - c'est ce que dit la citation que j'ai donnée: que ce n'est que dans certaines circonstances que le processus enfant obtient un SIGHUP. Et c'est strictement une simplification excessive de dire que ce n'est qu'un shell de connexion qui envoie SIGHUP, bien que ce soit le cas le plus courant. Si un processus avec plusieurs enfants se définit comme le processus de contrôle pour lui-même et ses enfants, le SIGHUP sera (commodément) envoyé à ses enfants lorsque le maître décède. OTOH, les processus posent rarement autant de problèmes - donc j'ai plus de choix que de soulever un gros problème.
Jonathan Leffler

2
Je me suis amusé avec pendant quelques heures et je n'ai pas pu le faire fonctionner. Cela aurait bien géré un cas où j'ai un démon avec des enfants qui doivent tous mourir à la sortie du parent.
Rob K

1

Si vous envoyez un signal au pid 0, en utilisant par exemple

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

ce signal est envoyé à l'ensemble du groupe de traitement, tuant ainsi efficacement l'enfant.

Vous pouvez le tester facilement avec quelque chose comme:

(cat && kill 0) | python

Si vous appuyez ensuite sur ^ D, vous verrez le texte "Terminated"comme une indication que l'interpréteur Python a bien été tué, au lieu d'être simplement quitté à cause de la fermeture de stdin.


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" imprime joyeusement 4 au lieu de Terminé
Kamil Szot

1

Dans le cas où cela est pertinent pour quelqu'un d'autre, lorsque je génère des instances JVM dans des processus enfants bifurqués à partir de C ++, la seule façon dont je pouvais obtenir que les instances JVM se terminent correctement après la fin du processus parent était de faire ce qui suit. J'espère que quelqu'un pourra fournir des commentaires dans les commentaires si ce n'était pas la meilleure façon de le faire.

1) Appelez prctl(PR_SET_PDEATHSIG, SIGHUP)le processus enfant forké comme suggéré avant de lancer l'application Java via execv, et

2) Ajoutez un hook d'arrêt à l'application Java qui interroge jusqu'à ce que son PID parent soit égal à 1, puis faites un effort Runtime.getRuntime().halt(0). L'interrogation se fait en lançant un shell séparé qui exécute la pscommande (Voir: Comment trouver mon PID en Java ou JRuby sous Linux? ).

EDIT 130118:

Il semble que ce n'était pas une solution solide. Je lutte toujours un peu pour comprendre les nuances de ce qui se passe, mais je recevais parfois des processus JVM orphelins lors de l'exécution de ces applications dans des sessions écran / SSH.

Au lieu d'interroger le PPID dans l'application Java, j'ai simplement demandé au hook d'arrêt d'effectuer un nettoyage suivi d'un arrêt brutal comme ci-dessus. Ensuite, j'ai veillé à invoquer waitpiddans l'application parent C ++ sur le processus enfant généré lorsqu'il était temps de tout terminer. Cela semble être une solution plus robuste, car le processus enfant s'assure qu'il se termine, tandis que le parent utilise les références existantes pour s'assurer que ses enfants se terminent. Comparez cela à la solution précédente qui avait mis fin au processus parent chaque fois qu'il le souhaitait, et avait demandé aux enfants de déterminer s'ils étaient orphelins avant de terminer.


1
L' PID equals 1attente n'est pas valide. Le nouveau parent pourrait être un autre PID. Vous devez vérifier s'il passe du parent d'origine (getpid () avant le fork ()) au nouveau parent (getppid () dans l'enfant différent de getpid () lorsqu'il est appelé avant le fork ()).
Alexis Wilke

1

Une autre façon de faire cela qui est spécifique à Linux est de créer le parent dans un nouvel espace de noms PID. Ce sera alors PID 1 dans cet espace de noms, et quand il le quittera, tous ses enfants seront immédiatement tués avec SIGKILL.

Malheureusement, pour créer un nouvel espace de noms PID, vous devez avoir CAP_SYS_ADMIN. Mais, cette méthode est très efficace et ne nécessite aucun changement réel pour le parent ou les enfants au-delà du lancement initial du parent.

Voir clone (2) , pid_namespaces (7) et unshare (2) .


J'ai besoin de modifier d'une autre manière. Il est possible d'utiliser prctl pour faire en sorte qu'un processus agisse comme le processus init pour tous ses enfants, petits-enfants et arrière-petits-enfants, etc ...
Omnifarious le

0

Si le parent décède, le PPID des orphelins passe à 1 - il vous suffit de vérifier votre propre PPID. D'une certaine manière, c'est un sondage, mentionné ci-dessus. voici une pièce de coquille pour ça:

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

J'ai trouvé 2 solutions, toutes deux pas parfaites.

1.Tuez tous les enfants en les tuant (-pid) lorsqu'ils reçoivent le signal SIGTERM.
Évidemment, cette solution ne peut pas gérer "kill -9", mais elle fonctionne dans la plupart des cas et est très simple car elle n'a pas besoin de se souvenir de tous les processus enfants.


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

De la même manière, vous pouvez installer le gestionnaire 'exit' comme ci-dessus si vous appelez process.exit quelque part. Remarque: Ctrl + C et un crash soudain ont été automatiquement traités par OS pour tuer le groupe de processus, donc plus ici.

2.Utilisez chjj / pty.js pour lancer votre processus avec le terminal de contrôle connecté.
Lorsque vous tuez le processus en cours de toute façon même par -9, tous les processus enfants seront également automatiquement tués (par le système d'exploitation?). Je suppose que parce que le processus actuel tient un autre côté du terminal, donc si le processus actuel meurt, le processus enfant obtiendra SIGPIPE donc meurt.


    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

J'ai réussi à faire une solution portable, sans polling avec 3 processus en abusant du contrôle du terminal et des sessions. C'est de la masturbation mentale, mais ça marche.

L'astuce est:

  • le processus A est démarré
  • le processus A crée un tuyau P (et ne le lit jamais)
  • le processus A se transforme en processus B
  • le processus B crée une nouvelle session
  • le processus B alloue un terminal virtuel pour cette nouvelle session
  • le processus B installe le gestionnaire SIGCHLD pour qu'il meure à la sortie de l'enfant
  • le processus B définit un gestionnaire SIGPIPE
  • le processus B se transforme en processus C
  • le processus C fait tout ce dont il a besoin (par exemple, exec () s le binaire non modifié ou exécute la logique)
  • le processus B écrit dans le tuyau P (et bloque de cette façon)
  • le processus A attend le processus B et se termine lorsqu'il meurt

De cette façon:

  • si le processus A meurt: le processus B obtient un SIGPIPE et meurt
  • si le processus B meurt: le processus A attend () revient et meurt, le processus C obtient un SIGHUP (car lorsque le chef de session d'une session avec un terminal attaché meurt, tous les processus du groupe de processus de premier plan obtiennent un SIGHUP)
  • si le processus C meurt: le processus B obtient un SIGCHLD et meurt, donc le processus A meurt

Lacunes:

  • le processus C ne peut pas gérer SIGHUP
  • le processus C sera exécuté dans une session différente
  • le processus C ne peut pas utiliser l'API de session / groupe de processus car cela rompra la configuration fragile
  • créer un terminal pour chacune de ces opérations n'est pas la meilleure idée qui soit

0

Même si 7 ans se sont écoulés, je viens de rencontrer ce problème car j'exécute l'application SpringBoot qui doit démarrer webpack-dev-server pendant le développement et doit le tuer lorsque le processus backend s'arrête.

J'essaie d'utiliser Runtime.getRuntime().addShutdownHookmais cela a fonctionné sur Windows 10 mais pas sur Windows 7.

Je l'ai changé pour utiliser un thread dédié qui attend la fin du processus ou InterruptedExceptionqui semble fonctionner correctement sur les deux versions de 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

Historiquement, à partir d'UNIX v7, le système de processus a détecté l'orphelinité des processus en vérifiant l'ID parent d'un processus. Comme je l'ai dit, historiquement, le init(8)processus système est un processus spécial pour une seule raison: il ne peut pas mourir. Il ne peut pas mourir car l'algorithme du noyau pour gérer l'attribution d'un nouvel identifiant de processus parent dépend de ce fait. lorsqu'un processus exécute son processus, le processus est devenu orphelin avant l'appel système.exit(2) appel (au moyen d'un appel système de processus ou par une tâche externe comme lui envoyant un signal ou similaire), le noyau réaffecte à tous les enfants de ce processus l'ID du processus init en tant qu'ID de processus parent. Cela conduit au test le plus simple et au moyen le plus portable de savoir si un processus est devenu orphelin. Vérifiez simplement le résultat dugetppid(2) appel système et s'il s'agit de l'ID de processus duinit(2)

Deux problèmes émergent de cette approche qui peuvent conduire à des problèmes:

  • tout d'abord, nous avons la possibilité de changer le initprocessus en n'importe quel processus utilisateur, alors comment pouvons-nous garantir que le processus init sera toujours le parent de tous les processus orphelins? Eh bien, dans le exitcode d'appel système, il y a une vérification explicite pour voir si le processus exécutant l'appel est le processus init (le processus avec pid égal à 1) et si c'est le cas, le noyau panique (il ne devrait plus être en mesure de maintenir hiérarchie des processus), il n'est donc pas permis au processus init de passer un exit(2)appel.
  • deuxièmement, il y a une condition de concurrence dans le test de base exposé ci-dessus. L'id du processus d'initialisation est supposé être historiquement 1, mais cela n'est pas garanti par l'approche POSIX, qui déclare (comme exposé dans d'autres réponses) que seul l'ID de processus d'un système est réservé à cette fin. Presque aucune implémentation posix ne fait cela, et vous pouvez supposer dans les systèmes dérivés Unix d'origine 1que la réponse à l' getppid(2)appel système suffit pour supposer que le processus est orphelin. Une autre façon de vérifier est de faire getppid(2)juste après le fork et de comparer cette valeur avec le résultat d'un nouvel appel. Cela ne fonctionne tout simplement pas dans tous les cas, car les deux appels ne sont pas atomiques ensemble, et le processus parent peut mourir après fork(2)et avant le premier getppid(2)appel système. La parent id only changes once, when its parent does ansortie de processus (2)call, so this should be enough to check if the getppid (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) `, mais vous pouvez supposer en toute sécurité ces processus comme n'ayant aucun parent non plus (sauf lorsque vous remplacez dans un système le processus init)

-1

J'ai transmis le pid parent en utilisant l'environnement à l'enfant, puis vérifié périodiquement si / proc / $ ppid existe à partir de l'enfant.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.