Hard- und Softwaresignale unter Turbo C: Weichen stellen

Hard- und Softwaresignale wie Busfehler sind vielen Programmierern aus absturzträchtigen Programmen bekannt. Meist bleibt danach nur noch der Griff zum Resetknopf. Dabei stellt die Programmiersprache C Funktionen bereit, um sinnvoll auf Fehlersignale zu reagieren.

Wer Bomben sieht, sieht oftmals schwarz - zumindest für seine Daten ‚ die gerade die Reise ins jenseits angetreten haben. Doch gibt es Wege, definiert auf diese üblen Situationen mittels ANSI-C-Funktionen zu reagieren. Da mit Turbo C ein sehr guter ANSI C-Compiler für den ST zur Verfügung steht, betrachten wir diesen - ähnliches gilt auch für Lattice C. Turbo C kennt nach ANSI u.a. folgende Funktionen:

	signal(), raise(), setjmp() und longjmp()

Auf die anderen Funktionen zu Standard-Fehlerabfragen wie z.B. ferror() etc. gehen wir hier nicht ein. Mit den oben genannten Funktionen fangen wir Exceptions ab und behandeln gezielt den aufgetretenen Fehler. Auf der TOS-Diskette finden Sie hierzu ein Beispiellisting.

Tritt ein Busfehler an einer Stelle des Programms auf, und ist wie im Beispiel eine eigene Fehlerroutine (»behandle_bus_error«) durch die Funktion »signal()« eingerichtet, verzweigt die Fehler- erst zur Benutzerroutine, bevor das Originalprogramm des TOS (Bombenmeldung) angesprungen wird. Hierdurch hat man Gelegenheit, individuell »bombenträchtige« Situationen zu meistern. Wichtig: Nach bestimmten Ausnahmezuständen (Exceptions) des 680xx ist der weitere Verlauf des Programmes nicht mehr sinnvoll. Hier ist es ratsam, zu einem früheren Zeitpunkt alle Inhalte der Prozessorregister zu sichern und die zerstörten Inhalte durch die gespeicherten zu überschreiben. Es ist müßig, 16 Register einzeln zu retten bzw. zu restaurieren. Deshalb gibt es die Funktionen »setjmp()« und »longjmp()«, die Ihnen diese Arbeit abnehmen. Mit deren Hilfe realisieren Sie auch die in »C« sonst verbotenen prozedurübergreifenden, sogenannten »langen« Sprünge. Die Funktion

	setjmp (env);

speichert in »env« die Registerinhalte der CPU, um diese bei einem späteren Aufruf von

	longjmp (env, val);

wieder zu restaurieren. Der Longjump ist nach ANSI nur unter bestimmten Bedingungen möglich, so beispielsweise nur innerhalb von »if ()«, »else if ()«, »else« und »switch«-Konstruktionen, sowie in Schleifen und innerhalb von einfachen Vergleichen (siehe Beispiel). Außerdem muß das Programm bereits einmal über »setjmp()« gelaufen sein und alle Registerinhalte gespeichert haben.

Funktionsweise der Signal-Routine

Die Funktion »signal()« überschreibt den durch den ersten Parameter ausgewählten Vektor (zum Beispiel Busfehler-Vektor 2) mit dem Vektor des zweiten Parameters (hier: »behandle_bus_error«). In der Regel liefert die Funktion den alten Vektor zurück, dies scheint derzeit nicht korrekt in Turbo C zu gelingen. Den alten Vektor sollten Sie sich in einem Puffer merken, um ihn am Ende des Programms zu restaurieren. Nach ANSI sollte dies durch den Parameter SIG_SYS automatisch geschehen. In Turbo C sind zusätzlich zu den in ANSI definierten Signal-Parametern alle wichtigen Exceptionvektoren (2 bis 79) für den 68000 definiert. Informationen zur Behandlung der Interrupt-Vektoren entnehmen Sie bitte der Literatur.

Die Signalverwaltung gibt dem Programmierer noch eine großartige Chance in die Hand: Außer auf Signale zu reagieren, die das Betriebssystem oder Soft- und Hardware aussenden, kann man auch auf Interrupts reagieren, die der Benutzer zu beliebigen Zeitpunkten während eines Programmablaufes gibt. Beispielsweise das Drücken der Tastenkombination <Control D>: Wurde durch den Signal-Parameter »SIGINT« eine Behandlungsroutine installiert, ist es möglich, ein Programm an jeder Stelle durch das Drücken von <Control D> zu unterbrechen und hierauf auch noch definiert einzugehen.

