Kapitel 8 Signale
Signale sind zwar das älteste Mittel zur Kommunikation zwischen den Prozessen, aber häufig immer noch ein ausreichendes und einfaches Mittel zum Zweck.
8.1 Grundlage zu den Signalen
 
Signale sind asynchrone Ereignisse und bewirken eine Unterbrechung auf der Prozessebene. Genauer, es handelt sich um Interrupt-Anforderungen auf der Prozessebene. Teilweise abhängig vom System gibt es ca. 30 Signale. Auflisten können Sie diese mit kill -l auf der Konsole. Bei mir (unter SUSE-Linux) sieht dies wie folgt aus:
$ 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 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 35) SIGRTMIN 36) SIGRTMIN+1
...
61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
Wie Sie richtig erkannt haben, fehlen einige. Warum, das ist mir nicht bekannt, aber SIG16 steht (stand) für »Stack fault«. 32–34 sind total unbelegt, während 35–63 unbelegt sind, aber noch einen halben Namen haben. Auf anderen Systemen wiederum (wie z. B. BSD) ist die Reihenfolge der einzelnen Signalnummern und deren Konstanten ein wenig anders. Daher empfiehlt es sich, schon im Voraus zu sagen, dass man immer die symbolischen Konstanten, anstatt der Nummern, verwenden sollte.
Für Signale gibt es viele Anwendungsmöglichkeiten auf der Prozessebene. Signale können u. a. zur Prozesssynchronisation verwendet werden, um vordefinierte Aktionen auszuführen, Prozesse zu beenden oder zu unterbrechen. Die Signale lassen sich in drei verschiedene Kategorien einordnen:
|
Systemsignale (Hardware- und System»fehler«) (ILL, TRAP, BUS, FPE, KILL, SEGV, XCPU, XFSZ, IO) |
|
Gerätesignale (HUP, INT, PIPE, ALRM, CHLD, CONT, STOP, TTIN, TTOU, URG, WINCH, IO) |
|
Benutzerdefinierte Signale (QUIT, ABRT, USR1, USR2, TERM) |
Tritt z. B. eines dieser Signale auf, wird es zuerst im entsprechenden Prozesstabelleneintrag hinterlegt (pending signal). Bekommt der Prozess, für den dieses Signal bestimmt war, dann die CPU und ist dieser an der Reihe, seine Befehle abzuarbeiten, so wird in einer Tabelle von Zeigern auf Funktionen in der Task-Struktur des Kernels nachgesehen, wie auf dieses Signal reagiert werden kann. Die Signalnummer wird dabei als Index dieser Struktur verwendet.
Wenn ein Prozess jetzt ein bestimmtes Signal empfängt, kann Folgendes passieren. Wurde für den Prozess, der ein Signal empfangen hat, eine entsprechende Verarbeitungsroutine angelegt, so wird diese ausgeführt. Mit dieser Verarbeitungsroutine fangen Sie quasi das Signal mit Ihrem Prozess auf. Wurde diese Verarbeitungsroutine ausgeführt, setzt der Prozess seine Ausführung dort fort, wo das Signal empfangen wurde.
Ansonsten führt der Kernel die Standardaktion des Signals für den Prozess aus. Diese Standardaktion hängt vom jeweiligen Signal ab. Meist ist dies die Beendigung des Prozesses. Einige Signale erzeugen auch einen Core-Dump (Speicherabbild eines Prozesses), der zum Debuggen verwendet werden kann.
Einige Signale werden von der C-Bibliothek vorbelegt. Daran ist nichts weiter besonders; diese Funktionen geben nur einen – auch noch landesspezifischen – String aus, welches Signal empfangen wurde, und beenden sich dann auch. Beweis:
$ export LANG=french
$ perl -e 'while(1){}' &
[1] 13206
$ kill -HUP %%
$
[1]+ Fin de la connexion (raccroché) perl -e 'while(1){}'
Außer einer eigenen Routine zum Abfangen des Signals und der Standardaktion des Kernels können Sie auch dafür sorgen, dass die Signale gar nicht beim Prozess ankommen. Dies geschieht, indem Sie ein Signal einfach ignorieren.
Hinweis Laut POSIX ist das weitere Verhalten von Prozessen undefiniert, wenn die Signale SIGFPE, SIGILL oder SIGSEGV ignoriert werden und diese auch nicht durch einen manuellen Aufruf von kill() oder raise() ausgelöst wurden.
|
Somit haben Sie also drei Möglichkeiten, um auf ein Signal zu reagieren:
|
Eintragen einer selbst geschriebenen Funktion |
|
Ignorieren des Signals (funktioniert nicht mit jedem Signal, z. B. SIGKILL und SIGSTOP) |
|
die voreingestellte Standardaktion verwenden |
Auftreten können Signale entweder durch Programmfehler (unerlaubter Speicherzugriff mit einem Zeiger, ungültige Instruktionen, Division durch null ...) oder werden durch den Benutzer selbst ausgelöst. Signale können z. B. mit der Tastenkombination (STRG)+(C) (SIGINT) oder (STRG)+(Z) (SIGTSTP) zur Beendigung bzw. zum Anhalten des Programms gesendet werden. Ebenso können diese vom Administrator mit kill an den Prozess gesendet werden. Aber auch andere Prozesses können einem Prozess ein Signal senden. Dadurch ist, wenn auch sehr beschränkt, eine einfache Interprozesskommunikation möglich.
STOP/TSTP: (STRG)+(Z) generiert TSTP für die aktuelle bash, die dann SIGSTOP auf den aktuell laufenden Prozess anwendet. Denn schickt man STOP einfach an das aktuelle Programm, so nimmt bash davon keine Kenntnis.
|
8.1.1 Signalmaske
 
