Wie funktionieren Signale intern?

31

Im Allgemeinen zu töten Prozesse erzeugen wir Signale wie SIGKILL, SIGTSTPusw.

Aber wie ist bekannt, wer dieses bestimmte Signal bestellt hat, wer es an einen bestimmten Prozess gesendet hat und wie Signale im Allgemeinen ihre Operationen ausführen? Wie funktionieren Signale intern?

Varun Chhangani
quelle
Die Frage ist etwas schwer zu verstehen. Ich entschuldige mich und meine keine Respektlosigkeit. Möchten Sie wissen, wer möglicherweise einen Befehl ausgeführt hat, der einen Prozess beendet hat, oder möchten Sie mehr über SIGKILL und SIGSTP erfahren?
Pullsumo
@mistermister Ich möchte wissen, wer möglicherweise einen Befehl ausgeführt hat, der einen Prozess beendet hat, und wie?
Varun Chhangani

Antworten:

35

Die 50.000 Fuß Ansicht ist, dass:

  1. Ein Signal wird entweder vom Kernel intern generiert (zum Beispiel, SIGSEGVwenn auf eine ungültige Adresse zugegriffen wird oder SIGQUITwenn Sie Ctrl+ drücken \) oder von einem Programm, das den killSyscall verwendet (oder von mehreren verwandten).

  2. Wenn es sich um einen der Systemaufrufe handelt, bestätigt der Kernel, dass der aufrufende Prozess über ausreichende Berechtigungen zum Senden des Signals verfügt. Wenn nicht, wird ein Fehler zurückgegeben (und das Signal tritt nicht auf).

  3. Wenn es sich um eines von zwei speziellen Signalen handelt, reagiert der Kernel bedingungslos darauf, ohne dass Eingaben vom Zielprozess erforderlich sind. Die beiden Spezialsignale sind SIGKILL und SIGSTOP. Alle folgenden Informationen zu Standardaktionen, Blockierungssignalen usw. sind für diese beiden irrelevant.

  4. Als nächstes findet der Kernel heraus, was mit dem Signal zu tun hat:

    1. Für jeden Prozess ist jedem Signal eine Aktion zugeordnet. Es gibt eine Reihe von Standardwerten ist, und Programme festlegen können unterschiedliche Verwendung sigaction, signalusw. Dazu gehören Dinge wie „ignorieren sie vollständig“, „den Prozess töten“, „den Prozess mit einem Core - Dump töten“, „den Prozess stoppen“, etc.

    2. Programme können auch die Übermittlung von Signalen ("gesperrt") signalweise ausschalten. Dann bleibt das Signal anstehend, bis es entsperrt wird.

    3. Programme können anfordern, dass der Kernel nicht selbst eine Aktion ausführt, sondern das Signal entweder synchron (mit sigwaitusw. oder signalfd) oder asynchron (durch Unterbrechen des Prozesses und Aufrufen einer bestimmten Funktion) an den Prozess liefert .

Es gibt eine zweite Gruppe von Signalen, die als "Echtzeitsignale" bezeichnet werden und keine spezielle Bedeutung haben. Außerdem können mehrere Signale in die Warteschlange gestellt werden (normale Signale stellen nur jeweils eines in die Warteschlange, wenn das Signal blockiert ist). Diese werden in Multithread-Programmen verwendet, damit die Threads miteinander kommunizieren können. Einige werden zum Beispiel in der POSIX-Thread-Implementierung von glibc verwendet. Sie können auch zur Kommunikation zwischen verschiedenen Prozessen verwendet werden (Sie können beispielsweise mehrere Echtzeitsignale verwenden, um ein fooctl-Programm eine Nachricht an den foo-Daemon senden zu lassen).

Versuchen Sie für eine Nicht-50.000-Fuß-Ansicht man 7 signalund auch die Kernel-interne Dokumentation (oder Quelle).

derobert
quelle
"Die zwei speziellen Signale sind SIGKILL und SIGSTOP", also was kann SIGCONT sein ...
Hauke ​​Laging
@HaukeLaging SIGCONT ist das Signal, das SIGSTOP rückgängig macht. In der Dokumentation wird es nicht als speziell aufgeführt. Ich bin mir also nicht sicher, ob ein Prozess es technisch so einstellen kann, dass es ignoriert wird. Dann können Sie es nicht fortsetzen (nur SIGKILL).
Derobert
22

Die Signalimplementierung ist sehr komplex und kernelspezifisch. Mit anderen Worten, verschiedene Kernel implementieren Signale unterschiedlich. Eine vereinfachte Erklärung lautet wie folgt:

Die CPU, basierend auf einem speziellen Registerwert, hat eine Adresse im Speicher, wo sie erwartet, eine "Interrupt-Deskriptor-Tabelle" zu finden, die tatsächlich eine Vektortabelle ist. Für jede mögliche Ausnahme gibt es einen Vektor, z. B. Division durch Null oder Trap wie INT 3 (Debug). Wenn die CPU auf die Ausnahme stößt, speichert sie die Flags und den aktuellen Befehlszeiger auf dem Stapel und springt dann zu der durch den relevanten Vektor angegebenen Adresse. In Linux zeigt dieser Vektor immer in den Kernel, in dem es einen Exception-Handler gibt. Die CPU ist nun fertig und der Linux-Kernel übernimmt.

