Willemers Informatik-Ecke
Ein Prozess kann von außen mittels kill-Befehl Signale empfangen.
Normalerweise >>stirbt<< der Prozess, wenn er ein solches Signal bekommt.
Man kann Programme auch so schreiben, dass sie durch einen Signalbehandler
auf die Signale reagieren.
signal() installiert Signalhandler
Der Funktion signal() meldet dem Betriebssystem, dass eine Funktion des
Programmes bestimmte Signale bearbeiten will.
#include <signal.h>
signal(int SignalNr, void (*Signalfunktion)(int));
|
SIGTERM: Geregelter Abgang
Das eine Signal, auf das ein Programm immer reagieren sollte ist SIGTERM. Er
wird z. B. beim Herunterfahren des Systems an jeden Prozess gesandt. Die
typische Reaktion sollte es sein, die Aktivitäten schnellstmöglich
einzustellen und einen konsistenten Zustand der Daten zu gewährleisten.
Dafür hat es im Falle des Herunterfahrens höchstens 5 Sekunden Zeit. Dann
wird es durch einen SIGKILL endgültig erlegt.
SIGHUP: Neue Konfiguration einlesen
Besonders bei Hintergrundprozessen ist es inzwischen üblich, das SIGHUP-Signal zu verwenden, um den Prozess dazu zu bewegen, Konfigurationsdateien neu zu lesen. So ist es möglich, Einstellungen zu ändern, ohne den Betrieb zu unterbrechen.
Die folgende Tabelle benennt alle Signalkonstanten, die der erste Parameter annehmen kann.
| Name | Bedeutung |
| SIGHUP | (1) Hangup: Terminalabschaltung oder Konfiguration neu einlesen |
| SIGINT | Unterbrechung durch ctrl-C oder Delete |
| SIGQUIT | Unterbrechung durch ctrl- |
| SIGILL | Illegale Anweisung |
| SIGTRACE | Im Debugmodus |
| SIGIOT | I/O Trap |
| SIGKILL | (9) nicht abfangbarer Tötungsaufruf |
| SIGBUS | Busfehler |
| SIGSEGV | Segmentation Violation |
| SIGPIPE | Schreiben auf ein nicht zum Lesen geöffnete Pipe |
| SIGALRM | Aufgesetzter Alarm |
| SIGTERM | (15) Terminierung |
| SIGUSR1 | (16) Benuterdefiniertes Signal zur freien Verfügung |
| SIGUSR2 | (17) Benuterdefiniertes Signal zur freien Verfügung |
| SIGCLD | Tod eines Sohnprozesses |
| SIGPWR | (19) Spannungsproblem |
Die eingeklammerten Zahlen sind die Nummern der Signale, soweit sie über die
Systeme hinweg einheitlich sind.
Beispiel mit SIGHUP
Das folgende Programm fängt einen SIGHUP und gibt bei jedem kill
eine Meldung auf dem Bildschirm aus.
#include <signal.h>
void SigHandler(int Nr)
{
puts("Signal gefangen");
}
main()
{
signal(SIGHUP, SigHandler);
for (;;) ;
}
|
Signale senden: kill
Auch aus einem Programm heraus können Signale mit der Funktion kill()
versendet werden.
#include <sys/types.h>
#include <signal.h>
int kill(int pid, int signal);
|
Parameter
Der Parameter pid gibt die Prozessnummer des Empfängers an.
Der Parameter signal bezeichnet das zu sendende Signal.
Im Erfolgsfall gibt die Funktion 0, ansonsten -1 zurück.
Auf Signale warten: pause
Die Funktion blockiert den Prozess und wartet auf ein beliebiges Signal.
#include <unistd.h>
int pause(void);
|
Dieser Aufruf liefert immer -1 als Rückgabewert.
Timeout setzen: alarm
Mit der Funktion alarm() wird ein Alarm aufgesetzt. Sobald die als
Parameter übergebenen Sekunden vergangen sind, wird dem Prozess ein Signal
SIGALRM zugesandt.
Unterbricht ewiges Warten
Diese Funktionalität ist wichtig, wenn man mit blockierenden Einheiten arbeitet,
bei denen die Anforderung nach einer gewissen Zeit abgebrochen werden soll.
Dies kommt beispielsweise in der Netzwerkprogrammierung vor.
Das eintreffende Signal unterbricht die blockierende I/O-Funktionen.
#include <unistd.h>
long alarm(long Sekunden);
|
Wiederholter Alarm
Der Rückgabewert ist 0. Steht allerdings noch ein Signal von einem vorher
aufgesetzten Alarm aus, wird dieser Alarm gelöscht und die Anzahl der Sekunden
zurückgegeben, die noch bis zum Alarm verblieben wären.
Zombies vereiteln
Ein Zombie ist ein verwaister Prozesstabelleneintrag
Ein Zombie ist ein Prozess, der in Wirklichkeit nicht mehr existiert.
Genauer gesagt handelt es sich um einen Eintrag in der Prozesstabelle, hinter
dem kein echter Prozess mehr steckt.
Normalerweise wartet der Vaterprozess auf das Ende des Sohnprozesses. Erst
dann läuft er weiter. Dies wird über den Systemaufruf wait() realisiert. Durch
diesen Aufruf wird der Vaterprozess in die Warteschlange gesetzt. Der Vater
kommt wieder frei, wenn der Sohn beendet wurde und wait() liefert auch den
Exitstatus des Sohnes. Diesen hinterlegt der Sohn beim Verscheiden in die
Prozesstabelle. Wenn aber der Vater gar keinen wait() ausführt, wird der
Eintrag in der Prozesstabelle nie gelöscht. Dieser Eintrag ist der Zombie.
Kindersignale ignorieren
Nun gibt es oft Situationen, in denen eben nicht gewartet wird, bis der Sohn
verscheidet, sondern eben gerade die Parallelität von Prozessen genutzt
werden soll. Das heißt, dass gerade bei Dämonen und Serverprozessen immer
die Gefahr besteht, dass Zombies entstehen.
Um dies zu vermeiden, kann man das Signal SIG_CLD ignorieren. Das passiert,
wenn der Funktion signal() statt der Behandlungsfunktion die Konstante
SIG_IGN angegeben word. Dies sagt dem Betriebssystem, dass dieses Signal in
Zukunft nicht beachtet werden soll.
Damit teilt der Vaterprozess mit, dass er keineswegs am weiteren Dasein
seines Sohnes interessiert ist und dass bitte Nachrichten nicht für ihn
aufgehoben werden sollen.