Ein gerne verwendeter, aber häufig missverstandener Begriff – die Signalmaske. Für jeden Prozess, der gestartet wird, wird solch eine Signalmaske angelegt. Darin werden die Signale gehalten, die gerade blockiert werden. Für jedes Signal steht dem Prozess ein Bit zur Verfügung. Ist dieses Bit gesetzt, dann wird das entsprechende Signal gerade blockiert.
8.1.2 Signale und fork()
 
Erzeugen Sie einen neuen Kindprozess, erbt dieser automatisch alle eingerichteten Signalhandler des Elternprozesses. Somit stehen dem Kindprozess dieselben Aktionen wie dem Elternprozess zur Verfügung.
Ein Tipp noch dazu, wie Sie Zombieprozesse verhindern können, ohne die Funktionen wait() oder waitpid() zu verwenden. Vermeiden können Sie diese, indem Sie das Signal SIGCHLD im Elternprozess ignorieren. Damit sagen Sie diesem, dass Sie nicht mehr an dem Kindprozess interessiert sind. Damit werden auch keine Nachrichten mehr für den Kindprozess aufgehoben. Allerdings funktioniert dieser Trick nicht auf jedem System.
Sie haben also zwei Möglichkeiten: entweder sich nicht um die Kinder kümmern (SIGCHLD ignorieren) oder eine Notiz erhalten (über das Signal). Im Signalhandler muss man dann natürlich – wenn man daran interessiert ist – am Zombie wait() aufrufen.
8.1.3 Signale und exec
 
Da der Adressraum mit einem Aufruf der exec-Familie ja vollkommen neu belegt wird, liegt es auf der Hand, dass im Gegensatz zum fork()-Aufruf keine eigenen Routinen mehr vorhanden sind. Die zu ignorierenden Signale hingegen bleiben auch nach dem exec-Aufruf erhalten. Für alle anderen Signale wird wieder die Standardaktion eingerichtet. Das auch daher, weil die C-Bibliothek sowieso ihre eigenen installiert, wenn man sie lädt. Und das passiert ja unweigerlich bei jedem Programm.
8.1.4 Übersicht zu den Signalen
 
