20.7 <signal.h> 

Signale sind nicht vorhersehbare Ereignisse, die zu einem nicht vorhersagbaren Zeitpunkt auftreten können, also asynchrone Ereignisse. Nach dem ANSI-C-Standard gibt es folgende Signale, die vorkommen können:
Name | Bedeutung |
SIGABRT |
Dieses Signal signalisiert, dass sich das Programm abnormal beendet hat (abort()). |
SIGFPE |
Dieses Signal wird z. B. angezeigt bei einer Division durch 0 oder einem Überlauf einer Zahl. |
SIGILL |
Dieses Signal wird angezeigt, wenn ein illegaler Hardware-Befehl ausgeführt wird. |
SIGINT |
Dieses Signal wird an alle Prozesse geschickt, wenn die Tastenkombination (Strg) + (C) gedrückt wurde. |
SIGSEGV |
Wird dies angezeigt, wurde versucht, auf eine unerlaubte Speicherstelle zu schreiben oder diese zu lesen. |
SIGTERM |
Beendigung eines Programms |
Unter Linux gibt es deutlich mehr Signale (ca. 30). Mit dem Befehl
kill -l
wird eine Liste der Signale unter Linux/UNIX ausgegeben. Tritt ein Signal auf, haben Sie folgende Möglichkeiten, darauf zu reagieren:
- Eintragen einer selbst geschriebenen Funktion
- Ignorieren des Signals (geht aber nicht mit SIGKILL)
- Verwenden der voreingestellten Default-Funktion (Bei den ANSI-C-Signalen ist dies immer eine Beendigung des Programms.)
Um auf die Signale zu reagieren, existiert ein sogenanntes Signalkonzept. Dabei richtet ein Prozess einen sogenannten Signalhandler ein. Dieser Signalhandler teilt – wenn das Signal auftritt – dem Systemkern mit, was er zu tun hat. Ein solcher Handler kann mit der Funktion signal() eingerichtet werden. Hier ihre Syntax:
#include <signal.h> void(*signal(int signr, void(*sighandler)(int)))(int);
Einen solchen Prototyp zu lesen, ist fast unmöglich. Aus diesem Grund wurde die Funktion in der Headerdatei <signal.h> wie folgt vereinfacht:
typedef void (*__p_sig_fn_t)(int); __p_sig_fn_t signal(int, __p_sig_fn_t);
Somit sieht der Prototyp folgendermaßen aus:
signalfunktion *signal(int signalnummer, signalfunktion *sighandler);
Mit dem Parameter signalnummer legen Sie die Nummer des Signals fest, für die ein Signalhandler eingerichtet werden soll. Dies ist dann eines der Signale, die Sie soeben in Tabelle 20.15 kennengelernt haben (bzw. unter Linux diejenigen, die mit kill -l aufgelistet wurden).
Für den Parameter sighandler sind zwei Konstanten in der Headerdatei <signal.h> deklariert: SIG_DFL und SIG_IGN. Mit SIG_DFL wird die Default-Aktion ausgeführt, was meist die Beendigung des Prozesses bedeutet. Ein Beispiel:
signal(SIGINT, SIG_DFL);
Falls Sie die Tastenkombination +
drücken, wird die Default-Einstellung des Signals SIGINT ausgeführt. Und die Default-Einstellung schreibt vor, dass das Programm beendet wird. Als zweite Möglichkeit können Sie Folgendes eingeben:
signal(SIGINT, SIG_IGN);
Drücken Sie jetzt die Tastenkombination +
, passiert gar nichts. Das Signal SIGINT wird mit der Angabe von SIG_IGN ignoriert. Als dritte Möglichkeit können Sie das Signal SIGINT abfangen und die Adresse einer eigenen Funktion übergeben, die ausgeführt werden soll, wenn die Tastenkombination
+
betätigt wurde:
signal(SIGINT,funktionsaufruf);
Jetzt wird es Zeit, dass Sie sich ansehen, wie die Funktion signal() in der Praxis eingesetzt wird:
/* signal1.c */ #include <stdio.h> #include <stdlib.h> #include <signal.h> void sigfunc(int sig) { int c; if(sig != SIGINT) return; else { printf("\nWollen Sie das Programm beenden (j/n) : "); c=getchar(); if(c == 'j') exit (EXIT_FAILURE); else return; } } int main(void) { int i; signal(SIGINT, sigfunc); while(1) { printf("Mit STRG+C beenden"); for(i = 0; i <= 48; i++) printf("\b"); } return EXIT_SUCCESS; }
Mit der Anweisung
signal(SIGINT, sigfunc);
wird ein Signalhandler für das Signal SIGINT eingerichtet, der beim Auftreten dieses Signals die Funktion sigfunc aufrufen soll.
Ein einfaches Beispiel bietet auch das Erstellen einer eigenen kleinen Shell. Die einzelnen Shellbefehle werden in einer Endlosschleife abgearbeitet. Mit der Tastenkombination +
lösen Sie dabei einen Neustart der Shell aus. Dieser Sprung (Neustart) wird mit den Funktionen der Headerdatei <setjmp.h> realisiert. Hier sehen Sie das Beispiel dazu:
/* a_simple_shell.c */ #include <stdio.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <stdlib.h> #define MAX 255 #define OK 0 jmp_buf restart; void ctrlc(int sig) { signal(sig, ctrlc); /* zurück zur Kommandozeile */ longjmp(restart, 1); return; } int main(void) { char *command; /* Installiere den Signalhandler. */ signal(SIGINT, ctrlc); if(setjmp(restart) != 0) printf("\n\nShell neu gestartet ...\n\n"); else printf("\n\nShell gestartet ...\n\n"); for (;;) { /* Hier können Sie machen, was Sie wollen. */ char puffer[MAX]; printf("$~> "); fgets(puffer, MAX, stdin); command = strtok(puffer, "\n"); if( strcmp(command, "test") == OK ) printf("Ihr Befehl lautete \"test\"\n"); else if( strcmp(command, "help") == OK ) printf("Brauchen Sie Hilfe?\n"); /* usw. eine Menge mehr Shellbefehle ... */ else if( strcmp(command, "exit") == OK ) exit (EXIT_SUCCESS); else { printf("\nUnbekannter Shellbefehl\n"); printf("Bekannte Befehle: test, help, exit\n\n"); } } return EXIT_SUCCESS; }
Dies ist eine einfache Schnittstelle einer eigenen Shell. Logischerweise müssen Sie statt der Ausgabe von Texten Ihre selbst geschriebenen Funktionen implementieren.
Hier sehen Sie ein weiteres Beispiel zu signal() mit dem Signal SIGABRT:
/* sigabort.c */ #include <stdio.h> #include <stdlib.h> #include <signal.h> void sigfunc(int sig) { if(sig == SIGABRT) printf("Demonstration von SIGABRT\n"); } int main(void) { signal(SIGABRT, sigfunc); abort(); return EXIT_SUCCESS; }
Um zu testen, ob der Aufruf der Funktion signal() überhaupt erfolgreich war, befindet sich in der Headerdatei <signal.h> der Fehlercode SIG_ERR, der mit dem Wert –1 definiert ist. Wollen Sie also die Funktion signal() auf Fehler überprüfen, sollte dies so aussehen:
if( signal(SIGINT,sigfunc) == SIG_ERR) { /* Fehler beim Aufruf von signal */
Es ist auch möglich, mit der Funktion raise()ein Signal an ein ausführendes Programm zu senden. Die Syntax der Funktion lautet:
int raise(int signr);
Damit können Sie ein Signal mit der signr an das Programm senden. Ein kurzes Beispiel:
/* raise_signal.c */ #include <stdio.h> #include <stdlib.h> #include <signal.h> void sigfunc(int sig) { if(sig == SIGINT) printf("SIGINT wurde ausgeloest\n"); } int main(void) { signal(SIGINT,sigfunc); printf("Mit ENTER SIGINT ausloesen\n"); getchar(); /* SIGINT auslösen */ raise(SIGINT); return EXIT_SUCCESS; }
Unter Linux/UNIX verwendet man allerdings in der Praxis ein etwas anderes Signalkonzept, da die signal()-Funktion von ANSI C hier einige Schwächen besitzt. Mehr dazu können Sie wieder meinem Buch »Linux-UNIX-Programmierung« entnehmen, das Sie auch auf meiner Webseite zum Online-Lesen vorfinden.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.