Empêcher la propagation de SIGINT vers le processus parent

10

Considérant un scénario où un programme parent (qui pourrait être un programme C ++ ou un script shell) exécute un script shell enfant, lorsque nous frappons Control + C (ou tout autre caractère configuré pour être le caractère INTR) pendant l'exécution du script shell enfant, un SIGINT est envoyé à tous les processus du groupe de processus de premier plan. Cela inclut le processus parent.

Source: POSIX.1-2008 XBD section 11.1.9

Existe-t-il un moyen de remplacer ce comportement par défaut? Que le processus ENFANT gère seul le SIGNAL sans qu'il se propage au parent?

Référence: Stack Overflow Post - Le processus parent ne se termine pas lorsque l'enfant est interrompu (TRAP INT)

Guddu
la source

Réponses:

11

(Inspiré par la réponse de Gilles)

Avec l' ISIGindicateur défini, le seul moyen pour le Childscript d'obtenir SIGINTsans obtenir son parent SIGINTest qu'il se trouve dans son propre groupe de processus. Cela peut être accompli avec l' set -moption.

Si vous activez l' -moption dans le Childscript shell, il effectuera le contrôle des travaux sans être interactif. Cela entraînera son exécution dans un groupe de processus distinct, empêchant le parent de recevoir le SIGINTlorsque le INTRcaractère est lu.

Voici la description POSIX de l' -moption :

-mCette option doit être prise en charge si la mise en œuvre prend en charge l'option User Portability Utilities. Tous les travaux doivent être exécutés dans leurs propres groupes de processus. Immédiatement avant que le shell émette une invite après la fin de la tâche en arrière-plan, un message signalant l'état de sortie de la tâche en arrière-plan doit être écrit sur l'erreur standard. Si un travail de premier plan s'arrête, le shell doit écrire un message d'erreur standard à cet effet, formaté comme décrit par l'utilitaire de travaux. En outre, si un travail change d'état autre que la sortie (par exemple, s'il s'arrête pour une entrée ou une sortie ou est arrêté par un signal SIGSTOP), le shell doit écrire un message similaire immédiatement avant d'écrire l'invite suivante. Cette option est activée par défaut pour les shells interactifs.

L' -moption est similaire à -i, mais elle ne modifie pas autant le comportement du shell -i.

Exemple:

  • le Parentscript:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    ./Child
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • le Childscript:

    #!/bin/sh -m
    #         ^^        
    # notice the -m option above!
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

Voici ce qui se passe lorsque vous appuyez sur Control+ Cen Childattendant l'entrée:

$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally

Remarquez comment le SIGINTgestionnaire du parent n'est jamais exécuté.

Alternativement, si vous préférez modifier Parentau lieu de Child, vous pouvez le faire:

  • le Parentscript:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    sh -m ./Child  # or 'sh -m -c ./Child' if Child isn't a shell script
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
  • le Childscript (normal; pas besoin de -m):

    #!/bin/sh
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"

Idées alternatives

  1. Modifiez les autres processus du groupe de processus de premier plan à ignorer SIGINTpendant la durée de Child. Cela ne répond pas à votre question, mais cela peut vous donner ce que vous voulez.
  2. Modifiez Childen:
    1. Utilisez stty -gpour sauvegarder les paramètres actuels du terminal.
    2. Exécuter stty -isigpour ne pas générer des signaux avec les INTR, QUITet les SUSPpersonnages.
    3. En arrière-plan, lisez l'entrée du terminal et envoyez les signaux vous-même selon le cas (par exemple, exécutez kill -QUIT 0lorsque Control+ \est lu, kill -INT $$lorsque Control+ Cest lu). Ce n'est pas anodin et il peut ne pas être possible de le faire fonctionner correctement si le Childscript ou tout ce qu'il exécute est censé être interactif.
    4. Restaurez les paramètres du terminal avant de quitter (idéalement à partir d'un piège EXIT).
  3. Identique à # 2, sauf qu'au lieu de courir stty -isig, attendez que l'utilisateur frappe Enterou une autre clé non spéciale avant de tuer Child.
  4. Écrivez votre propre setpgidutilitaire en C, Python, Perl, etc. que vous pouvez utiliser pour appeler setpgid(). Voici une implémentation C brute:

    #define _XOPEN_SOURCE 700
    #include <unistd.h>
    #include <signal.h>
    
    int
    main(int argc, char *argv[])
    {
        // todo: add error checking
        void (*backup)(int);
        setpgid(0, 0);
        backup = signal(SIGTTOU, SIG_IGN);
        tcsetpgrp(0, getpid());
        signal(SIGTTOU, backup);
        execvp(argv[1], argv + 1);
        return 1;
    }

    Exemple d'utilisation de Child:

    #!/bin/sh
    
    [ "${DID_SETPGID}" = true ] || {
        # restart self after calling setpgid(0, 0)
        exec env DID_SETPGID=true setpgid "$0" "$@"
        # exec failed if control reached this point
        exit 1
    }
    unset DID_SETPGID
    
    # do stuff here
Richard Hansen
la source
4

Comme l'explique le chapitre que vous citez de POSIX, SIGINT est envoyé à l'ensemble du groupe de processus de premier plan. Par conséquent, pour éviter de tuer le parent du programme, faites en sorte qu'il s'exécute dans son propre groupe de processus.

Les shells ne donnent pas accès setpgrpvia une construction intégrée ou syntaxique, mais il existe un moyen indirect de le réaliser, qui est d'exécuter le shell de manière interactive. (Merci à Stéphane Gimenez pour l'astuce.)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'
Gilles 'SO- arrête d'être méchant'
la source
+1 pour l'idée intelligente, bien que sachez que le comportement du shell change lorsqu'il s'agit d'un shell interactif (du moins le shell POSIX le fait; je ne connais pas les détails de ksh). Exemples: ${ENV}provient, le shell ne se fermera pas immédiatement lorsqu'il rencontre une erreur, SIGQUITet SIGTERMest ignoré.
Richard Hansen
1

Eh bien, à partir de la question Stack Overflow que vous avez référencée, elle indique clairement que le parent doit être configuré pour gérer le signal.

Deuxièmement, la référence POSIX indique clairement que "Si ISIG est défini, le caractère INTR doit être supprimé lors du traitement".

Voilà donc deux options. La troisième serait d'exécuter l'enfant dans son propre groupe de processus.

bahamat
la source
Merci pour votre réponse. J'ai également besoin de trouver un moyen pour que l'utilisateur quitte le script. Existe-t-il un moyen de définir ISIG et en même temps permettre à une combinaison de touches CONTRL (CTRL-Q peut être) de quitter le script shell sans envoyer de signaux au parent et de même? Pouvez-vous également me dire comment exécuter l'enfant dans un groupe de processus différent de son parent?
Guddu