it-swarm.com.de

Warum wird SIGINT nicht an den untergeordneten Prozess weitergegeben, wenn es an den übergeordneten Prozess gesendet wird?

Wie kann ich bei einem Shell-Prozess (z. B. sh) und seinem untergeordneten Prozess (z. B. cat) das Verhalten von simulieren? Ctrl+C Verwenden Sie die Prozess-ID der Shell?


Das habe ich versucht :

Ausführen von sh und dann cat:

[[email protected] ~]$ sh
sh-4.3$ cat
test
test

Senden von SIGINT an cat von einem anderen Terminal:

[[email protected] ~]$ kill -SIGINT $PID_OF_CAT

cat hat das Signal empfangen und (wie erwartet) beendet.

Das Senden des Signals an den übergeordneten Prozess scheint nicht zu funktionieren. Warum wird das Signal nicht an cat weitergegeben, wenn es an den übergeordneten Prozess sh gesendet wird?

Das funktioniert nicht:

[[email protected] ~]$ kill -SIGINT $PID_OF_SH
67
rob87

Wie CTRL+C funktioniert

Das erste ist zu verstehen, wie CTRL+C funktioniert.

Wenn Sie drücken CTRL+CIhr Terminalemulator sendet ein ETX-Zeichen (Ende des Textes/0x03).
Das TTY ist so konfiguriert, dass es beim Empfang dieses Zeichens ein SIGINT an die Vordergrundprozessgruppe des Terminals sendet. Diese Konfiguration kann mit stty -a Und intr = ^C; Angezeigt werden. Die POSIX-Spezifikation besagt, dass beim Empfang von INTR] ein SIGINT an die Vordergrundprozessgruppe dieses Terminals gesendet werden soll.

Was ist die Vordergrundprozessgruppe?

Nun stellt sich die Frage, wie Sie die Prozessgruppe im Vordergrund bestimmen. Die Vordergrundprozessgruppe ist einfach die Gruppe von Prozessen, die alle von der Tastatur erzeugten Signale empfangen (SIGTSTP, SIGINT usw.).

Der einfachste Weg, die Prozessgruppen-ID zu bestimmen, ist die Verwendung von ps:

ps ax -O tpgid

Die zweite Spalte ist die Prozessgruppen-ID.

Wie sende ich ein Signal an die Prozessgruppe?

Nachdem wir die Prozessgruppen-ID kennen, müssen wir das POSIX-Verhalten beim Senden eines Signals an die gesamte Gruppe simulieren.

Dies kann mit kill erfolgen, indem ein - Vor die Gruppen-ID gesetzt wird.
Wenn Ihre Prozessgruppen-ID beispielsweise 1234 lautet, würden Sie Folgendes verwenden:

kill -INT -1234

Simulieren CTRL+C unter Verwendung der Terminalnummer.

Das Obige behandelt also, wie man simuliert CTRL+C als manueller Prozess. Aber was ist, wenn Sie die TTY-Nummer kennen und simulieren möchten? CTRL+C für dieses Terminal?

Das wird sehr einfach.

Nehmen wir an, $tty Ist das Terminal, auf das Sie abzielen möchten (Sie können dies erhalten, indem Sie tty | sed 's#^/dev/##' Im Terminal ausführen).

kill -INT -$(ps h -t $tty -o tpgid | uniq)

Dadurch wird ein SIGINT an die Vordergrundprozessgruppe von $tty Gesendet.

92
Patrick

Wie vinc17 sagt , gibt es keinen Grund dafür. Wenn Sie eine signalerzeugende Schlüsselsequenz eingeben (z. Ctrl+C) wird das Signal an alle Prozesse gesendet, die an das Terminal angeschlossen sind (diesem zugeordnet sind). Es gibt keinen solchen Mechanismus für Signale, die von kill erzeugt werden.

Ein Befehl wie

kill -SIGINT -12345

sendet das Signal an alle Prozesse in Prozessgruppe 12345; siehe kill (1) und kill (2) . Untergeordnete Elemente einer Shell befinden sich normalerweise in der Prozessgruppe der Shell (zumindest, wenn sie nicht asynchron sind). Das Senden des Signals an das Negativ der PID der Shell kann also das tun, was Sie möchten.


Oops

