22.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:
Tabelle 22.5
Makros für Fehlersignale
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 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 so genanntes Signalkonzept. Dabei richtet ein Prozess einen so genannten 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 Prototypen 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, welche Sie soeben in der Tabelle kennen gelernt 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 (Strg) + (C) 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 (Strg) + (C), 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, welche ausgeführt werden soll, wenn die Tastenkombination (Strg) + (C) betätigt wurde:
signal(SIGINT,funktionsaufruf);
Jetzt wird es Zeit, zu sehen, 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 (Strg) + (C) lösen Sie dabei einen Neustart der Shell aus. Dieser Sprung (Neustart) wird mit den Funktionen der Headerdatei <setjmp.h> realisiert. Hier 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.
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, ein Signal an ein ausführendes Programm mit der Funktion raise() zu senden. Die Syntax der Funktion:
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 ausgelöst\n");
}
int main(void) {
signal(SIGINT,sigfunc);
printf("Mit ENTER SIGINT auslösen\n");
getchar();
/* SIGINT auslösen */
raise(SIGINT);
return EXIT_SUCCESS;
}
Unter Linux/UNIX verwendet man allerdings in Praxis ein etwas anderes Signalkonzept, da die signal()-Funktion von ANSI C hier einige Schwächen besitzt. Mehr dazu können Sie wieder aus meinem Buch »Linux-UNIX-Programmierung« entnehmen, welches Sie auch auf meiner Webseite zum Online-Lesen vorfinden.
|