Grundlagen und Mechanismen von Linux-Signalen (ab Kernel v5.5)

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.

■ Grundlegend

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.

1. Was ist ein Signal?

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.

all_signal (2)-flow.jpg

2. Beispiel für die Signalverwendung

Zur besseren Vorstellung sind hier drei Beispiele für die Signalverwendung aus Sicht des Benutzers aufgeführt.

Befehl töten

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

Schlüssel unterbrechen

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

Programm (C-Sprach-API)

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

3. Signalnummer und Signalname

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.

4. Standardsignal, Echtzeitsignal

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].

5. Signalaktion

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. ** **.

all_signal-sigaction.jpg

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.)

Standardbetrieb

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).

all_signal-kernel_handler.jpg

[^ 5 core]: Siehe man 5 core für Details.

Standardsignal und entsprechendes Standardverhalten

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.

Beispiel für eine Änderung der Signalaktion

Verwenden Sie APIs (C) wie den Befehl trap und sigaction (), um andere Signalaktionen als ** SIGKILL ** und ** SIGSTOP ** zu ändern.

Trap-Befehl

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 (C-Sprach-API)

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.

6. Signalblock

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].

all_signal-sigblock.jpg

[^ 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.

Beispiel für die Einstellung des Signalblocks

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

7. Überprüfen Sie den Signalstatus

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

■ Kernel (v5.5)

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.

1. Datenstruktur

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.

Flag, das ein anstehendes Signal anzeigt

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.

all_signal-thread_flag.jpg

Warteschlange für das Halten von Signalen (für bestimmte Prozesse, für ganze Thread-Gruppen)

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).

(* 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.

Signalblockinformationen

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).

all_signal-sig_block.jpg

[^ real_blocked]: real_blocked scheint gesetzt zu sein, wenn der Systemaufruf rt_sigtimedwait (), rt_sigtimedwait_time32 (), rt_sigtimedwait_time64 () verwendet wird.

Informationen zu Signalaktionen

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.

all_signal-sig_handle.jpg

(*) 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

2. Implementierung

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).

Signalerzeugung

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).

all_signal-signal generate.jpg

__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.

kernel/signal.c (L1065)

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.

kernel/signal.c (L984)

 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.

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.

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.

Signalübertragung

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.

all_signal-signal delivery.jpg

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.

kernel/signal.c (L2521)

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, &current->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 = &current->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.

3. Bonus

Signalbezogener Code in / 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.

fs/proc/array.c#L266 (L266)

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 }

Warum SIGKILL und SIGSTOP nicht blockieren können

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).

kernel/signal.c (L3004)

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 }

Ignorieren Sie SIGKILL, SIGSTOP oder warum Sie keinen Signalhandler einstellen können

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 }

■ Referenzen

Recommended Posts

Grundlagen und Mechanismen von Linux-Signalen (ab Kernel v5.5)
[Muss für Anfänger] Grundlagen von Linux
[Linux] Lernen Sie die Grundlagen von Shell-Befehlen
Linux-Grundlagen
Linux-Grundlagen
[Python] Kapitel 02-01 Grundlagen von Python-Programmen (Operationen und Variablen)
Bestätigung der Grundlagen des Wettbewerbs professionell ~ gcd und lcm ~