Dieser Artikel fasst meine Forschungen zu den Grundlagen und Mechanismen von Linux-Signalen (Implementierung im Kernel) zusammen.
Normalerweise benutze ich Signale, aber ich habe nicht verstanden, wie sie funktionieren, deshalb habe ich sie als Thema für das Studium des Kernels nachgeschlagen. Es war komplizierter und umfangreicher als ich es mir vorgestellt hatte, daher gab es einige Teile, die ich nicht ausschreiben konnte (Teile, die ich nicht untersuchen konnte), aber ich glaube, ich habe den allgemeinen Fluss (Mechanismus) verstanden.
Dieser Artikel besteht hauptsächlich aus "■ Basic Edition" und "■ Kernel Edition (v5.5)". Ich denke, dass es notwendig ist, die Grundlagen zu kennen, um den Mechanismus zu verstehen, also ist er so aufgebaut. Denjenigen, die die Grundlagen der Buchstufe verstehen, wird empfohlen, aus "[■ Kernel Edition (v5.5)](# -Kernel Edition-v55)" zu lesen.
Lassen Sie uns zunächst einen kurzen Blick auf die Grundlagen von Signalen werfen.
Die detaillierte Verwendung und Fehlerbehandlung der Befehle und der API (C-Sprache), die in den Beispielen angezeigt werden, entfällt. Für Details verwenden Sie bitte die Informationen in Mann und Referenzen.
Eine Funktion des Kernels, die Prozesse und Prozessgruppen über verschiedene Ereignisse informiert (Software-Interrupt). Ereignisbenachrichtigungen können von verschiedenen Orten (Ihren / anderen Prozessen, Kernel) gesendet werden, einschließlich:
Das Signal wird im folgenden Ablauf verarbeitet. Wir werden uns die Details später ansehen.
Zur besseren Vorstellung sind hier drei Beispiele für die Signalverwendung aus Sicht des Benutzers aufgeführt.
Der Befehl kill sendet ein Signal (SIGTERM), um den Schlafprozess zu beenden.
$ ps
PID TTY TIME CMD
2662 pts/0 00:00:00 bash
2679 pts/0 00:00:00 sleep
2685 pts/0 00:00:00 ps
$ kill 2679
[1]+ Terminated sleep 1000
$ ps
PID TTY TIME CMD
2662 pts/0 00:00:00 bash
2696 pts/0 00:00:00 ps
Die Interrupt-Taste (Strg + C) sendet ein Signal (SIGINT), um den Endlosschleifenbefehl (Prozessgruppe) zu beenden.
$ while :; do sleep 1; echo "Stop me !"; done
Stop me !
Stop me !
Stop me !
^C
Sie können auch ein Signal (SIGTERM) in Ihrem eigenen Programm (send_sig.c) senden, um den Schlafprozess wie folgt zu beenden:
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = atoi(argv[1]);
kill(pid, SIGTERM);
return 0;
}
Kompilierungs- und Ausführungsbeispiel
$ ps
PID TTY TIME CMD
30285 pts/0 00:00:00 bash
30597 pts/0 00:00:00 sleep
30631 pts/0 00:00:00 ps
$ gcc -o send_sig send_sig.c
$ ./send_sig 30597
[1]+ Terminated sleep 1000
$ ps
PID TTY TIME CMD
30285 pts/0 00:00:00 bash
30663 pts/0 00:00:00 ps
Die Signale werden entsprechend ihrer beabsichtigten Verwendung nummeriert und benannt.
$ 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
Einige der Signalnummern sind jedoch architekturabhängig und können vom obigen Beispiel (x86_64) [^ 7signal] abweichen.
[^ 7 Signal]: Siehe Mann 7 Signal
für Details.
Signale können grob in zwei Typen unterteilt werden: Standardsignale und Echtzeitsignale.
Der grobe Unterschied ist
--Standardsignal ... Traditionell verwendete Signale wie SIGKILL (1 bis 31)
ist. Die etwas subtileren Unterschiede sind:
Standardsignal | Echtzeitsignal | |
---|---|---|
Neu/Alt | Alt | Neu |
Signalname | Verschiedene für jedes Signal | SIGRTMIN(+n), SIGRTMAX(-n) |
Signalnummer | 1 〜 31 | 32 〜 64 |
Standardbetrieb(Details werden später beschrieben) | Verschiedene für jedes Signal | Ende des Prozesses(Alle Signale) |
Verwenden | Verschiedene für jedes Signal | Benutzerdefinierte(Alle Signale) |
Mehrere gleiche Signale Verhalten zum Zeitpunkt des Empfangs |
Erhalte nur einen | Erhalte alle |
Mehrere gleiche Signale Bestellung beim Versand |
Keine Regelung | In der Reihenfolge angekommen, in der sie gesendet wurden |
Mehrere verschiedene Signale Bestellung beim Versand |
Keine Regelung | In aufsteigender Reihenfolge der Signalnummer angekommen |
Dieser Artikel befasst sich nicht eingehender mit Echtzeitsignalen [^ 7signal].
Beim Empfang eines Signals gibt es drei Arten von Aktionen (Signalaktionen). Dieses Verhalten kann gegenüber der später beschriebenen Sigaction () geändert werden. ** SIGKILL und SIGSTOP können jedoch nur als Standardbetrieb verwendet werden. ** **.
Es tut nichts, wenn es ein Signal empfängt.
(Geben Sie zum Festlegen SIG_IGN für sa_handler mit sigaction () an, das später beschrieben wird.)
Wenn ein Signal empfangen wird, wird die für jedes Signal definierte Standardoperation (Term, Ign, Core, Stop, Cont, später beschrieben) ausgeführt.
(Geben Sie zum Festlegen SIG_DFL für sa_handler mit der später beschriebenen Sigaction () an (Standard).)
Wenn es ein Signal empfängt, führt es eine benutzerdefinierte Aktion aus.
(Geben Sie zum Festlegen eine benutzerdefinierte Funktion in sa_handler mit sigaction () usw. an, die später beschrieben wird.)
Die Standardoperation ist für jedes Signal definiert, und es gibt die folgenden fünf Typen.
Operationsname | Bedeutung |
---|---|
Term | Ende des Prozesses |
Ign | nichts tun(sigaction()SIG, die mit eingestellt werden kann_Gleich wie IGN) |
Core | Prozessbeendigung und Core-Dump-Generierung[^5core] |
Stop | Prozesspause(TASK_Übergang in den Zustand STOPPED) |
Cont | Setzen Sie einen angehaltenen Prozess fort(TASK_Rückkehr aus dem Zustand STOPPED) |
Dies gilt jedoch nur für Standardsignale. Begriff nur für Echtzeitsignale (siehe Abbildung unten).
[^ 5 core]: Siehe man 5 core
für Details.
Den Standardsignalen 1 bis 31 ist jeweils das Standardverhalten zugeordnet.
Signalname | Signalnummer(x86_64) | Standardbetrieb | Bedeutung |
---|---|---|---|
SIGHUP | 1 | Term | Erkennung des Hang des Steuerterminals oder Tod des Steuerprozesses |
SIGINT | 2 | Term | Unterbrechung von der Tastatur(Ctrl + C) |
SIGQUIT | 3 | Core | Beenden Sie die Tastatur(Ctrl + Q) |
SIGILL | 4 | Core | Illegale Bestellung |
SIGTRAP | 5 | Core | Trace zum Debuggen/Haltepunktsignal |
SIGABRT | 6 | Core | abort(3)Aufhängungssignal von |
SIGBUS | 7 | Core | Busfehler(Unzulässiger Speicherzugriff) |
SIGFPE | 8 | Core | Gleitkomma-Ausnahme |
SIGKILL | 9 | Term | Tötungssignal |
SIGUSR1 | 10 | Term | Benutzerdefiniertes Signal(Teil 1) |
SIGSEGV | 11 | Core | Unzulässige Speicherreferenz |
SIGUSR2 | 12 | Term | Benutzerdefiniertes Signal(Teil 2) |
SIGPIPE | 13 | Term | Rohrzerstörung(Export in ein Rohr ohne Lesegerät) [^7pipe]。 |
SIGALRM | 14 | Term | alarm(2)Timersignal von |
SIGTERM | 15 | Term | Endsignal |
SIGSTKFLT | 16 | Term | Numerischer Rechenprozessor(Coprozessor)Stapelfehler in(ungebraucht) |
SIGCHLD | 17 | Ign | Untergeordneten Prozess anhalten(Fortsetzen)Oder enden |
SIGCONT | 18 | Cont | Setzen Sie den Pausenvorgang fort |
SIGSTOP | 19 | Stop | Prozess anhalten |
SIGTSTP | 20 | Stop | Halten Sie am Steuerterminal an(Ctrl + Z) |
SIGTTIN | 21 | Stop | Hintergrundprozess-Lesesteuerungsterminal |
SIGTTOU | 22 | Stop | Der Hintergrundprozess wurde in das Steuerterminal geschrieben |
SIGURG | 23 | Ign | Out-of-Band-Daten und Notfalldaten sind im Socket vorhanden |
SIGXCPU | 24 | Core | CPU-Zeitlimit(RLIMIT_CPU)Darüber hinaus[^2setrlimit]。 |
SIGXFSZ | 25 | Core | Dateigrößenbeschränkung(RLIMIT_FSIZE)Darüber hinaus[^2setrlimit]。 |
SIGVTALRM | 26 | Term | Virtueller Timer(CPU-Zeit im Benutzermodus für den Prozess)Zeitüberschreitung |
SIGPROF | 27 | Term | Zeitüberschreitung bei der Profilerstellung |
SIGWINCH | 28 | Ign | Fenstergröße des Steuerterminals geändert |
SIGIO | 29 | Term | Asynchron I./O Ereignis |
SIGPWR | 30 | Term | Stromversorgungsfehler |
SIGSYS | 31 | Core | Einen illegalen Systemaufruf gemacht[^2seccomp]。 |
[^ 7 Pipe]: Siehe "Man 7 Pipe" für Details.
[^ 2 setrlimit]: Siehe man 2 setrlimit
für Details.
[^ 2 seccomp]: Siehe man 2 seccomp
für Details.
Verwenden Sie APIs (C) wie den Befehl trap und sigaction (), um andere Signalaktionen als ** SIGKILL ** und ** SIGSTOP ** zu ändern.
Trap ist eine Art integrierter Befehl, der in Bash integriert ist und es Ihnen ermöglicht, Signalaktionen [^ 1bash] festzulegen.
Versuchen Sie hier, (erstes Argument '') so einzustellen, dass beim Empfang von SIGINT nichts unternommen wird (ignorieren Sie das Signal).
[^ 1 Bash]: Siehe "Man 1 Bash" für Details.
Beispiel für die Einstellung des zu ignorierenden Signals
$ trap '' SIGINT
Selbst wenn SIGINT empfangen wird, reagiert es in diesem Zustand nicht, sodass die Interrupt-Taste (Strg + C) nicht wie im folgenden Ausführungsbeispiel gezeigt funktioniert.
$ while true; do sleep 1; echo "Stop me !"; done
Stop me !
Stop me !
^CStop me ! <--- Ctrl +Das Drücken von C funktioniert nicht und der Befehl wird fortgesetzt
Stop me !
Stop me !
^Z <--- Ctrl +Drücken Sie Z, um anzuhalten(SIGTSTP-Übertragung)
[1]+ Stopped sleep 1
sigaction () ist eine API (C-Sprache), mit der Sie das Verhalten von Signalen festlegen können [^ 2 sigaction].
Versuchen Sie im folgenden Beispielprogramm (set_sigh.c), die Handlerfunktion festzulegen, die ausgeführt werden soll, wenn SIGINT empfangen wird.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
static void handler(int signo)
{
/*
*Ursprünglich sollten Sie asynchrone signal-sichere Funktionen in Handlern verwenden.
*Hier ist nicht printf()、exit()Ich benutze eine Funktion wie.
*Über asynchrones Signal sicher$Siehe Mann 7 Signal.
*/
printf(" ... Caught the SIGINT (%d)\n", signo);
exit(EXIT_SUCCESS);
}
int main(void)
{
unsigned int sec = 1;
struct sigaction act;
//Handler beim Empfang von SIGINT()Zum Ausführen einstellen.
memset(&act, 0, sizeof act);
act.sa_handler = handler;
sigaction(SIGINT, &act, NULL);
// Ctrl +Geben Sie jede Sekunde eine Nachricht aus, bis sie mit C usw. endet.
for (;;) {
sleep(sec);
printf("Stop me !\n");
}
return 0;
}
Im folgenden Ausführungsbeispiel gibt handler () nach dem Empfang von SIGINT eine Nachricht aus (... hat den SIGINT (2) gefangen) und beendet das Programm.
$ gcc -o set_sigh set_sigh.c
$ ./set_sigh
Stop me !
Stop me !
Stop me !
^C ... Caught the SIGINT (2) <--- Ctrl +Drücken Sie C, um die Handlerfunktion auszuführen
Eine andere API ist signal (), das aus Gründen der Portabilität veraltet ist (alte API). Sofern Sie keinen bestimmten Grund haben, sollten Sie die Verwendung des Signals [^ 2] vermeiden.
[^ 2 Sigaction]: Siehe Man 2 Sigaction
für Details.
[^ 2 Signal]: Siehe Mann 2 Signal
für Details.
Andere Signale als ** SIGKILL und SIGSTOP ** können pro Prozess blockiert werden.
Wenn ein Prozess beispielsweise ein SIGINT empfängt, wird er normalerweise durch Standardverhalten beendet. Wenn er jedoch das SIGINT blockiert, reagiert er nicht, wie das Ignorieren eines Signals, wenn er das SIGINT empfängt.
Der Unterschied zum Ignorieren des Signals besteht darin, ob das empfangene Signal zurückgehalten werden soll oder nicht. Wenn das Signal ignoriert wird, wird das Signal nicht gehalten, aber wenn es ein Signalblock ist, kann es gehalten werden. Wenn es nicht blockiert ist, kann es daher verarbeitet werden, ohne dass das Signal erneut empfangen werden muss [^ sigign].
[^ sigign]: Es kann abgebrochen werden, auch wenn das Signal ignoriert wird, aber das im Status "Signal ignoriert" empfangene Signal wird nicht gehalten, sodass es auch nach dem Löschen nicht verarbeitet wird. Sie müssen das Ignorieren löschen und das Signal erneut empfangen.
Verwenden Sie eine API (C-Sprache) wie sigprocmask (), um den Signalblock [^ 2 sigprocmask] festzulegen.
Das folgende Beispielprogramm (block_sig.c) versucht, SIGINT zu blockieren und SIGINT 5 Sekunden später zu entsperren.
[^ 2 sigprocmask]: Siehe man 2 sigprocmask
für Details.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(void)
{
unsigned int sec = 5;
sigset_t set;
//Leeren Sie den Signalsatz und fügen Sie SIGINT hinzu
sigemptyset(&set);
sigaddset(&set, SIGINT);
//Block SIGINT(in Wartestellung)Machen
sigprocmask(SIG_BLOCK, &set, NULL);
printf("Block the SIGINT for %d sec\n", sec);
sleep(sec);
//SIGINT entsperren
printf("\nPassed %d sec, unblock the SIGINT\n", sec);
sigprocmask(SIG_UNBLOCK, &set, NULL);
printf("Done !\n");
return 0;
}
Im Ausführungsbeispiel wird SIGINT nach dem SIGINT-Block gesendet, aber Sie können sehen, dass die Operation beim Empfang von SIGNINT ausgeführt wird, nachdem der Block freigegeben wurde.
$ gcc -o block_sig block_sig.c
$ ./block_sig
Block the SIGINT for 5 sec
^C^C^C^C^C^C^C^C^C^C <--- Ctrl +Reagiert nicht, wenn C gedrückt wird
Passed 5 sec, unblock the SIGINT <---Da es danach von SIGINT beendet wird"Done !"Ist keine Ausgabe
Sie können den Status wie Signalblock (ausstehend) überprüfen und von / proc / <PID> / status
ignorieren.
$ cat /proc/29498/status
Name: bash
--- snip ---
SigQ: 0/29305 //Signal in Warteschlange zur realen UID dieses Prozesses/Obergrenze des Signals in der Warteschlange
SigPnd: 0000000000000000 //Spezifischer Prozess(Faden)Anzahl der anstehenden ausstehenden Signale
ShdPnd: 0000000000000000 //Anzahl der anstehenden Signale für die gesamte Thread-Gruppe
SigBlk: 0000000000010000 //Signal blockiert werden(Bitmaskenwert)
SigIgn: 0000000000380004 //Signal ignoriert(Bitmaskenwert)
SigCgt: 000000004b817efb //Signal wartet darauf, erfasst zu werden(Bitmaskenwert)
--- snip ---
Von diesen sind SigBlk, SigIgn und SigCgt etwas verwirrend zu lesen, da sie von einem Maskenwert namens ** Signal Set ** verwaltet werden, um mehrere Signale zusammen darzustellen. Zum Beispiel hat SigIgn (0000000000380004) die folgende Bedeutung (hexadezimal in binär konvertieren, das entsprechende Signal von Position 1 bestimmen): Mit anderen Worten bedeutet SigIgn (ignoriertes Signal) mit PID 29498 vier Signale, die 3, 20, 21, 22 entsprechen.
0000000000380004 (hex) -> 1110000000000000000100 (bit)Signal Name Nummer
||| *---------> SIGQUIT (03)
||*--------------------------> SIGTSTP (20)
|*---------------------------> SIGTTIN (21)
*----------------------------> SIGTTOU (22)
Sie können die gleichen Informationen auch mit ps s
überprüfen.
$ ps s
UID PID PENDING BLOCKED IGNORED CAUGHT STAT TTY TIME COMMAND
1000 29498 0000000000000000 0000000000010000 0000000000380004 000000004b817efb Ss pts/0 0:00 /bin/bash
1000 29517 0000000000000000 0000000000010000 0000000000380004 000000004b817efb Ss pts/1 0:00 /bin/bash
1000 29572 0000000000000000 0000000000000000 0000000000003000 00000001ef804eff S+ pts/0 0:00 vim kernel/signal.c
1000 29581 0000000000000000 0000000000000000 0000000000000000 0000000000000000 S pts/1 0:00 sleep 100
1000 29588 0000000000000000 0000000000000000 0000000000000000 00000001f3d1fef9 R+ pts/1 0:00 ps s
Von hier an sprechen wir aus einer Kernelperspektive. Werfen wir einen Blick auf den Ablauf vom Senden eines Signals bis zur Verarbeitung der Signalaktion.
Schauen wir uns zunächst die Datenstruktur an. Eine Datenstruktur ist eine Struktur, die in der Kernel-Implementierung angezeigt wird. Viele Strukturen sind an Signalen beteiligt, daher ist es hilfreich, sie zuerst zu kennen, damit Ihr Code besser verstanden wird.
Schauen wir uns als nächstes die Kernel-Implementierung [^ kernel_ver] an. Signale haben eine schrittweise Verarbeitungskonfiguration in der Reihenfolge der Signalerzeugung (erste Stufe) und Signalübertragung (zweite Stufe), wie unten gezeigt. Schauen wir uns also die einzelnen an.
--Signalerzeugung (1. Stufe) ... Aufzeichnung der Signalübertragung in der Datenstruktur des Zielprozesses (Signal halten)
[^ kernel_ver]: Wie im Titel erwähnt, verwendet die Kernel-Version v5.5, die zum Zeitpunkt der Untersuchung die neueste ist.
Signale sind wie folgt mit vielen Strukturen wie task_struct, signal_struct und soapand_struct verknüpft:
Ich denke, es ist sehr leicht, verwirrt zu werden, deshalb habe ich die Teile, die ich persönlich für jeden Zweck wichtig halte, wie folgt zusammengefasst.
Die Signalerzeugung versetzt den Prozess, der das Signal empfängt, in den Signalhalt (wartet auf die Signalübertragung), bis es durch die Signalübertragung verarbeitet wird. Dieser Zustand wird durch das Flag TIF_SIGPENDING angezeigt und in den Thread-Flags (Flags) aufgezeichnet.
Das empfangene Signal wird von der Sigqueue-Struktur verwaltet und enthält einige verwandte Informationen (* 1). Sie werden in eine ausstehende Warteschlange gestellt (an die Sigpending-Liste angehängt). Dabei wird je nach Ziel des Signals eine von zwei Warteschlangen (für einen bestimmten Prozess oder für die gesamte Thread-Gruppe) verwendet.
Auch das gehaltene Signal wird im Signal aufgezeichnet. Sie finden diesen Wert unter / proc / <PID> / status
(siehe" Signal Status Check "oben und" Bonus "siehe unten).
Für einen bestimmten Prozess
Für die gesamte Thread-Gruppe
(* 1) Einige Informationen beziehen sich auf die Mitglieder der folgenden kernel_siginfo-Struktur.
Mitglied | Bedeutung |
---|---|
si_signo | Signalnummer |
si_code | Code, der den Ursprung des Signals angibt(※2) |
si_errno | Fehlercode der Anweisung, die das Auftreten des Signals verursacht hat |
__sifilelds | Si je nach Zustand wegen Vereinigung_pid (Ziel pid)、si_uid (Ziel-UID)Ändern |
(* 2) Die folgenden Werte werden in den Code eingegeben, der die Signalquelle angibt.
Auszug aus include / uapi / asm-generic / siginfo.h
Definitionsname | Wert | Bedeutung |
---|---|---|
SI_USER | 0 | kill()、sigsend()、raise()Signalübertragung durch |
SI_KERNEL | 0x80 | Signalisierung durch den Kernel |
SI_QUEUE | -1 | sigqueue()Signalübertragung durch |
SI_TIMER | -2 | Signalübertragung über die Zeit des POSIX-Timers |
SI_MESGQ | -3 | Signalübertragung aufgrund einer Änderung des Status der POSIX-Nachrichtenwarteschlange |
SI_ASYNCIO | -4 | Asynchron I./O (AIO)Signalübertragung nach Abschluss |
SI_SIGIO | -5 | Signalübertragung durch SIGIO-Warteschlange |
SI_TKILL | -6 | tkill()、tgkill()Signalübertragung durch |
SI_DETHREAD | -7 | sent by execve() killing subsidiary threads [^si_dethread] |
SI_ASYNCNL | -60 | getaddrinfo_a()Signalübertragung durch Abschluss der Namenssuche in |
Die obigen Werte sind allen Signalen gemeinsam. Abhängig vom jeweiligen Signal kann ein anderer Wert wie folgt eingegeben werden [^ 2 sigaction].
Signalname | si_Wert zur Eingabe des Codes |
---|---|
SIGBUG | BUS_* |
SIGCHLD | CLD_* |
SIGFPE | FPE_* |
SIGILL | ILL_* |
SIGPOLL/SIGIO | POLL_* |
SIGSEGV | SEGV_* |
SIGTRAP | TRAP_* |
SIGSYS | SYS_SECCOMP |
[^ Signalsatz]: Informationen zum Signalsatz finden Sie oben unter "Signalstatusprüfung (Signalsatz)".
[^ si_dethread]: man 2 execve
Ich habe die Kernelquelle usw. überprüft, aber ich konnte keine Informationen über diesen Originaltext hinaus finden und die Bedeutung nicht gut verstehen, daher werde ich sie so beschreiben, wie sie ist.
Von sigprocmask () usw. blockierte Signalnummern werden in den Blockinformationen aufgezeichnet (blockiert, readl_blocked [^ real_blocked]). Sie finden diesen Wert unter / proc / <PID> / status
(siehe" Signal Status Check "oben und" Bonus "siehe unten).
[^ real_blocked]: real_blocked scheint gesetzt zu sein, wenn der Systemaufruf rt_sigtimedwait (), rt_sigtimedwait_time32 (), rt_sigtimedwait_time64 () verwendet wird.
Wenn Sie die Einstellungen für die Signalaktion mit sigaction () usw. ändern, werden einige Informationen (*) im Signalhandler-Deskriptor (soapand_struct) gespeichert. Die Signalhandler-Deskriptoraktion [x] ist auch die Signalnummer (_NSIG). ) Da es sich um ein Array handelt (Aktion [Signal Nummer 1]), wird es für jedes Signal eingestellt.
(*) Einige Informationen beziehen sich auf die Mitglieder der folgenden Sigaktionsstruktur.
Mitglied | Bedeutung |
---|---|
sa_flags | SA zeigt, wie Signale verwendet werden_*Flagge[^2sigaction] |
sa_handler | SIG_IGN、SIG_Arten von Signalaktionen wie DFL, Zeiger auf Signalhandler |
sa_mask | Blockieren beim Ausführen des Handlers(Empfang verbieten)Signal |
Werfen wir einen Blick auf den Ablauf vom Senden eines Signals bis zur Verarbeitung der Signalaktion (Verarbeitung, wenn kill -TERM 1234
ausgeführt wird).
Von nun an werden wir jeweils "Signalerzeugung" und "Signalübertragung" separat betrachten (der Teil "..." wird weggelassen).
Die Hauptaufgabe in dieser Phase besteht darin, in der Datenstruktur des Zielprozesses in der Datenstruktur des Zielprozesses aufzuzeichnen, dass "ein Signal gesendet wurde (auf Signalübertragung wartet)" und es zu benachrichtigen (z. B. Standardbetrieb). Signalaktionen werden durch Signalübertragung abgewickelt.
Als Zusammenfassung des folgenden Codelesens wird das Gesamtbild der Verarbeitung bei der Signalerzeugung dargestellt. Da es viele Funktionsaufrufe gibt, beginnen wir beim Lesen des Codes mit __send_signal (), der wichtigsten Funktion (Kernfunktion).
__send_signal()
Diese Funktion entscheidet, ob das übertragene Signal geliefert werden soll (prepare_signal ()) und stellt das Signal in die Warteschlange (__sigqueue_alloc (), list_add_tail ()), die die Nummer des Haltesignals aufzeichnet ( sigaddset ()). Setzen Sie für die Zustellung das Thread-Flag des Prozessdeskriptors auf TIF_SIGPENDING, um den zu liefernden Prozess zu aktivieren (complete_signal ()).
Siehe unten für Details.
1065 static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
1066 enum pid_type type, bool force)
1067 {
1068 struct sigpending *pending;
1069 struct sigqueue *q;
1070 int override_rlimit;
1071 int ret = 0, result;
....
1076 if (!prepare_signal(sig, t, force))
1077 goto ret;
Zeile 1076 prepare_signal () prüft, ob das Signal geliefert werden muss Machen. Wenn für den Signalhandler (t-> seufzen-> Aktion [sig -1] .sa.sa_handler) beispielsweise SIG_IGN festgelegt ist oder wenn das Standardverhalten ein Ignoriersignal (Ign) ist, muss es nicht geliefert werden. Springe also zum Ret-Label. Wenn das Signal blockiert ist, müssen Sie das Signal halten, der Rest auch.
Es führt auch die folgende Verarbeitung für das Stoppsignal und SIGCONT durch.
Entfernen Sie SIGCONT aus der Signalhaltewarteschlange (*).
Entfernen Sie alle Stoppsignale aus der Signalhaltewarteschlange (*) und aktivieren Sie den Thread im Status __TASK_STOPPED mit wake_up_state () (starten Sie den Prozess neu).
Wenn der Prozess (Thread-Gruppe) gerade beendet wird (SIGNAL_STOP_STOPPED), wird davon ausgegangen, dass der Prozess abgeschlossen ist, und SIGNAL_CLD_CONTINUED und SIGNAL_STOP_CONTINUED werden in den Flags des Prozessdeskriptors gesetzt. Wenn group_stop_count gezählt wird, auch wenn es nicht beendet wird, bedeutet dies, dass der Beendigungsprozess (wahrscheinlich) bereits abgeschlossen wurde. Setzen Sie daher SIGNAL_CLD_STOPPED und SIGNAL_STOP_CONTINUED in die Flags des Prozessdeskriptors.
(*) Wie unter "Datenstruktur" erläutert, gibt es zwei Arten von Signalhaltewarteschlangen: "für einen bestimmten Prozess (t-> anstehend)" und "für die gesamte Thread-Gruppe (t-> signal-> shared_pending)". es gibt. Hier entfernen wir das relevante Signal aus beiden Warteschlangen.
1078
1079 pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
Zeile 1079 bestimmt, ob die Signalhaltewarteschlange "für einen bestimmten Prozess" oder "für die gesamte Thread-Gruppe" zum Anstehen verwendet werden soll. Diesmal übergibt der Befehl kill PIDTYPE_TGID an type, sodass das Ausstehende für die gesamte Thread-Gruppe gilt.
1080 /*
1081 * Short-circuit ignored signals and support queuing
1082 * exactly one non-rt signal, so that we can get more
1083 * detailed information about the cause of the signal.
1084 */
1085 result = TRACE_SIGNAL_ALREADY_PENDING;
1086 if (legacy_queue(pending, sig))
1087 goto ret;
In Zeile 1086 Legacy_queue () ist "sig" "Standardsignal" und "bereits Signal" Wenn es sich in der Warteschlange befindet, springt es zum Ret-Label (ein Standardsignal kann mehr als ein identisches Signal empfangen, aber nur eines).
1088
1089 result = TRACE_SIGNAL_DELIVERED;
1090 /*
1091 * Skip useless siginfo allocation for SIGKILL and kernel threads.
1092 */
1093 if ((sig == SIGKILL) || (t->flags & PF_KTHREAD))
1094 goto out_set;
Wie kommentiert, springt das Signal, wenn es ein SIGKILL ** ODER ** -Ziel ist, ein Kernel-Thread (PF_KTHREAD-Flag), zum Label "out_set" und führt keine nachfolgende Registrierungsverarbeitung für die Signalhaltewarteschlange durch.
1096 /*
1097 * Real-time signals must be queued if sent by sigqueue, or
1098 * some other real-time mechanism. It is implementation
1099 * defined whether kill() does so. We attempt to do so, on
1100 * the principle of least surprise, but since kill is not
1101 * allowed to fail with EAGAIN when low on memory we just
1102 * make sure at least one signal gets delivered and don't
1103 * pass on the info struct.
1104 */
1105 if (sig < SIGRTMIN)
1106 override_rlimit = (is_si_special(info) || info->si_code >= 0);
1107 else
1108 override_rlimit = 0;
1109
Da SIGRTMIN in Zeile 1105 32 ist, verzweigt sich der Prozess in Abhängigkeit davon, ob "sig" ein Standardsignal ist oder nicht.
In der wahren Wurzel (Zeile 1106) is_si_special () ist "info" SEND_SIG_NOINFO (im Benutzermodus). Gibt true zurück, wenn der Prozess ein Signal gesendet hat) oder SEND_SIGPRIV (ein Signal wurde vom Kernel gesendet). Das rechte info-> si_code> = 0
(SI_USER oder SI_KERNEL) gibt ebenfalls true zurück.
Beachten Sie, dass diesmal der Befehl kill info-> si_code
auf SI_USER setzt, sodass override_rlimit
wahr ist.
1110 q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
1111 if (q) {
1112 list_add_tail(&q->list, &pending->list);
1113 switch ((unsigned long) info) {
1114 case (unsigned long) SEND_SIG_NOINFO:
1115 clear_siginfo(&q->info);
1116 q->info.si_signo = sig;
1117 q->info.si_errno = 0;
1118 q->info.si_code = SI_USER;
1119 q->info.si_pid = task_tgid_nr_ns(current,
1120 task_active_pid_ns(t));
1121 rcu_read_lock();
1122 q->info.si_uid =
1123 from_kuid_munged(task_cred_xxx(t, user_ns),
1124 current_uid());
1125 rcu_read_unlock();
1126 break;
1127 case (unsigned long) SEND_SIG_PRIV:
1128 clear_siginfo(&q->info);
1129 q->info.si_signo = sig;
1130 q->info.si_errno = 0;
1131 q->info.si_code = SI_KERNEL;
1132 q->info.si_pid = 0;
1133 q->info.si_uid = 0;
1134 break;
1135 default:
1136 copy_siginfo(&q->info, info);
1137 break;
1138 }
....
In Zeile 1110 hat __sigqueue_alloc () einen neuen Sigqueue-Typ "q" für die Signalverwaltung. Zuordnungsversuche. Die interne Verarbeitung dieser Funktion prüft, ob die Anzahl der vom Eigentümer (Benutzer) des Zielprozesses gehaltenen Signale die Obergrenze überschreitet, und versucht, falls nicht, zuzuweisen. Wenn override_rlimit
jedoch true ist, wird versucht, eine Zuordnung vorzunehmen, ohne die Obergrenze zu überprüfen.
Die Zeilen 1112 bis 1138 sind die Verarbeitung, wenn die Zuordnung erfolgreich ist.
In Zeile 1112 list_add_tail () wird das "q" der Signalhaltewarteschlange zugewiesen. Wird registriert (mit der Liste verbunden) und bei der Verarbeitung ab der 1113. Zeile werden die Signalinformationen gemäß dem Argument "info" in "q" gespeichert.
1157 out_set:
....
1159 sigaddset(&pending->signal, sig);
....
Fügen Sie die aktuelle Signalnummer zu dem in der Signalhaltewarteschlange eingestellten Signal hinzu (siehe "Signalstatus" und "Datenstruktur" oben).
1175 complete_signal(sig, t, type);
1176 ret:
1177 trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
1178 return ret;
1179 }
Das complete_signal () in Zeile 1175 wird später beschrieben.
Zeile 1177 trace_signal_generate () ist ein Tracepoint mit dem Namen signal_generate im Makro TRACE_EVENT. Scheint zu schaffen. Es wird verwendet (gemeldet), um Kernel-Trace-Informationen auszugeben, wie z.
kill-5371 [003] 1058202.036613: signal_generate: sig=15 errno=0 code=0 comm=sleep pid 5359 grp=1 res=0
complete_signal()
Diese Funktion wird aufgerufen, wenn Sie das übertragene Signal mit __send_signal () senden möchten.
Verwenden Sie dann diese Funktion, um den Prozess zu finden, der für die Signalübertragung bereit ist. Wenn gefunden, setzen Sie TIF_SIGPENDING in das Thread-Flag des entsprechenden Prozesses und wachen Sie auf (benachrichtigen).
Siehe unten für Details.
984 static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
985 {
986 struct signal_struct *signal = p->signal;
987 struct task_struct *t;
988
989 /*
990 * Now find a thread we can wake up to take the signal off the queue.
991 *
992 * If the main thread wants the signal, it gets first crack.
993 * Probably the least surprising to the average bear.
994 */
995 if (wants_signal(sig, p))
996 t = p;
...
Zeile 995 want_signal () ermöglicht die Signalübertragung (bereit) mit dem folgenden Muster: Suchen Sie nach einem Prozess.
Das Signal ist nicht blockiert (SIGKILL, SIGSTOP kann nicht blockiert werden) ** UND **
--Prozess wird nicht beendet (p-> Flags haben kein PF_ EXITING) ** UND **
--Signal (sig
) ist SIGKILL (Kraft ohne Berücksichtigung des Prozessstatus oder der Flag-Präsenz)
Das Signal ist nicht blockiert (SIGKILL, SIGSTOP kann nicht blockiert werden) ** UND ** --Prozess wird nicht beendet (p-> Flags haben kein PF_ EXITING) ** UND **
Der Prozessstatus (p-> Status) wird nicht angehalten (__TASK_STOPPED) oder von einem Debugger usw. gestoppt (__TASK_TRACED) ** AND ** --Process läuft auf der CPU (aktueller Prozess) ** ODER ** Prozess-Thread-Flags (p-> thread_info-> Flags) haben kein TIF_SIGPENDING
1021 /*
1022 * Found a killable thread. If the signal will be fatal,
1023 * then start taking the whole group down immediately.
1024 */
1025 if (sig_fatal(p, sig) &&
1026 !(signal->flags & SIGNAL_GROUP_EXIT) &&
1027 !sigismember(&t->real_blocked, sig) &&
1028 (sig == SIGKILL || !p->ptrace)) {
Wie Sie in den Kommentaren sehen können, suchen Sie nach Killable-Threads. Wenn alle folgenden Bedingungen erfüllt sind, wird die Verarbeitung der Zeilen 1039 bis 1042 durchgeführt.
(*) Sig_fatal () gibt in den folgenden beiden Mustern true zurück.
Signal ist Standardsignal ** UND ** --Standardverhalten ist kein Ign / Stop-Signal ** UND **
Die Signalaktion ist SIG_DFL
Das Signal ist ein Echtzeitsignal ** UND **
Die Signalaktion ist SIG_DFL
1029 /*
1030 * This signal will be fatal to the whole group.
1031 */
1032 if (!sig_kernel_coredump(sig)) {
sig_kernel_coredump () gibt true zurück, wenn das Standardverhalten kein Core-Dump-Signal (Core-Signal) ist.
1033 /*
1034 * Start a group exit and wake everybody up.
1035 * This way we don't have other threads
1036 * running and doing things after a slower
1037 * thread has the fatal signal pending.
1038 */
1039 signal->flags = SIGNAL_GROUP_EXIT;
1040 signal->group_exit_code = sig;
1041 signal->group_stop_count = 0;
1042 t = p;
1043 do {
1044 task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
1045 sigaddset(&t->pending.signal, SIGKILL);
1046 signal_wake_up(t, 1);
1047 } while_each_thread(p, t);
1048 return;
1049 }
1050 }
Nach der Verarbeitung der Zeilen 1039 bis 1042 wird die folgende Verarbeitung wiederholt, bis das Do-While die Thread-Gruppe durchsucht.
Wenn die Schleife verlassen wird, endet die Verarbeitung durch diese Funktion.
1052 /*
1053 * The signal is already in the shared-pending queue.
1054 * Tell the chosen thread to wake up and dequeue it.
1055 */
1056 signal_wake_up(t, sig == SIGKILL);
1057 return;
1058 }
Wenn Sie diesen Punkt erreichen können, verwenden Sie das Thread-Flag (t-) mit signal_wake_up (). Setzen Sie TIF_SIGPENDING in> Flags), um den Prozess im Status TASK_INTERRUPTIBLE zu aktivieren (Benachrichtigungs-Hold-Signal). Es aktiviert auch Prozesse im Status TASK_WAKEKILL, wenn das Signal SIGKILL ist.
Dies ist das Ende des "Signalerzeugungsprozesses". Als nächstes schauen wir uns "Signal Delivery" an.
Die Hauptaufgabe in dieser Phase besteht darin, Signalaktionen wie Signalhandler und Standardverhalten auszuführen. Dies ist jedoch nur möglich, wenn der Prozess ein anstehendes Signal hat (mit dem oben unter "Signalerzeugung" beschriebenen TIF_SIGPENDING-Flag).
Als Zusammenfassung des folgenden Codelesens wird das Gesamtbild der Verarbeitung bei der Signalübertragung dargestellt. Beachten Sie, dass ein Teil des Codes für die Signalübertragung von der Architektur abhängt. Hier wird jedoch der Code für x86 (unter arch / x86) betrachtet.
exit_to_usermode_loop()
Diese Funktion wird jedes Mal aufgerufen, wenn Sie vom Kernelmodus in den Benutzermodus zurückkehren (Interrupt- / Ausnahmehandler, nach Systemaufrufverarbeitung usw.), um das Thread-Flag zu überprüfen. Wenn zu diesem Zeitpunkt TIF_SIGPENDING vorhanden ist, wird do_signal () aufgerufen (Zeile 160).
Siehe unten für Details.
arch/x86/entry/common.c (L136)
136 static void exit_to_usermode_loop(struct pt_regs *regs, u32 cached_flags)
137 {
138 /*
139 * In order to return to user mode, we need to have IRQs off with
140 * none of EXIT_TO_USERMODE_LOOP_FLAGS set. Several of these flags
141 * can be set at any time on preemptible kernels if we have IRQs on,
142 * so we need to loop. Disabling preemption wouldn't help: doing the
143 * work to clear some of the flags can sleep.
144 */
145 while (true) {
146 /* We have work to do. */
147 local_irq_enable();
...
158 /* deal with pending signal delivery */
159 if (cached_flags & _TIF_SIGPENDING)
160 do_signal(regs);
...
171 /* Disable IRQs and retry */
172 local_irq_disable();
173
174 cached_flags = READ_ONCE(current_thread_info()->flags);
175
176 if (!(cached_flags & EXIT_TO_USERMODE_LOOP_FLAGS))
177 break;
178 }
179 }
do_signal()
Diese Funktion führt das Standardverhalten (get_signal ()) und die Ausführung des Signalhandlers (handle_signal ()) aus. Außerdem werden Systemaufrufe erneut ausgeführt und Signalblockinformationen wiederhergestellt, die bei Bedarf vorübergehend von einem bestimmten Systemaufruf neu geschrieben wurden.
Siehe unten für Details.
arch/x86/kernel/signal.c (L806)
806 /*
807 * Note that 'init' is a special process: it doesn't get signals it doesn't
808 * want to handle. Thus you cannot kill init even with a SIGKILL even by
809 * mistake.
810 */
811 void do_signal(struct pt_regs *regs)
812 {
813 struct ksignal ksig;
814
815 if (get_signal(&ksig)) {
816 /* Whee! Actually deliver the signal. */
817 handle_signal(&ksig, regs);
818 return;
819 }
820
Get_signal () in Zeile 815 führt Standardoperationen aus und ruft Signale aus der Warteschlange ab. Wenn das Ergebnis wahr ist (falls abgerufen), führen Sie handle_signal () aus, um den Signalhandler auszuführen. Wenn es falsch ist (nicht abgerufen werden konnte), wird die nachfolgende Verarbeitung ausgeführt (get_signal () und handle_signal () sind wichtige Funktionen, daher werden Details später beschrieben).
821 /* Did we come from a system call? */
822 if (syscall_get_nr(current, regs) >= 0) {
823 /* Restart the system call - no handlers present */
824 switch (syscall_get_error(current, regs)) {
825 case -ERESTARTNOHAND:
826 case -ERESTARTSYS:
827 case -ERESTARTNOINTR:
828 regs->ax = regs->orig_ax;
829 regs->ip -= 2;
830 break;
831
832 case -ERESTART_RESTARTBLOCK:
833 regs->ax = get_nr_restart_syscall(regs);
834 regs->ip -= 2;
835 break;
836 }
837 }
Dies ist der Prozess im Zusammenhang mit der erneuten Ausführung des Systemaufrufs (dies ist der Prozess, da der Prozess mit einem Fehler unterbrochen werden kann, wenn während der Verarbeitung des Systemaufrufs ein Signal empfangen wird).
Zeile 822 syscall_get_nr () ist das Benutzermodusregister ( Holen Sie sich die Systemrufnummer von regs
). Wenn es größer oder gleich 0 ist, dann Zeile 824 syscall_get_erro () Es erhält die Fehlernummer und der Kernel führt den Systemaufruf entsprechend der Fehlernummer erneut aus.
Die erneute Ausführung von Systemaufrufen wurde ausführlich im Artikel "Neuausführung von Linux-Systemaufrufen" beschrieben. Klicken Sie hier für Details wie die Bedeutung von regs-> ip- = 2
.
838
839 /*
840 * If there's no signal to deliver, we just put the saved sigmask
841 * back.
842 */
843 restore_saved_sigmask();
844 }
In restore_saved_sigmask () durch Systemaufrufe wie ppoll, pselect, epoll, sigsuspend Stellt die vorübergehend neu geschriebenen Signalblockinformationen (task_struct-> blockiert) aus den zuvor gespeicherten Signalblockinformationen (task_struct-> saved_sigmask) wieder her.
get_signal()
Diese Funktion ruft ein Signal aus der Signalhaltewarteschlange ab und führt als Reaktion auf das Signal Standardaktionen aus.
Siehe unten für Details.
2521 bool get_signal(struct ksignal *ksig)
2522 {
2523 struct sighand_struct *sighand = current->sighand;
2524 struct signal_struct *signal = current->signal;
2525 int signr;
....
2540 relock:
....
2542 /*
2543 * Every stopped thread goes here after wakeup. Check to see if
2544 * we should notify the parent, prepare_signal(SIGCONT) encodes
2545 * the CLD_ si_code into SIGNAL_CLD_MASK bits.
2546 */
2547 if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
2548 int why;
2549
2550 if (signal->flags & SIGNAL_CLD_CONTINUED)
2551 why = CLD_CONTINUED;
2552 else
2553 why = CLD_STOPPED;
2554
2555 signal->flags &= ~SIGNAL_CLD_MASK;
2556
2557 spin_unlock_irq(&sighand->siglock);
2558
2559 /*
2560 * Notify the parent that we're continuing. This event is
2561 * always per-process and doesn't make whole lot of sense
2562 * for ptracers, who shouldn't consume the state via
2563 * wait(2) either, but, for backward compatibility, notify
2564 * the ptracer of the group leader too unless it's gonna be
2565 * a duplicate.
2566 */
2567 read_lock(&tasklist_lock);
2568 do_notify_parent_cldstop(current, false, why);
2569
2570 if (ptrace_reparented(current->group_leader))
2571 do_notify_parent_cldstop(current->group_leader,
2572 true, why);
2573 read_unlock(&tasklist_lock);
2574
2575 goto relock;
2576 }
....
Wie im Kommentar in Zeile 2543 erwähnt, wird das Flag in Zeile 2547 (SIGNAL_CLD_MASK == SIGNAL_CLD_STOPPED oder SIGNAL_CLD_CONTINUED) verwendet, wenn SIGCONT mit prepare_signal () verarbeitet wird, das durch die Signalerzeugung (__send_signal () aufgerufen wird. Wird gesetzt. Wenn dieses Flag gesetzt ist, sendet do_notify_parent_cldstop () ein Signal (SIGCHLD) an das übergeordnete Element.
2588 for (;;) {
2589 struct k_sigaction *ka;
....
2616 /*
2617 * Signals generated by the execution of an instruction
2618 * need to be delivered before any other pending signals
2619 * so that the instruction pointer in the signal stack
2620 * frame points to the faulting instruction.
2621 */
2622 signr = dequeue_synchronous_signal(&ksig->info);
2623 if (!signr)
2624 signr = dequeue_signal(current, ¤t->blocked, &ksig->info);
2625
2626 if (!signr)
2627 break; /* will return 0 */
....
Linie 2622dequeue_synchronous_signal()SynchronisiertesSignalmit(SIG[SEGV|BUS|ILL|TRAP|FPE|SYS])AusderWarteschlange.Wennnicht,Zeile2624dequeue_signal()Entfernt das asynchrone Signal aus der Warteschlange mit. Wenn nicht, brechen Sie aus der Schleife aus und beenden Sie die Verarbeitung mit dieser Funktion.
2635 ka = &sighand->action[signr-1];
2636
2637 /* Trace actually delivered signals. */
2638 trace_signal_deliver(signr, &ksig->info, ka);
Zeile 2638 trace_signal_deliver () ist ein TRACE_EVENT-Makro mit dem Namen signal_deliver. Es scheint einen Trace-Punkt zu erstellen. Es wird verwendet (gemeldet), um Kernel-Trace-Informationen auszugeben, wie z. Wenn wir uns den ursprünglichen Kommentar ansehen, scheint es, dass das Signal geliefert wurde, als wir hier ankamen.
trace-cmd-17636 [001] 607498.455844: signal_deliver: sig=2 errno=0 code=128 sa_handler=5567b90d1540 sa_flags=1400000
Das Folgende ist die Verarbeitung in Bezug auf Signalaktionen wie Signal Ignorieren, Signalhandler und Standardbetrieb. Verzweigungen nach dem Inhalt von "ka" (Zeile 2635).
2639
2640 if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
2641 continue;
Wenn die Signalaktion in der C-Sprach-API sigaction () usw. auf Ignorieren (SIG_IGN) eingestellt ist, wird nichts unternommen.
2642 if (ka->sa.sa_handler != SIG_DFL) {
2643 /* Run the handler. */
2644 ksig->ka = *ka;
2645
2646 if (ka->sa.sa_flags & SA_ONESHOT)
2647 ka->sa.sa_handler = SIG_DFL;
2648
2649 break; /* will return non-zero "signr" value */
2650 }
Zeile 2642 setzt die Adresse von "ka" in "ksig-> ka" in Zeile 2644, wenn es sich nicht um SIG_DFL handelt (dh wenn Sie einen Signalhandler eingestellt haben). Dies wird verwendet, wenn nach dem Beenden von get_signal () mit handle_signal () auf das Register für den Benutzerprozess gesetzt wird.
SA_ONESHOT (ein Flag, das mit sigaction () gesetzt werden kann) in Zeile 2646 bedeutet, dass die Signalaktion nach Ausführung des Signalhandlers auf den Standardwert (SIG_DFL) zurückgesetzt wird. Das heißt, der Signalhandler wird nur ausgeführt, wenn zum ersten Mal ein Signal empfangen wird.
Schließlich unterbricht break die Schleife und beendet den Vorgang mit dieser Funktion. In diesem Fall sollte signr
die abgerufene Signalnummer enthalten, damit handle_signal () ausgeführt wird, das unten beschrieben wird.
2651
2652 /*
2653 * Now we are doing the default action for this signal.
2654 */
2655 if (sig_kernel_ignore(signr)) /* Default is nothing. */
2656 continue;
....
Von hier aus ist es der Prozess, der sich auf die Ausführung der Standardoperation bezieht.
Zeile 2655 sig_kernel_ignore () ignoriert das Standardverhalten von signr
(Ign) Gibt true zurück. In diesem Fall nichts tun.
2671
2672 if (sig_kernel_stop(signr)) {
2673 /*
2674 * The default action is to stop all threads in
2675 * the thread group. The job control signals
2676 * do nothing in an orphaned pgrp, but SIGSTOP
2677 * always works. Note that siglock needs to be
2678 * dropped during the call to is_orphaned_pgrp()
2679 * because of lock ordering with tasklist_lock.
2680 * This allows an intervening SIGCONT to be posted.
2681 * We need to check for that and bail out if necessary.
2682 */
2683 if (signr != SIGSTOP) {
2684 spin_unlock_irq(&sighand->siglock);
2685
2686 /* signals can be posted during this window */
2687
2688 if (is_current_pgrp_orphaned())
2689 goto relock;
2690
2691 spin_lock_irq(&sighand->siglock);
2692 }
2693
2694 if (likely(do_signal_stop(ksig->info.si_signo))) {
2695 /* It released the siglock. */
2696 goto relock;
2697 }
2698
2699 /*
2700 * We didn't actually stop, due to a race
2701 * with SIGCONT or something like that.
2702 */
2703 continue;
2704 }
In Zeile 2672 sig_kernel_stop () ist die Standardoperation von signr
die Prozesspause ( Gibt true zurück, wenn das Signal Stop ist. Wenn true, setzen Sie SIGNAL_STOP_STOPPED in den Prozessdeskriptor-Flags (Flags) in Zeile 2694 unter do_signal_stop (). Steh auf und unterbreche den Prozess (TASK_STOPPED).
Wenn das Signal jedoch nicht SIGSTOP ist und die Prozessgruppe verwaist ist (übergeordnet), wird do_signal_stop () nicht ausgeführt (pausiert nicht).
2705
2706 fatal:
2707 spin_unlock_irq(&sighand->siglock);
....
2711 /*
2712 * Anything else is fatal, maybe with a core dump.
2713 */
2714 current->flags |= PF_SIGNALED;
2715
2716 if (sig_kernel_coredump(signr)) {
2717 if (print_fatal_signals)
2718 print_fatal_signal(ksig->info.si_signo);
2719 proc_coredump_connector(current);
2720 /*
2721 * If it was able to dump core, this kills all
2722 * other threads in the group and synchronizes with
2723 * their demise. If we lost the race with another
2724 * thread getting here, it set group_exit_code
2725 * first and our do_group_exit call below will use
2726 * that value and ignore the one we pass it.
2727 */
2728 do_coredump(&ksig->info);
2729 }
....
In Zeile 2716 sig_kernel_coredump () ist das Standardverhalten von signr
Core Dump (Core). Gibt true zurück. Wenn true, setzen Sie SIGNAL_GROUP_COREDUMP in die Signalbeschreibungsflags (Flags) in Zeile 2728 do_coredump (). Führt die Verarbeitung der Core-Dump-Generierung durch.
2731 /*
2732 * Death signals, no core dump.
2733 */
2734 do_group_exit(ksig->info.si_signo);
2735 /* NOTREACHED */
2736 }
Wenn das Standardverhalten von "signr" Prozessbeendigung (Term) oder Core Dump (Core) ist, Zeile 2724 do_group_exit () Setzen Sie SIGNAL_GROUP_EXIT in den Signal-Deskriptor-Flags (Flags) in exit.c # L876), um den Prozess (Thread-Gruppe) zu beenden.
Beachten Sie, dass "}" in Zeile 2736 ein Paar von "for (;;) {" in Zeile 2588 ist. Sie können diese Schleife mit einer Unterbrechung in Zeile 2627 oder einer Unterbrechung in Zeile 2649 unterbrechen. Ersteres ist, wenn nichts aus der Signalhaltewarteschlange abgerufen werden kann, und letzteres, wenn der Signalhandler in der Signalaktion (sa_handler) gesetzt ist.
2737 spin_unlock_irq(&sighand->siglock);
2738
2739 ksig->sig = signr;
2740 return ksig->sig > 0;
2741 }
Legt fest, ob die zuletzt aus der Warteschlange abgerufene Signalnummer größer als 0 ist. Wenn es groß ist, bedeutet dies, dass das Signal abgerufen wurde und true zurückgibt und mit dem nächsten handle_signal () fortfährt.
handle_signal()
Diese Funktion versetzt den Stapelrahmen in den Benutzermodus für die erneute Ausführung von Systemaufrufen (falls erforderlich) und die Ausführung des Signalhandlers.
arch/x86/kernel/signal.c (L71)
710 static void
711 handle_signal(struct ksignal *ksig, struct pt_regs *regs)
712 {
713 bool stepping, failed;
714 struct fpu *fpu = ¤t->thread.fpu;
715
...
719 /* Are we from a system call? */
720 if (syscall_get_nr(current, regs) >= 0) {
721 /* If so, check system call restarting.. */
722 switch (syscall_get_error(current, regs)) {
723 case -ERESTART_RESTARTBLOCK:
724 case -ERESTARTNOHAND:
725 regs->ax = -EINTR;
726 break;
727
728 case -ERESTARTSYS:
729 if (!(ksig->ka.sa.sa_flags & SA_RESTART)) {
730 regs->ax = -EINTR;
731 break;
732 }
733 /* fallthrough */
734 case -ERESTARTNOINTR:
735 regs->ax = regs->orig_ax;
736 regs->ip -= 2;
737 break;
738 }
739 }
740
...
Obwohl es auch von do_signal () [^ restart_syscall] ausgegeben wurde, gibt es auch eine Verarbeitung im Zusammenhang mit der erneuten Ausführung des Systemaufrufs. Es gibt jedoch nur zwei Muster, in denen der Kernel Systemaufrufe erneut ausführt:
--Wenn der Fehler -ERESTARTSYS (Zeile 728) ist und sa_flags SA_RESTART hat (ein Flag, das mit sigaction () gesetzt werden kann). --Wenn der Fehler -ERESTARTNOINTR (Zeile 734) in Zeile 734 ist
Andernfalls führt der Kernel den Systemaufruf nicht erneut aus und setzt regs-> ax
auf -EINTR (ein Fehler, dass der Funktionsaufruf unterbrochen wurde). Aufgrund dieses Fehlers entscheidet der Benutzer (Benutzerprogrammbedingung), ob der Systemaufruf erneut ausgeführt werden soll.
[^ restart_syscall]: Die erneute Ausführung des Systemaufrufs mit do_signal () ist der Pfad (wenn get_signal () false ist), der verwendet wird, wenn handle_signal () nicht ausgeführt wird, sodass er sich hier nicht mit dem erneuten Ausführungsprozess des Systemaufrufs überschneidet. ..
750 failed = (setup_rt_frame(ksig, regs) < 0);
...
751 if (!failed) {
752 /*
753 * Clear the direction flag as per the ABI for function entry.
754 *
755 * Clear RF when entering the signal handler, because
756 * it might disable possible debug exception from the
757 * signal handler.
758 *
759 * Clear TF for the case when it wasn't set by debugger to
760 * avoid the recursive send_sigtrap() in SIGTRAP handler.
761 */
762 regs->flags &= ~(X86_EFLAGS_DF|X86_EFLAGS_RF|X86_EFLAGS_TF);
763 /*
764 * Ensure the signal handler starts with the new fpu state.
765 */
766 fpu__clear(fpu);
767 }
768 signal_setup_done(failed, ksig, stepping);
769 }
In Zeile 750 setup_rt_frame () im Benutzermodus für die Ausführung des Signalhandlers Stellen Sie den Stapelrahmen von ein. Der Stapelrahmen enthält die Informationen, die zum Ausführen des Signalhandlers erforderlich sind (Signalnummer, Siginfostrukturinformationen und Informationen zum Aufrufen des Systemaufrufs rt_sigreturn () aus dem Benutzermodusprozess. Rt_sigreturn () befindet sich nach dem Ausführen des Signalhandlers. Erforderlich, um zum Kernel-Modus zurückzukehren.
Wenn setup_rt_frame () erfolgreich gesetzt wurde, werden die Flags (regs-> flags
) und der FPU-Status (Gleitkommaregister) in den Zeilen 762 und 766 (aus unbekannten Gründen) gelöscht.
Und in der letzten (Zeile 768) signal_setup_done () ist die Einstellung in setup_rt_frame () erfolgreich oder fehlgeschlagen. Der Prozess verzweigt sich.
--Falls erfolgreich: sigorsets () mit aktuellen Prozessblockinformationen (aktuell-> blockiert) und Sigaction () mit dem Benutzer Nimmt die logische Summe (OR) der von gesetzten Blockinformationen (sigaction-> sa_mask) und setzt sie auf die Blockinformationen des aktuellen Prozesses. Wenn das Thread-Flag auf TIF_SINGLESTEP (vom Debugger verwendet) gesetzt ist, dann ptrace_notify (SIGTRAP). Führen Sie h # L146) aus.
Dies ist das Ende des "Signalübermittlungs" -Prozesses.
/ proc / <PID> / status
Ich habe den Code durchsucht, um herauszufinden, woher die Werte wie SigPnd, ShdPnd usw. in / proc / <PID> / status
stammen.
In task_sig () gab es eine Antwort. Zum Beispiel enthält SigBlk ("blockiert") "p-> blockiert" in Zeile 283.
266 static inline void task_sig(struct seq_file *m, struct task_struct *p)
267 {
268 unsigned long flags;
269 sigset_t pending, shpending, blocked, ignored, caught;
270 int num_threads = 0;
271 unsigned int qsize = 0;
272 unsigned long qlim = 0;
273
274 sigemptyset(&pending);
275 sigemptyset(&shpending);
276 sigemptyset(&blocked);
277 sigemptyset(&ignored);
278 sigemptyset(&caught);
279
280 if (lock_task_sighand(p, &flags)) {
281 pending = p->pending.signal;
282 shpending = p->signal->shared_pending.signal;
283 blocked = p->blocked;
284 collect_sigign_sigcatch(p, &ignored, &caught);
285 num_threads = get_nr_threads(p);
286 rcu_read_lock(); /* FIXME: is this correct? */
287 qsize = atomic_read(&__task_cred(p)->user->sigpending);
288 rcu_read_unlock();
289 qlim = task_rlimit(p, RLIMIT_SIGPENDING);
290 unlock_task_sighand(p, &flags);
291 }
292
293 seq_put_decimal_ull(m, "Threads:\t", num_threads);
294 seq_put_decimal_ull(m, "\nSigQ:\t", qsize);
295 seq_put_decimal_ull(m, "/", qlim);
296
297 /* render them all */
298 render_sigset_t(m, "\nSigPnd:\t", &pending);
299 render_sigset_t(m, "ShdPnd:\t", &shpending);
300 render_sigset_t(m, "SigBlk:\t", &blocked);
301 render_sigset_t(m, "SigIgn:\t", &ignored);
302 render_sigset_t(m, "SigCgt:\t", &caught);
303 }
Ich habe den Code nach dem Grund durchsucht, warum SIGKILL und SIGSTOP nicht blockiert werden können.
Die Antwort war sys_rt_sigprocmask (), das zur Laufzeit sigprocmask () aufgerufen wird und für Signalblöcke verwendet wird. SIGKILL und SIGSTOP wurden aus den neu gesetzten Blockinformationen (new_set
) in sigdelsetmask () in Zeile 3025 entfernt (dh die Einstellung zum Blockieren von SIGKILL und SIGSTOP setzt sie nicht tatsächlich).
3003 /**
3004 * sys_rt_sigprocmask - change the list of currently blocked signals
3005 * @how: whether to add, remove, or set signals
3006 * @nset: stores pending signals
3007 * @oset: previous value of signal mask if non-null
3008 * @sigsetsize: size of sigset_t type
3009 */
3010 SYSCALL_DEFINE4(rt_sigprocmask, int, how, sigset_t __user *, nset,
3011 sigset_t __user *, oset, size_t, sigsetsize)
3012 {
3013 sigset_t old_set, new_set;
3014 int error;
....
3022 if (nset) {
3023 if (copy_from_user(&new_set, nset, sizeof(sigset_t)))
3024 return -EFAULT;
3025 sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));
3026
3027 error = sigprocmask(how, &new_set, NULL);
3028 if (error)
3029 return error;
3030 }
....
3037 return 0;
3038 }
Sie können den Signalblock auch mit sa_mask in sigaction () setzen. Schauen wir uns das also auch hier an.
Im Fall von sigaction () wurde dieselbe Verarbeitung in den Zeilen 3967 und 3968 von sys_rt_sigaction () -> do_sigaction () durchgeführt.
3949 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
3950 {
3951 struct task_struct *p = current, *t;
3952 struct k_sigaction *k;
3953 sigset_t mask;
....
3966 if (act) {
3967 sigdelsetmask(&act->sa.sa_mask,
3968 sigmask(SIGKILL) | sigmask(SIGSTOP));
....
3988 }
....
3991 return 0;
3992 }
Ich habe auch den Grund untersucht, warum SIGKILL und SIGSTOP im Code nicht ignoriert oder geändert werden können.
In Zeile 3955 von sys_rt_sigaction () -> do_sigaction () gab es eine Antwort, die aufgerufen wird, wenn sigaction () ausgeführt wird. In der Bedingung ganz rechts ist "act" ein gültiger Wert und sig_kernel_only () ist wahr. Rückgabe eines Fehlers (ungültiges Argument) mit aktiviertem -EINVAL (wenn das Signal SIGKILL oder SIGSTOP ist). Das heißt, die Übergabe von SIGKILL und SIGSTOP an sigaction () schlägt mit -EINVAL fehl. In diesem Fall können Sie natürlich weder Ignorieren (SIG_IGN) noch Signalhandler (benutzerdefinierte Funktion) einstellen.
3949 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
3950 {
3951 struct task_struct *p = current, *t;
3952 struct k_sigaction *k;
3953 sigset_t mask;
3954
3955 if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
3956 return -EINVAL;
....
3966 if (act) {
....
3969 *k = *act;
3970 /*
3971 * POSIX 3.3.1.3:
3972 * "Setting a signal action to SIG_IGN for a signal that is
3973 * pending shall cause the pending signal to be discarded,
3974 * whether or not it is blocked."
3975 *
3976 * "Setting a signal action to SIG_DFL for a signal that is
3977 * pending and whose default action is to ignore the signal
3978 * (for example, SIGCHLD), shall cause the pending signal to
3979 * be discarded, whether or not it is blocked"
3980 */
3981 if (sig_handler_ignored(sig_handler(p, sig), sig)) {
3982 sigemptyset(&mask);
3983 sigaddset(&mask, sig);
3984 flush_sigqueue_mask(&mask, &p->signal->shared_pending);
3985 for_each_thread(p, t)
3986 flush_sigqueue_mask(&mask, &t->pending);
3987 }
3988 }
....
3991 return 0;
3992 }
man 1 bash
man 2 setrlimit
man 2 seccomp
man 2 signal
man 2 sigaction
man 3 getaddrinfo_a
man 5 proc
man 7 signal
man 7 pipe
Recommended Posts