Jetzt haben Sie zwar einiges zu den Signalen erfahren, so dass es an der Zeit ist, die einzelnen Signale, die Sie mit kill -l auflisten können, näher zu erläutern. Es muss allerdings dazu erwähnt werden, dass die Signale und Nummern zum Teil systemabhängig sind. Die hier verwendete Übersicht gilt für Intel- und PPC-Prozessorsysteme. Auf Sparc- und Alpha- oder MIPS-Prozessor basierende Systeme haben auf jeden Fall zumindest zum Teil eine andere Signalnummer. Die Tabellen sind aufgeteilt nach Themen der Signale.
Tabelle 8.1
Signale, die meist bei Programmfehlern auftreten
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGFPE
|
8
|
Core & Ende
|
POSIX
|
Problem bei einer Gleitkommaoperation (z. B. Teilung durch null)
|
SIGILL
|
4
|
Core & Ende
|
POSIX
|
Ungültige Instruktion wurde ausgeführt.
|
SIGBUS
|
7
|
Core & Ende
|
|
Fehler auf dem System-Bus
|
SIGSYS
|
31
|
Core & Ende
|
|
Ungültiges Argument bei System-Call
|
SIGEMT
|
|
Ende
|
|
Emulations-Trap
|
SIGTRAP
|
5
|
Core & Ende
|
|
Unterbrechung (Einzelschrittausführung)
|
SIGIOT
|
|
Core & Ende
|
|
wie SIGABRT
|
SIGABRT
|
6
|
Core & Ende
|
POSIX
|
Abnormale Beendigung
|
SIGSEGV
|
11
|
Core & Ende
|
POSIX
|
Speicherzugriff auf unerlaubtes Speichersegment
|
Tabelle 8.2
Signale, die den Prozess beenden
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGTERM
|
15
|
Ende
|
POSIX
|
Programme, die SIGTERM abfangen, bieten meistens einen »Soft Shutdown« an.
|
SIGINT
|
2
|
Ende
|
POSIX
|
Interrupt der Dialogstation ((STRG)+(C))
|
SIGHUP
|
1
|
Ende
|
POSIX
|
Abbruch einer Dialogstationsleitung bzw. Neuladen der Konfiguration für Dämonen
|
SIGKILL
|
9
|
Ende
|
POSIX
|
das Signal kill
|
SIGQUIT
|
3
|
Core & Ende
|
POSIX
|
das Signal quit von einer Dialogstation
|
Tabelle 8.3
Signale, die bei Beendigung eines Timers auftreten – Alarm
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGALRM
|
14
|
Ende
|
POSIX
|
Zeituhr ist abgelaufen – alarm().
|
SIGVTALRM
|
26
|
Ende
|
BSD, SVR4
|
Der virtuelle Wecker ist abgelaufen.
|
SIGPROF
|
27
|
Ende
|
|
Timer zur Profileinstellung ist abgelaufen.
|
Tabelle 8.4
Asynchrone E/A-Signale
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGIO
|
29
|
Ignoriert
|
BSD, SVR4
|
Socket E/A ist möglich.
|
SIGURG
|
23
|
Ignoriert
|
BSD, SVR4
|
Dringender Socketstatus ist eingetreten.
|
SIGPOLL
|
|
Ende
|
SVR4
|
Ein anstehendes Ereignis bei Streams wird signalisiert.
|
Tabelle 8.5
Signale zur Prozesskontrolle
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGCHLD
|
17
|
Ignoriert
|
POSIX
|
Der Kindprozess wurde beendet oder angehalten.
|
SIGTTIN
|
21
|
Anhalten
|
POSIX
|
Prozess wollte aus einem Hintergrundprozess der Kontrolldialogstation lesen.
|
SIGTTOU
|
22
|
Anhalten
|
POSIX
|
Prozess wollte in einem Hintergrundprozess der Kontrolldialogstation schreiben.
|
SIGCLD
|
|
Ignoriert
|
|
wie SIGCHLD
|
SIGSTOP
|
19
|
Anhalten
|
POSIX
|
Der Prozess wurde angehalten.
|
SIGTSTP
|
20
|
Anhalten
|
POSIX
|
Der Prozess wurde »von Hand« mit STOP angehalten.
|
SIGCONT
|
18
|
Ignoriert
|
POSIX
|
Ein angehaltener Prozess soll weiterlaufen.
|
Tabelle 8.6
Signale, die bei Fehlern einer Operation ausgelöst werden
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGPIPE
|
13
|
Ende
|
POSIX
|
Es wurde in eine Pipe geschrieben, woraus niemand liest. Es wurde versucht, in eine Pipe mit O_NONBLOCK zu schreiben, woraus keiner liest.
|
SIGLOST
|
|
Ende
|
|
Eine Dateisperre ging verloren.
|
SIGXCPU
|
24
|
Core & Ende
|
BSD, SVR4
|
Maximale CPU-Zeit wurde überschritten.
|
SIGXFSZ
|
25
|
Core & Ende
|
BSD, SVR4
|
Maximale Dateigröße wurde überschritten.
|
Tabelle 8.7
Die restlichen Signale
Name
|
Nr.
|
Aktion
|
verfügbar
|
Bedeutung
|
SIGUSR1
SIGUSR2
|
10, 12
|
Ende
|
POSIX
|
Frei zur eigenen Benutzung
|
SIGWINCH
|
28
|
Ignoriert
|
BSD
|
Window-Größe hat sich verändert.
|
|