Wie vinc17 weist darauf hin funktioniert dies nicht für interaktive Shells. Hier ist eine Alternative, die funktionieren könnte:

kill -SIGINT - $ (echo $ (ps -pPID_of_Shell o tpgid =))

ps -pPID_of_Shell Ruft Prozessinformationen zur Shell ab. o tpgid= Weist ps an, nur die Terminalprozessgruppen-ID ohne Header auszugeben. Wenn dies weniger als 10000 ist, zeigt ps es mit führenden Leerzeichen an. Die $(echo …) ist ein schneller Trick, um führende (und nachfolgende) Leerzeichen zu entfernen.

Ich habe dies in flüchtigen Tests auf einem Debian-Computer zum Laufen gebracht.

Die Frage enthält eine eigene Antwort. Das Senden des SIGINT an den cat -Prozess mit kill ist eine perfekte Simulation dessen, was passiert, wenn Sie drücken Ctrl+C.

Genauer gesagt sendet das Interrupt-Zeichen (standardmäßig ^C) SIGINT an jeden Prozess in der Vordergrundprozessgruppe des Terminals. Wenn Sie anstelle von cat einen komplizierteren Befehl mit mehreren Prozessen ausführen würden, müssten Sie die Prozessgruppe beenden, um den gleichen Effekt wie ^C Zu erzielen.

Wenn Sie einen externen Befehl ohne den Hintergrundoperator & Ausführen, erstellt die Shell eine neue Prozessgruppe für den Befehl und benachrichtigt das Terminal, dass sich diese Prozessgruppe jetzt im Vordergrund befindet. Die Shell befindet sich noch in einer eigenen Prozessgruppe, die nicht mehr im Vordergrund steht. Dann wartet die Shell auf das Beenden des Befehls.

Hier scheinen Sie durch ein weit verbreitetes Missverständnis zum Opfer geworden zu sein: Die Idee, dass die Shell etwas unternimmt, um die Interaktion zwischen ihren untergeordneten Prozessen und dem Terminal zu erleichtern. Das stimmt einfach nicht. Sobald die Setup-Arbeiten abgeschlossen sind (Prozesserstellung, Einstellung des Terminalmodus, Erstellung von Pipes und Umleitung anderer Dateideskriptoren sowie Ausführung des Zielprogramms), wird die Shell wartet nur. Was Sie in cat eingeben, wird nicht durch die Shell geleitet, egal ob es sich um eine normale Eingabe oder ein signalerzeugendes Sonderzeichen wie ^C Handelt. Der cat -Prozess hat über seine eigenen Dateideskriptoren direkten Zugriff auf das Terminal, und das Terminal kann Signale direkt an den cat -Prozess senden, da es sich um die Vordergrundprozessgruppe handelt. Die Shell ist aus dem Weg gegangen.

Nachdem der cat -Prozess beendet ist, wird die Shell benachrichtigt, da sie das übergeordnete Element des cat -Prozesses ist. Dann wird die Shell aktiv und stellt sich wieder in den Vordergrund.

Hier ist eine Übung, um Ihr Verständnis zu verbessern.

Führen Sie an der Shell-Eingabeaufforderung in einem neuen Terminal den folgenden Befehl aus:

exec cat

Das Schlüsselwort exec bewirkt, dass die Shell cat ausführt, ohne einen untergeordneten Prozess zu erstellen. Die Shell wird durch cat ersetzt. Die PID, die früher zur Shell gehörte, ist jetzt die PID von cat. Überprüfen Sie dies mit ps in einem anderen Terminal. Geben Sie einige zufällige Zeilen ein und stellen Sie sicher, dass cat sie Ihnen wiederholt, um zu beweisen, dass sie sich trotz fehlender Shell-Prozesse als übergeordnetes Element immer noch normal verhalten. Was passiert, wenn Sie drücken? Ctrl+C jetzt?

Antworten:

SIGINT wird an den Katzenprozess geliefert, der stirbt. Da dies der einzige Prozess auf dem Terminal war, wird die Sitzung beendet, als hätten Sie an einer Shell-Eingabeaufforderung "Beenden" gesagt. In der Tat cat was Ihre Shell für eine Weile.

12
user41515