Beachten Sie, dass Sie auch eine Ausnahme von der Software auslösen können. Zum Beispiel drückt der Benutzer CTRL- C, dann geht dieser Aufruf an den Kernel , die ihren eigenen Exception - Handler aufruft. Im Allgemeinen gibt es verschiedene Möglichkeiten, auf den Handler zuzugreifen, aber unabhängig davon, dass das Gleiche passiert: Der Kontext wird auf dem Stapel gespeichert, und der Ausnahmehandler des Kernels wird angesprungen.

Der Ausnahmehandler entscheidet dann, welcher Thread das Signal empfangen soll. Wenn so etwas wie eine Division durch Null auftritt, ist es einfach: Der Thread, der die Ausnahme verursacht hat, erhält das Signal. Bei anderen Signaltypen kann die Entscheidung jedoch sehr komplex sein, und in einigen ungewöhnlichen Fällen kann ein mehr oder weniger zufälliger Thread auftreten bekomme das Signal.

Um das Signal zu senden, was der Kernel tut, muss zuerst ein Wert eingestellt werden, der den Signaltyp SIGHUPoder was auch immer angibt . Dies ist nur eine ganze Zahl. Jeder Prozess hat einen Speicherbereich "Pending Signal", in dem dieser Wert gespeichert wird. Dann erstellt der Kernel eine Datenstruktur mit den Signalinformationen. Diese Struktur enthält ein Signal "Disposition", das standardmäßig, ignoriert oder verarbeitet werden kann. Der Kernel ruft dann seine eigene Funktion auf do_signal(). Die nächste Phase beginnt.

do_signal()entscheidet zunächst, ob es das Signal verarbeiten soll. Wenn es sich zum Beispiel um eine Tötung handelt , wird do_signal()der Prozess am Ende der Geschichte abgebrochen. Ansonsten sieht es nach der Disposition aus. Wenn die Disposition Standard ist, wird do_signal()das Signal gemäß einer vom Signal abhängigen Standardrichtlinie verarbeitet. Wenn die Disposition Handle ist, bedeutet dies, dass es im Anwenderprogramm eine Funktion gibt, die für die Behandlung des betreffenden Signals ausgelegt ist, und der Zeiger auf diese Funktion befindet sich in der oben genannten Datenstruktur. In diesem Fall ruft do_signal () eine andere Kernelfunktion auf,handle_signal(), der dann den Vorgang durchläuft, in den Benutzermodus zurückzukehren und diese Funktion aufzurufen. Die Details dieser Übergabe sind äußerst komplex. Dieser Code in Ihrem Programm wird normalerweise automatisch mit Ihrem Programm verknüpft, wenn Sie die Funktionen in verwenden signal.h.

Durch geeignete Prüfung des anstehenden Signalwerts kann der Kernel feststellen, ob der Prozess alle Signale verarbeitet. Ist dies nicht der Fall, werden geeignete Maßnahmen ergriffen, die den Prozess abhängig vom Signal möglicherweise in den Ruhezustand versetzen oder beenden oder andere Maßnahmen ergreifen.

Tyler Durden
quelle
15

Obwohl diese Frage beantwortet wurde, möchte ich einen detaillierten Ablauf von Ereignissen im Linux-Kernel veröffentlichen.
Dies wurde vollständig von Linux-Posts kopiert : Linux-Signale - Interna im Blog „Linux posts“ unter sklinuxblog.blogspot.in.

Signal User Space C-Programm

Beginnen wir mit dem Schreiben eines einfachen C-Programms für den Signalbenutzerraum:

#include<signal.h>
#include<stdio.h>

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

Dieser Code weist einen neuen Handler für das SIGINT-Signal zu. SIGINT kann mit der Tastenkombination Ctrl+ an den laufenden Prozess gesendet werden C. Wenn Ctrl+ Cgedrückt wird, wird das asynchrone Signal SIGINT an die Task gesendet. Dies entspricht auch dem Senden des kill -INT <pid>Befehls in einem anderen Terminal.

Wenn Sie eine kill -l(das ist ein Kleinbuchstabe L, der für "Liste" steht) ausführen, werden Sie die verschiedenen Signale kennen, die an einen laufenden Prozess gesendet werden können.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

Folgende Tastenkombination kann auch zum Senden bestimmter Signale verwendet werden:

  • Ctrl+ C- Sendet SIGINT, dessen Standardaktion darin besteht, die Anwendung zu beenden.
  • Ctrl+ \  - Sendet SIGQUIT, dessen Standardaktion darin besteht, den Anwendungs-Dump-Core zu beenden.
  • Ctrl+ Z- Sendet SIGSTOP, das das Programm anhält.

Wenn Sie das obige C-Programm kompilieren und ausführen, erhalten Sie die folgende Ausgabe:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Auch mit Ctrl+ Coder wird kill -2 <pid>der Vorgang nicht beendet. Stattdessen führt es den Signalhandler aus und kehrt zurück.

