Verhindern der Weitergabe von SIGINT an den übergeordneten Prozess

10

In Anbetracht eines Szenarios, in dem ein übergeordnetes Programm (könnte ein C ++ - Programm oder ein Shell-Skript sein) ein untergeordnetes Shell-Skript ausführt, wenn wir während der Ausführung des untergeordneten Shell-Skripts Strg + C (oder ein beliebiges Zeichen, das als INTR-Zeichen konfiguriert ist) drücken, Ein SIGINT wird an alle Prozesse in der Vordergrundprozessgruppe gesendet. Dies schließt den übergeordneten Prozess ein.

Quelle: POSIX.1-2008 XBD Abschnitt 11.1.9

Gibt es eine Möglichkeit, dieses Standardverhalten zu überschreiben? Dass der CHILD-Prozess allein das SIGNAL handhabt, ohne dass es sich an die Eltern weitergibt?

Referenz: Stapelüberlauf nach dem übergeordneten Prozess wird nicht abgeschlossen, wenn das untergeordnete Element unterbrochen wird (TRAP INT)

Guddu
quelle

Antworten:

11

(Inspiriert von Gilles 'Antwort)

Wenn das ISIGFlag gesetzt ist, kann das ChildSkript nur dann abrufen, wenn es SIGINTübergeordnet SIGINTist, wenn es sich in einer eigenen Prozessgruppe befindet. Dies kann mit der set -mOption erreicht werden.

Wenn Sie die -mOption im ChildShell-Skript aktivieren, wird die Jobsteuerung ausgeführt, ohne interaktiv zu sein. Dies führt dazu, dass Dinge in einer separaten Prozessgruppe ausgeführt werden, wodurch verhindert wird, dass das übergeordnete Element das empfängt, SIGINTwenn das INTRZeichen gelesen wird.

Hier ist die POSIX-Beschreibung der -mOption :

-mDiese Option wird unterstützt, wenn die Implementierung die Option User Portability Utilities unterstützt. Alle Jobs sollen in eigenen Prozessgruppen ausgeführt werden. Unmittelbar bevor die Shell nach Abschluss des Hintergrundjobs eine Eingabeaufforderung ausgibt, wird eine Nachricht, die den Beendigungsstatus des Hintergrundjobs meldet, in den Standardfehler geschrieben. Wenn ein Vordergrundjob angehalten wird, schreibt die Shell eine entsprechende Nachricht mit Standardfehler, die wie vom Jobdienstprogramm beschrieben formatiert ist. Wenn ein Job einen anderen Status als das Beenden ändert (z. B. wenn er für die Eingabe oder Ausgabe stoppt oder durch ein SIGSTOP-Signal gestoppt wird), muss die Shell unmittelbar vor dem Schreiben der nächsten Eingabeaufforderung eine ähnliche Nachricht schreiben. Diese Option ist standardmäßig für interaktive Shells aktiviert.

Die -mOption ähnelt -i, ändert jedoch das Verhalten der Shell nicht annähernd so stark wie -isie.

Beispiel:

  • das ParentSkript:

    #!/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"
  • das ChildSkript:

    #!/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"

Dies passiert, wenn Sie Control+ drücken, Cwährend Sie Childauf die Eingabe warten:

$ ./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

Beachten Sie, dass der SIGINTHandler des Elternteils niemals ausgeführt wird.

Wenn Sie Parentstattdessen lieber ändern möchten Child, können Sie dies alternativ tun:

  • das ParentSkript:

    #!/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"
  • das ChildSkript (normal; keine Notwendigkeit -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"

Alternative Ideen

  1. Ändern Sie die anderen Prozesse in der Vordergrundprozessgruppe, um sie SIGINTfür die Dauer von zu ignorieren Child. Damit wird Ihre Frage nicht beantwortet, aber Sie erhalten möglicherweise das, was Sie möchten.
  2. Ändern Childzu:
    1. Verwenden Sie stty -gdiese Option, um die aktuellen Terminaleinstellungen zu sichern.
    2. Führen Sie stty -isignicht erzeugen Signale mit den INTR, QUITund SUSPZeichen.
    3. Lesen Sie im Hintergrund den Terminaleingang und senden Sie die Signale gegebenenfalls selbst (z. B. ausführen, kill -QUIT 0wenn Control+ \gelesen wird, kill -INT $$wenn Control+ Cgelesen wird). Dies ist nicht trivial und es ist möglicherweise nicht möglich, dass dies reibungslos funktioniert, wenn das ChildSkript oder etwas, das es ausführt, interaktiv sein soll.
    4. Stellen Sie die Terminaleinstellungen vor dem Beenden wieder her (idealerweise ab einer Falle EXIT).
  3. Wie # 2 mit der Ausnahme , anstatt läuft stty -isig, Warten auf den Benutzer Hit Enteroder einem anderen Nicht-Spezialschlüssel vor der Tötung Child.
  4. Schreiben Sie Ihr eigenes setpgidDienstprogramm in C, Python, Perl usw., das Sie zum Aufrufen verwenden können setpgid(). Hier ist eine grobe C-Implementierung:

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

    Anwendungsbeispiel von 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
quelle
4

Wie in dem von POSIX zitierten Kapitel erläutert, wird SIGINT an die gesamte Prozessgruppe im Vordergrund gesendet. Um zu vermeiden, dass das übergeordnete Programm beendet wird, sorgen Sie dafür, dass es in einer eigenen Prozessgruppe ausgeführt wird.

Shells bieten keinen Zugriff setpgrpüber ein eingebautes oder syntaktisches Konstrukt, aber es gibt einen indirekten Weg, dies zu erreichen, nämlich die Shell interaktiv auszuführen. (Danke an Stéphane Gimenez für den Trick.)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'
Gilles 'SO - hör auf böse zu sein'
quelle
+1 für die clevere Idee, aber Vorsicht, dass sich das Verhalten der Shell ändert, wenn es sich um eine interaktive Shell handelt (zumindest die POSIX-Shell; ich kenne die Details von nicht ksh). Beispiele: ${ENV}Wird bezogen, wird die Shell nicht sofort beendet, wenn ein Fehler auftritt, SIGQUITund SIGTERMwird ignoriert.
Richard Hansen
1

Aus der Frage zum Stapelüberlauf, auf die Sie verwiesen haben, geht eindeutig hervor, dass das übergeordnete Element für die Verarbeitung des Signals konfiguriert werden muss.

Zweitens heißt es in der POSIX-Referenz eindeutig: "Wenn ISIG gesetzt ist, wird das INTR-Zeichen bei der Verarbeitung verworfen."

Das sind also zwei Möglichkeiten. Das dritte wäre, das Kind in einer eigenen Prozessgruppe auszuführen.

Bahamat
quelle
Vielen Dank für Ihre Antwort. Ich muss auch einen Weg finden, wie der Benutzer das Skript verlassen kann. Gibt es eine Möglichkeit, ISIG festzulegen und gleichzeitig zuzulassen, dass eine CONTRL-Tastenkombination (STRG-Q kann dies sein) das Shell-Skript verlässt, ohne Signale an das übergeordnete Element zu senden, und ebenfalls? Können Sie mir bitte auch sagen, wie ich das Kind in einer anderen Prozessgruppe als das Elternteil ausführen kann?
Guddu