it-swarm.com.de

Wie beendet Strg-C einen untergeordneten Prozess?

Ich versuche zu verstehen wie CTRL+C Beendet einen untergeordneten, aber keinen übergeordneten Prozess. Ich sehe dieses Verhalten in einigen Skript-Shells wie bash, in denen Sie einen lang laufenden Prozess starten und ihn dann durch Eingabe beenden können CTRL-C und das Steuerelement kehrt zur Shell zurück.

Können Sie erklären, wie es funktioniert und warum der übergeordnete Prozess (Shell) nicht beendet wurde?

Muss die Shell eine spezielle Behandlung durchführen? CTRL+C event und wenn ja was genau macht es

61
vitaut

Signale werden standardmäßig vom Kernel verarbeitet. Alte Unix-Systeme hatten 15 Signale; jetzt haben sie mehr. Sie können </usr/include/signal.h> Überprüfen (oder -l töten). CTRL+C ist das Signal mit dem Namen SIGINT.

Die Standardaktion zum Behandeln jedes Signals ist auch im Kernel definiert und beendet normalerweise den Prozess, der das Signal empfangen hat.

Alle Signale (außer SIGKILL) können vom Programm verarbeitet werden.

Und das macht die Shell:

  • Wenn die Shell im interaktiven Modus ausgeführt wird, verfügt sie über eine spezielle Signalbehandlung für diesen Modus.
  • Wenn Sie ein Programm ausführen, z. B. find, führt die Shell Folgendes aus:
    • forks selbst
    • und für das Kind die Standard-Signalbehandlung einstellen
    • ersetzen Sie das untergeordnete Element durch den angegebenen Befehl (z. B. durch find).
    • wenn Sie drücken CTRL+CDie übergeordnete Shell verarbeitet dieses Signal, aber das untergeordnete Shell empfängt es - mit der Standardaktion - und beendet es. (Das Kind kann auch die Signalverarbeitung implementieren.)

Sie können trap Signale auch in Ihrem Shell-Skript ...

Und Sie können die Signalverarbeitung auch für Ihre interaktive Shell festlegen. Geben Sie dies oben in Ihr Feld ein ~/.profile. (Stellen Sie sicher, dass Sie bereits angemeldet sind, und testen Sie es mit einem anderen Terminal - Sie können sich selbst aussperren.)

trap 'echo "Dont do this"' 2

Jetzt jedes Mal, wenn Sie drücken CTRL+C In Ihrer Shell wird eine Nachricht gedruckt. Vergessen Sie nicht, die Linie zu entfernen!

Bei Interesse können Sie die einfache Behandlung des alten /bin/sh - Signals im Quellcode überprüfen hier .

Oben gab es einige Fehlinformationen in den Kommentaren (jetzt gelöscht), also wenn sich jemand hier für einen sehr netten Link interessiert - wie das Signalhandling funktioniert .

62
jm666

Lesen Sie zuerst den Wikipedia-Artikel über die POSIX-Terminalschnittstelle vollständig durch.

Das Signal SIGINT wird von der Terminalleitungsdisziplin generiert und an alle Prozesse in der Vordergrundprozessgruppe des Terminals gesendet . Ihre Shell hat bereits eine neue Prozessgruppe für den von Ihnen ausgeführten Befehl (oder die Befehlspipeline) erstellt und dem Terminal mitgeteilt, dass diese Prozessgruppe die Vordergrundprozessgruppe des Terminals ist. Jede gleichzeitige Befehlspipeline verfügt über eine eigene Prozessgruppe, und die Vordergrund-Befehlspipeline ist diejenige mit der Prozessgruppe, die die Shell im Terminal als programmiert hat Vordergrundprozessgruppe des Terminals. Das Umschalten von "Jobs" zwischen Vordergrund und Hintergrund hängt (abgesehen von einigen Details) davon ab, ob die Shell dem Terminal mitteilt, welche Prozessgruppe jetzt die Vordergrundgruppe ist.

Der Shell-Prozess selbst befindet sich in einer weiteren eigenen Prozessgruppe und empfängt daher kein Signal, wenn sich eine dieser Prozessgruppen im Vordergrund befindet. So einfach ist das.

21
JdeBP

Das Terminal sendet das INT-Signal (Interrupt) an den Prozess, der aktuell an das Terminal angeschlossen ist. Das Programm empfängt es dann und kann es ignorieren oder beenden.

Es ist nicht unbedingt erforderlich, dass ein Prozess zwangsweise geschlossen wird (obwohl Sie standardmäßig abort() aufrufen müssen, wenn Sie nicht mit Sigint umgehen, aber ich glaube, das muss nachgeschlagen werden).

Natürlich ist der laufende Prozess von der Shell isoliert, von der er gestartet wurde.

Wenn Sie wollte die übergeordnete Shell gehen, starten Sie Ihr Programm mit exec:

exec ./myprogram

Auf diese Weise wird die übergeordnete Shell durch den untergeordneten Prozess ersetzt

7
sehe

setpgid POSIX C-Prozessgruppen-Minimalbeispiel

Ein minimal ausführbares Beispiel für die zugrunde liegende API könnte das Verständnis erleichtern.

Dies zeigt, wie das Signal an das untergeordnete Element gesendet wird, wenn das untergeordnete Element 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 gibt wieder aus:

sigint parent
sigint child

Dies zeigt, wie:

  • mit kill(-pgid, SIGINT) ein Signal an eine ganze Prozessgruppe 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. BEFEHL mit Ctrl + \.

Mit setpgid ausführen

Wenn Sie mit einem Argument ausführen, z.

./setpgid 1

dann ändert das Kind seine pgid, und jetzt wird jedes Mal nur ein einziges Zeichen 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 ebenfalls:

sigint parent

Sie können die Eltern weiterhin wie bisher mit einem SIGQUIT töten:

Ctrl + \

das Kind hat jetzt jedoch eine andere PGID und empfängt dieses Signal nicht! Dies ist ersichtlich aus:

ps aux | grep setpgid

Du musst es explizit töten mit:

kill -9 16470

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

Getestet unter Ubuntu 18.04.

CTRL+C ist eine Map zum kill Befehl. Wenn Sie sie drücken, sendet kill ein SIGINT-Signal, das den Vorgang unterbricht.

Kill: http://en.wikipedia.org/wiki/Kill_ (command)

SIGINT: http://en.wikipedia.org/wiki/SIGINT_ (POSIX)

1
Brice Favre