Wie das Signal an den Prozess gesendet wird

Wenn wir die Interna des Signals sehen, das an einen Prozess gesendet wird, und Jprobe mit dump_stack auf __send_signalfunction setzen, sehen wir folgenden Aufruf-Trace:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

Die Hauptfunktion zum Senden des Signals lautet also:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Jetzt ist alles eingerichtet und die notwendigen Änderungen werden task_structam Prozess vorgenommen.

Umgang mit Signal

Das Signal wird von einem Prozess geprüft / verarbeitet, wenn es vom Systemaufruf zurückkommt oder wenn die Rückkehr vom Interrupt erfolgt ist. Die Rückkehr vom Systemaufruf ist in Datei vorhanden entry_64.S.

Es wird die Funktion int_signal aufgerufen, von entry_64.Sder aus die Funktion aufgerufen wird do_notify_resume().

Lassen Sie uns die Funktion überprüfen do_notify_resume(). Diese Funktion prüft, ob wir das TIF_SIGPENDINGFlag gesetzt haben in task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

SYSTEM ruft und signalisiert

"Langsame" Systemaufrufe, z. B. Blockieren von Lese- / Schreibvorgängen, Versetzen von Prozessen in den Wartezustand: TASK_INTERRUPTIBLEoder TASK_UNINTERRUPTIBLE.

Ein Task im Status TASK_INTERRUPTIBLEwird TASK_RUNNINGdurch ein Signal in den Status geändert . TASK_RUNNINGbedeutet, dass ein Prozess geplant werden kann.

Wenn es ausgeführt wird, wird sein Signalhandler ausgeführt, bevor der langsame Systemaufruf abgeschlossen ist. Der syscallwird standardmäßig nicht abgeschlossen.

Wenn das SA_RESTARTFlag gesetzt ist, syscallwird es nach Abschluss des Signalhandlers neu gestartet.

Verweise

K_K
quelle
Vielen Dank, dass Sie sich bemüht haben, einen Beitrag zur Website zu leisten. (1) Wenn Sie jedoch Material von einer anderen Website kopieren (Wort für Wort, Buchstabe für Buchstabe, einschließlich der Grammatik- und Satzzeichenfehler), sollten Sie dies bestätigen also viel klarer. Es reicht nicht aus, die Quelle als „Referenz“ aufzuführen, obwohl dies erforderlich ist. Sofern Sie nicht der Autor des Blogs sind (K_K = sk?), Müssen Sie in diesem Fall keinen Link zu diesem Blog erstellen. Wenn Sie dies jedoch tun, müssen Sie angeben, dass es Ihr Blog ist. … (Fortsetzung)
G-Man sagt, dass Monica am
(Fortsetzung)… (2) Ihre Quelle (das Blog, aus dem Sie kopiert haben) ist nicht sehr gut. Es ist vier Jahre her, seit die Frage gestellt wurde; Finden Sie keine bessere Referenz zum Kopieren? (Wenn Sie der ursprüngliche Autor sind, tut mir leid.) Zusätzlich zu den oben genannten Grammatik- und Interpunktionsfehlern (und im Allgemeinen schlampiger Formulierung und schlechter Formatierung) ist es falsch. (2a) Strg + Z sendet SIGTSTP, nicht SIGSTOP. (SIGTSTP, wie SIGTERM kann, gefangen werden; SIGSTOP, wie SIGKILL kann nicht.) ... (Fortsetzung)
G-Man sagt "wieder einzusetzen Monica
(Fortsetzung)… (2b) Die Shell sendet kein Strg + C-Signal. Die Shell hat keine Rolle beim Senden von Signalen (außer wenn der Benutzer den killBefehl verwendet, der eine eingebaute Shell ist). (2c) Während Semikolons nach dem Schließen }einer Funktion streng genommen keine Fehler darstellen, sind sie unnötig und höchst unorthodox. (3) Selbst wenn alles richtig wäre, wäre es keine sehr gute Antwort auf die Frage. (3a) Die Frage ist zwar etwas unklar, scheint sich aber darauf zu konzentrieren, wie Akteure (Benutzer und Prozesse ) Signale initiieren (dh senden ). … (Fortsetzung)
G-Man sagt, dass Monica am
(Fortsetzung)… Die Antwort scheint sich auf Signale zu konzentrieren, die vom Kernel generiert wurden (insbesondere auf Signale, die über die Tastatur generiert wurden), und darauf, wie der Empfängerprozess auf Signale reagiert. (3b) Die Frage scheint auf der Ebene von "Jemand hat meinen Prozess getötet - wer hat es getan und wie?" Zu stehen Kernel-Datenstrukturen. IMO, das ist ein unangemessen niedriges Niveau - zumal es keine Referenzen gibt, bei denen ein Leser mehr über diese internen Abläufe erfahren könnte.
G-Man sagt, dass Monica am
1
Es ist mein eigener Blog. Meine eigenen Spuren. Das ist es, was ich will. Jeder soll solch einen detaillierten Fluss kennen. In der Luft zu sprechen ist sinnlos Kanal .. das ist Kernel interne Antwort nicht Grammatik Interna.
K_K