Es gibt keinen Grund, das SIGINT an das Kind weiterzugeben. Darüber hinaus heißt es in der POSIX-Spezifikation system() : "Die Funktion system () ignoriert die Signale SIGINT und SIGQUIT und blockiert das Signal SIGCHLD, während auf das Beenden des Befehls gewartet wird."

Wenn die Shell das empfangene SIGINT weitergegeben hat, z. nach einem echten Ctrl+CDies würde bedeuten, dass der untergeordnete Prozess das Signal SIGINT zweimal empfängt, was zu unerwünschtem Verhalten führen kann.

4
vinc17

setpgid Minimales Beispiel für die POSIX C-Prozessgruppe

Mit einem minimalen ausführbaren Beispiel der zugrunde liegenden API ist es möglicherweise einfacher zu verstehen.

Dies zeigt, wie das Signal an das Kind gesendet wird, wenn das Kind seine Prozessgruppe nicht mit setpgid geändert hat.

haupt c

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

volatile sig_atomic_t is_child = 0;

void signal_handler(int sig) {
    char parent_str[] = "sigint parent\n";
    char child_str[] = "sigint child\n";
    signal(sig, signal_handler);
    if (sig == SIGINT) {
        if (is_child) {
            write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
        } else {
            write(STDOUT_FILENO, parent_str, sizeof(parent_str) - 1);
        }
    }
}

int main(int argc, char **argv) {
    pid_t pid, pgid;

    (void)argv;
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        is_child = 1;
        if (argc > 1) {
            /* Change the pgid.
             * The new one is guaranteed to be different than the previous, which was equal to the parent's,
             * because `man setpgid` says:
             * > the child has its own unique process ID, and this PID does not match
             * > the ID of any existing process group (setpgid(2)) or session.
             */
            setpgid(0, 0);
        }
        printf("child pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)getpgid(0));
        assert(kill(getppid(), SIGUSR1) == 0);
        while (1);
        exit(EXIT_SUCCESS);
    }
    /* Wait until the child sends a SIGUSR1. */
    pause();
    pgid = getpgid(0);
    printf("parent pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)pgid);
    /* man kill explains that negative first argument means to send a signal to a process group. */
    kill(-pgid, SIGINT);
    while (1);
}

GitHub Upstream .

Kompilieren mit:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -Wpedantic -o setpgid setpgid.c

Ohne setpgid ausführen

Ohne CLI-Argumente wird setpgid nicht ausgeführt:

./setpgid

Mögliches Ergebnis:

child pid, pgid = 28250, 28249
parent pid, pgid = 28249, 28249
sigint parent
sigint child

und das Programm hängt.

Wie wir sehen können, ist die pgid beider Prozesse dieselbe, da sie über fork vererbt wird.

Dann, wann immer Sie schlagen:

Ctrl + C

Es wird wieder ausgegeben:

sigint parent
sigint child

Dies zeigt, wie:

  • um mit kill(-pgid, SIGINT) ein Signal an eine gesamte Prozessgruppe zu senden
  • Strg + C auf dem Terminal sendet standardmäßig einen Kill an die gesamte Prozessgruppe

Beenden Sie das Programm, indem Sie ein anderes Signal an beide Prozesse senden, z. SIGQUIT mit Ctrl + \.

Mit setpgid ausführen

Wenn Sie mit einem Argument arbeiten, z.

./setpgid 1

dann ändert das Kind seine pgid und jetzt wird jedes Mal nur ein einziges Zeichen nur vom Elternteil gedruckt:

child pid, pgid = 16470, 16470
parent pid, pgid = 16469, 16469
sigint parent

Und jetzt, wann immer Sie schlagen:

Ctrl + C

nur der Elternteil empfängt das Signal:

sigint parent

Sie können den Elternteil weiterhin wie zuvor mit einem SIGQUIT töten:

Ctrl + \

das Kind hat jetzt jedoch eine andere PGID und empfängt dieses Signal nicht! Dies kann gesehen werden von:

ps aux | grep setpgid

Sie müssen es explizit töten mit:

kill -9 16470

Dies macht deutlich, warum Signalgruppen existieren: Andernfalls würden eine Reihe von Prozessen übrig bleiben, die ständig manuell gereinigt werden müssen.

Getestet unter Ubuntu 18.04.