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

signal(SIGCLD, SIG_IGN);

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.

Diese Seite basiert auf Inhalten aus dem Buch Arnold Willemer: Wie werde ich UNIX-Guru?