Leider ist gerade diese Signal-Behandlung in Turbo C noch fehlerhaft, da der ST zwar in der benutzerdefinierten Routine landet, sich jedoch bei einer anschließenden Tastenabfrage aufhängt. Turbo C 2.0 setzt zudem bei allen Signal-Routinen den »etv_critic«Vektor nicht zurück, was zu einer Endlosschleife in der »exit()« -Routine führt. Ist dieser kleine Fehler beseitigt, stellt ein professionelles Signal-Handling ebenso wenig ein Problem dar, wie die korrekte Einflußnahme auf fast alle üblen Situationen - angefangen beim Busfehler bis zum Abbruch einer Endlosschleife. Implementiert Borland dann noch die derzeit nicht vorhandenen Parameter »SIGTERM« (ordentliche Beendigung von Programmen), »SIGFPE« (Behandlung von Fließkomma-Fehlern - sehr wichtig!), »SIGALARM« (Aufwecken von »schlafenden« Prozessen) und vielleicht auch »SIGKILL« (absoluter Programmabbruch eines bestimmten, auch im Hintergrund laufenden Prozesses), ist der Weg zum Multitasking-Atari für C-Programmierer nicht allzu weit. Die oben genannten Signale lassen sich zwar durch »raise()« erzeugen, jedoch nicht abfangen. Alle durch »signal()« abfangbaren Exceptions lassen sich ebenfalls durch »raise()« erzeugen. Damit steht es ihnen offen, sehr einfach Busfehler zu verursachen - vielleicht um die »signal()-Verwaltung zu testen. Eine Einschränkung gibt es beim ANSI C-Compiler Lattice C: Hier sind »signal()«-Abfragen nur durch die von »raise()« erzeugten Signale möglich. Dies schränkt die Verwendbarkeit dieser Routinen sehr ein. Durch die Signalverwaltung in Kooperation von »setjmp()« und »longjmp()« ist es relativ einfach, auf alle bösartigen Systemfehler zu reagieren. Trotzdem ist nicht sichergestellt, daß jedes Programm nach einer Exception ohne Schwierigkeiten weiterläuft. Dies ergibt sich aus der Tatsache, daß »longjmp()« nicht den gesamten Programmstack restauriert. Ebenso problematisch sind Unterbrechungen durch <Control D> während des Zugriffs auf Massenspeicher oder innerhalb von Interruptroutinen. Aus diesem Grunde sollten Sie bei einem Systemfehler immer darauf aufmerksam machen, daß der Benutzer das Programm möglichst bald beenden bzw. sogar nach dem Programmende einen Reset durchführen kann.

Tips rund um Signale

Die Abfrage des Vektors »SICTRACF« sollte bei Debuggern unterbleiben, da sie diese Exeption verwenden. Die Signalroutinen für Privilegverletzung (SIGPRIV) und Interruptabfrage durch <Control D> (»SIGINT«) lassen den Atari TT schon bei der Initialisierung in die ewigen Jagdgründe eingehen. Diese Abfragen sollten Sie solange meiden, bis Borland eine komplette Anpassung des Turbo C an den TT vorgenommen hat. (ah)

Listing 1. Beispiel zur Abfrage von <Control D> Busfehler und Adreßfehler


#include <stdio h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <ext.h>

#define BUSERROR 1
#define ADRERROR 2
#define ABBRUCH 3
#define TRUE 1

jmp_buf register_speicher;	/* Speicher für 16 Register */
sigfunc_t old[3]:	/* Feld mit Pointern auf alte
				   Fehlerroutinen */
/* Prototypen */

void init_signal(void);
void exit_signal(void):
void init_errorcheck(void):
void behandle_signal(int);

int wahl;

void main(void)	/* 3 Bomben legen */
{
	int *a = NULL, *b = 0x3;	/* Bus- und Adress-Error vorbereiten*/

	init-signal();	/* Signalhandling initialisieren */

	wahl = BUSERROR;	/* Fangen wir mal dem Bus-Error an */
	init_errorcheck();

	if (wahl == BUSERROR)
		*a = 10;	/* Bus-Error gewollt - na bitte! */

	else if (wahl = ADRERROR)
		*b=10;	/* Adresz-Error gewollt - aber gern!*/
	else
	{
		printf("\n Zum Abbrechen der Endloschleife Control D drücken!");

	while(TRUE);	/* und die gefürchtete Endlosschleife */
	}
	exit_signal();	/* Programm definiert verlassen */

	exit(0);	/* hier würde man sonst nie hinkommen */

	void behandle_signal(int sig)
	{
		longjmp(register_speicher.sig);	/* Umbesetzung aller Register
				   ->Sprung nach setimp in main */
	}

	void init_errorcheck(void)
	{
		int i, rueck, weiter;

		if ((rueck = setjmp(register_speicher)) != 0)
		/* Rücksprungadresse und Register merken */

		switch(rueck)
		{
			case SIGSEGV:	/* Bus-Error behandeln */
				printf("\nBus-Error ist aufgetretent\n"
				       "Rückgabewert von longjmp(): %d\n",rueck);
				printf("Weiter ?");
				wahl = ADRERROR;
				weiter=getch();
			break;

			case SIGADR:	/* Adress-Error behandeln */
				printf("\nAdress-Error ist aufgetreten!\n"
				       "Rückgabewert von longjmp(): %d\n",rueck);
				printf("Weiter");
				weiter=getch();
				wahl = ABBRUCH;
			break;

			case SIGINT:
				printf("\nControl D wurde gedrückt
				       " Jetzt nur keine Tastendrücke!\n"
				       "Rückgabewert von longjmp(): %d\n",rueck);
				/* getch(); wird erst möglich sein, wenn der
				   Fehler in Turbo C behoben sein wird: statt
				   dessen einfache schleife und raus */

				for(i=0; i<20000; i++);
					weiter = ´n´;
			break;
		}
		if(weiter ==‘j‘ || weiter == ‘J‘)
		{
			init_errorcheck();

			/* Alle Register neu retten, da sie durch
			  ‚longjmp()‘ zerstört werden könnten */
			return;
		}
		exit_signal();
		exit(rueck);
	}
}
void init_signal(void)
{
	/* Signalverwaltung für SIGINT, SIGSEGV und SIGADR einhängen */

	old[0] = signal(SIGINT, (void (*)(int)) behandle_signal);
	old[1] = signal(SIGSEGV,(void (*)(int)) behandle_signal);
	old[2] = signal(SIGADR, (void (*)(int)) behandle_signal);
}
void exit_signal(void)
{
	/* Signalverwaltung für SIGINT, SIGSEGV und SIGADR wieder aushängen */
	signal(SIGINT, (void (*)(int)) old[0]);
	signal(SIGSEGV,(void (*)(int)) old[1]);
	signal(SIGADR, (void (*)(int)) old[2]):
}


Martin Hanser
Aus: TOS 05 / 1991, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite