ST-Ecke: Residente Programme und Worktree-Routine

Für immer und ewig muß es ja nicht gerade sein, allerdings möchte man doch ab und zu mal ein Programm im Computer starten, das nach Verlassen der Initialisierung weiterhin vorhanden ist. Solche Vorgehensweisen benötigt man meist dann, wenn eine Routine im Interrupt laufen soll. Das diese Programminitialisierung unseren Lesern Probleme macht, erkennt man an den vielen Zuschriften, in denen danach gefragt wird.

In der letzten Ausgabe haben wir uns mit der Heilung des Blitters beschäftigt - wie geht es Ihrem Blitter, gut? - und dabei eine Routine in das Betriebssystem eingebunden, die den Busfehler abfragt. Nach einer kurzen Initialisierung wurde die eigentliche Routine im Speicher RESIDENT verankert. Es heißt übrigens tatsächlich resident und nicht (!) resistent, wie viele meinen, denn resident heißt frei übersetzt ortsansässig, während resistent widerstandsfähig bedeutet. Da wir unser Programm aber nicht vor irgendwelchen Viren schützen sondern nur im Speicher verankern wollen, ist resistent sicherlich nicht ganz passend. Trotzdem ist in der Zeit der Virus-Construction-Sets und böswilligen Hacker sicherlich ein Algorithmus angebracht, der so manche Programme und Disketten resistent macht -nur, gegen Viren ist man nicht gefeit und eine Betriebssystemroutine gibt es dafür schon gar nicht.

Bild 1: So sieht ein Programm im Speicher aus

Zunächst wollen wir uns anschauen, wie ein Programm im Computer aussieht. Bei einem Rechner wie dem ATARI ST sind die Programme auf der Diskette nicht so gespeichert, wie sie später auch im Speicher vorliegen. Dies liegt erstens daran, daß ein Programm später an einer vorher nicht bestimmbaren Adresse liegen wird und zweitens durch große Variablenbereiche sehr viel Platz auf der Diskette verschwenden würde. Man kann also nicht sagen, daß eine Variable an einer bestimmten Adresse liegen wird, sondern man legt diese Variable an das Ende des für das Programm benötigten Speicherplatzes (TEXT-Segment) und adressiert dann relativ zum Befehl. Speichert man ein Programm mit solchen Variablen ab, weiß man aber nicht welche Adresse diese Variable haben wird. Das Auffüllen der Platzhalter mit Adressen, das (natürlich) vor dem Starten des Programms geschieht, nennt man Relozieren. Für diesen Reloziervorgang ist in der Programmdatei eine Tabelle vorhanden, anhand derer der Vorgang abgearbeitet wird.

Nun gibt es Variablen, die schon vordefiniert, also mit Anfangswerten belegt sind, und andere, die keinerlei Vorbelegung haben. Die zweite Art von Variablen wird häufig für diverse Puffer und sonstige große Speicherbereiche verwendet. Stellen Sie sich vor, Ihr Programm benötigte ein Megabyte Speicher, und Sie müßten diesen Bereich in ihrer Programmdatei miteinbauen. Spätestens dann sollten Sie sich überlegen, ob Sie Ihr Programm nicht gleich auf einer Harddisk ausliefem. Glücklicherweise müssen Sie das nicht, da es ein Segment in ihrem Programm gibt, das beschreibt, welche Variablen in welcher Größe benötigt werden. Erst beim Starten des Programms wird dann entsprechend den Angaben Speicherplatz reserviert und die Adressen im Programm eingetragen.

Zusammengefaßt

Den Bereich der schon mit Werten vorbelegten Variablen nennt man DATA-Segment, BASIC-Fanatiker werden gewisse Ähnlichkeiten zu DATA-Zeilen erkennen, während der zweite Bereich als BSS-Segment bezeichnet wird. BSS bedeutet Block-Storage-Segment (eigentlich ist das zweite s und das Wort Segment doppelt gemoppelt, aber was soll’s, es hat sich so eingebürgert, also bleiben auch wir dabei), da in ihm die Angabe über das Organisieren größerer Blöcke im Speicher zu finden sind. Wir wissen also, daß unser Programm im Speicher aus einem TEXT-Segment, das nicht, wie man vielleicht denken könnte, die Ausgabetexte des Programms sondern den eigentlichen Programmcode enthält, einem DATA-Segment, welches vordefinierte Variablen- auch Programmtexte - beinhaltet und einem BSS-Segment besteht. Wenn das BSS-Segment im Speicher des Rechners vorliegt, verbraucht es natürlich den vordefinierten Speicherplatz der gewünschten Puffer (beispielsweise unsere oben erwähnten 1 Megabyte).

Die Basis aller Dinge

Netterweise wird jedem Programm im Speicher von GEMDOS noch eine besondere Informations-Liste spendiert, die sich BASEPAGE nennt. Wie der Name schon sagt, liegt dieser Speicherbereich zu Beginn des Programms (BASE) und hat (wie es bei einer PAGE nun einmal üblich ist) 256 Bytes Länge. Wir wollen uns diese BASEPAGE einmal genauer anschauen.

Wir finden außer einigen momentan für uns nicht so wichtigen Informationen auch die Längen der einzelnen Segmente. Stellt sich die Frage, warum diese Längen überhaupt interessant sind! Dazu sollten Sie wissen, daß GEMDOS beim Laden des Programmes den größten, freien, zusammenhängenden Speicherplatz reserviert. Es ist dann Sache des Programmes, den benötigten Speicherplatz wieder schrumpfen zu lassen. Dies geschieht mit der GEMDOS-Routine Mshrink, die als Parameter den Beginn des Blocks und die Länge benötigt. Der Beginn unseres Blocks ist der Beginn der BASEPAGE, und die Länge ergibt sich aus der Summe der Einträge p_tlen, p_dlen, p_blen der BASEPAGE und deren Länge (256). Die Adresse der BASEPAGE wird freundlicherweise von GEMDOS vor dem Start des Programms auf den Stack geschrieben, so daß wir sie mit “move.l 4(sp), dest" erfahren können. Die folgende Vorgehensweise sehen Sie in INSTALL des Beispielprogramms, was in Listing 1 zu finden ist. Die eigentliche residente Speicherung geschieht später.

Zwei sind einer zuviel

Im weiteren Verlauf des Programms folgt die Initialisierung. Da häufig Initialisierungen auf Speicherbereiche zugreifen, die privilegiert sind - in unserem Beispiel wird auf die Ausnahmevektoren zugegriffen -, wird diese indirekt über die XBIOS-Routine SUPEREXEC aufgerufen. Schauen wir uns (besonders für diejenigen unter Ihnen, die unsere GOOD-BLIT-Routine letzten Monat schon bewunderten) kurz die Installierung an. Zunächst werden alle Interrupts ausgeschaltet und dann der Originalzeiger des Busvektors geholt. Im nächsten Schritt kommt eine interessante Sache: Es wird geprüft, ob dieser Vektor schon auf unsere (vielleicht schon einmal vorher gestartete) Routine zeigt. Dies geschieht folgendermaßen: Man schreibt vor das Programm irgendeinen Code, in unserem Fall GB10 (Goodblit 1.0). Liest man nun den Startvektor aus, um unser Programm einzuklinken, prüft man, ob dieser Vektor nicht schon auf ein Programm zeigt, das vor der Startadresse diesen Code hat. Ist dies der Fall, so wurde zu einem früheren Zeitpunkt unsere Routine fest im Speicher installiert, und wir können uns jede weitere Installierung sparen, denn was sollen wir mit zweimal demselben Programm im Speicher? Ist das Programm noch nicht im Speicher verankert gewesen, so schreiben wir die Startadresse unseres Programms in den Ausnahmevektor, andernfalls merken wir uns die Reinitialisierung in INSTALLED. War das Programm schon installiert, wird es heimlich, still und leise über die GEMDOS-Routine P_TERM0 verlassen, die den gesamten Speicher des Programmes freigibt. War das Programm noch nicht im Rechner vorhanden, so müssen wir dafür sorgen, daß beim Verlassen des Programms ein gewisser Teil im Speicher nicht freigegeben wird.

Unsere Routine wird seßhaft

Bei der Programmierung muß man darauf achten, daß die Routinen, die resident im Speicher bleiben sollen, an den Anfang des Programmes und aufeinanderfolgend gelegt werden. Außerdem sollte man beachten, daß kein DATA- und BSS-Bereich verwendet wird, denn dann kann man alle Daten hinter diesen Routinen freigeben. Die Länge wird über die Differenz von Anfang (RESID_A) und Ende (RESID_B) berechnet und das Programm nicht über P_TERM0 sondern über PTERMRES (Gemdos-Routine 49) verlassen. GEMDOS gibt nun allen Speicherplatz ab der Adresse, die sich aus BASEPAGE+Länge_der_residenten_Routinen ergibt, frei. Da sich DATA- und BSS-Segment in dem freigegebenen Bereich befinden, wäre es sehr ungesund (eine bombige Idee, es einmal zu versuchen), auf diesen Bereich zuzugreifen. Wenn Sie unbedingt diese Bereich verwenden wollen, müssen Sie PTERMRES die oben für MSHRINK berechnete Speicherlänge übergeben. Denken Sie dabei aber daran, daß dann ihre von nun an unnützen Initialisierungsroutinen auch resident bleiben. Bevor ich das Thema residente Programme beende, möchte ich sie noch darauf hinweisen, daß die Routinen durch das oben beschriebene Verfahren natürlich nicht RESETFEST sind. Dazu gehört schon etwas mehr Aufwand, “aber das ist eine andere Geschichte, und davon möchte ich Euch das nächste Mal erzählen....”. Zum Schluß unserer heutigen ST-Ecke habe ich noch eine erfreuliche Meldung zu verkünden: Ein Leser hat sich die Mühe gemacht, die in der Mai-Ausgabe der ST-Computer veröffentlichte WORK_TREE-Routine in PASCAL umzuschreiben. Diese Routine, die von Herrn Reichel aus Karlsruhe geschrieben wurde, möchte ich den PASCAL-Freunden unter unseren Lesern nicht vorenthalten. Für weitere Informationen bitte ich Sie, in der Mai-Ausgabe nachzuschlagen. Nur eine kleine Randbemerkung für alle diejenigen unter Ihnen, die diese ST-Ecke gelesen haben: Beachten Sie, daß in der allgemeinen Darstellung der WORK_TREE-Routine eine SOLANGE-WIE-Schleife (in C bedeutet dies DO-WHILE) verwendet wurde, die es in PASCAL nur in der Form SOLANGE-BIS (REPEAT-UNTIL) gibt. Dabei muß, um Gleichheit zu erreichen, die Schleifenbedingung umgedreht werden. Das bedeutet, daß aus “Solange,wie ob_next ungleich -1” ein “Solange, bis ob_next gleich -1 “ wird. Dieser kleine Fehler ist Herrn Reichel unterlaufen. Trotzdem bedanke ich mich vielmals bei ihm für die Mühe, die er sich gemacht hat. Die PASCAL-Routine finden Sie in Listing 2. Das Beispiel ist so ausgelegt, daß die Resource-Datei geladen wird. Viel Spaß und Erfolg beim Einsatz dieser Routine, und wie wäre es zum Beispiel mit einer WORK_TREE-Routine in MODULA-2?

Listing 1: Goodblit-Programminitialisierung

*
*	-------------------
*	|    GOODBLIT     |
*	--------------------
*
*	BLITTER Fehler-Korrektur
*
*	ASM source Code der Initialisierung
*	der gesamten Source-Code ist in ST-Computer
*	Ausgabe Juni 1988 zu finden.
* -----------------------------------------------
*
*	(c) 1988 by IMAGIC Grafik GbR.
*
*	geschrieben 13-2-88 by Jörg	Drücker.
*
* ===============================================
*
* Dieses Programm installiert sich selbst RESIDENT im Speicher
* -----------------------------------------------
*
*	Anmerkung!	Dieses Programm	ist	für	den	AUTO-Ordner
*	gedacht, kann aber auch direkt von DESKTOP aus
*	gestartet werden.
*
* ===============================================
*
*	OS-Konstanten
*
*	TRAPS:
GEMDOS:	equ	1
BIOS:	equ	13
XBIOS:	equ	14
*
*	GEMDOS:
P_TERM0:	equ	8
C_CONMS:	equ	9
P_TERMRES:	equ	$31
M_SHRINK:	equ	$4A
*
*	XBIOS:
SUPEREXEC:	equ	38
*
* ----------------------------------------------------*
* Der Bereich von RESID_A bis RESID_B bleibt resident
* ----------------------------------------------------*

RESID_A: bra	INSTALL

*-----------------------------------------------*
*
*	Hauptprogramm
*	-------------
*
*-----------------------------------------------*

dc.b	GB10'	*	"installiert"	Merker

MAIN:

* ... hier kommt das Hauptprogramm hin...

RESID_B:

*---------------------------------*
*                                 *
* Installierung                   *
*                                 *
*---------------------------------*

INSTALL:	move.l	sp,a6		* System-Stack-Pointer
			lea		USTACK,sp	* Installiere Benutzer-Stack
			move.l	4(a6),a6	* Adresse der BASEPAGE
			move.l	$C(a6),a4	* TEXT Segment-Länge
			adda.l	$14(a6),a4	*	+ DATA Segment-Länge
			adda.l	$lC(a6),a4	*	+ B55 Segment-Länge
			pea		256(a4)		*	+ BASEPAGE-Länge
			pea		(a6)		* BA5EPAGE
			clr.w	-(sp)		* dummy
			move.w	#M_5HRINK,-(sp) * verkleinere benutzten Speicher
			trap	#GEMDOS
			lea		12(sp),sp 	* Stack korrigieren
			pea		INSTAL_GOODBLIT(pc) * installiere GOODBLIT
			move.w	#SUPEREXEC,-(sp)
			trap	#XBIOS		* In Supervisor-Mode ausführen
			addq.l	#6,sp
			move.w	INSTAL_FLAG(pc),d0 * Klappte Installierung
			bne.s 	ITS_DONE	* JA. Text ausgeben, resident bleiben
			move.w	#P_TERM0,-(sp) * Programm ohne Anmerkung verlassen
			trap	#GEMDOS
*===============================*

ITS_DONE: 	pea		ID(pc)
			move.w	#C_CONWS,-(sp)
			trap	#GEMDOS
			addq.l	#6,sp
			clr.w	-(sp)		* Returnwert 0
			pea		RESID_B-RESID_A*256 *	minimale Resident-Länge
			move.w	#P_TERMRES,-(a7)
			trap	#GEMDOS

*-------------------------------*	

INSTAL_GOODBLIT:

			ori.w	#$780,sr 	* IPL 7, Interrupts ausschalten
			lea	8,al				*	Bus-Fehler-Zeiger
			movea.l	(a1),a8			*	Original-Wert holen
			cmpi.l	#'GB10',-(a0) 	*	GOODBLIT schon installiert ? 
			beq.s	INSTALLED		* scheint so -> Programmende
			lea		JMP_BU5ERR*2 (pc), a0 * Adresse der Modifikation 
			move.l	(a1),(a8)		*	Alten Vektor in Befehl abspeichern 
			move.l	#MAIN,(a1)		*	und uns in den Zeiger schreiben
			st		IN5TAL_FLAG		*	"Installiert" -Merker setzen

INSTALLED:
			rts

*-------------------------------*	

INSTAL_FLA6: dc.w 0				*	Enthält FF, wenn schon installiert

*-------------------------------*	
* Ausgabe-Text

ID:			dc.b	$1B,'E'
			dc.b	'The Unknown ',$80,' 1988 by Me'
END.ID:		dc.w	0

*-------------------------------*	

			bss

			ds.b	$108
USTACK:		ds.w	0

			end

Listing 2: Worktree-Routine in PASCAL

{$P-}					(*	UNBEDINGT NOTWENDIG *)
{ 'schützt' vor Überlauf des Stacks bei der Rekursion }

program worktest:

const
{$I GEMCONST.PAS }
	Wurzel =0;

TYPE
	{$I GEMTYPE.PAS}

VAR
	bau : Dialog.Ptr: 
	s : String[10]; 
	p : Tree_index; 
	junk: Integer;

{$I GEMSUBS.PAS }

FUNCTION Select_Objc( box : Dialog.Ptr;
			item : integer ) : integer;
BEGIN
	Obj.SetState(box,item,SELECTED,FAL5E);
END;

FUNCTION Deselect_Objc( box : Dialog_Ptr; item : integer) : integer;
BEGIN
	Obj_SetState(Box,item,NORMAL,FAL5E);
END;

FUNCTION Pr_baum( box : Dialog_Ptr; item : integer) : integer;
BEGIN
	writeln('item: ',item);
END;

{-----------------------------------------------------}
{                                                     }
{                   W O R K _ T R E E                 }
{                   = = = = = = = = =                 }
{                                                     }
{ Übertragung der C-Implementierung z. Durchwanderung }
{ der Objekt-Struktur eines Baumes in PASCAL 2.0 -    }
{ Aus ST-Computer 5/88 S. 145                         }
{ Volker REICHEL 7500 Karlsruhe 41 4. Mai 1988        }
{                                                     }
{*****************************************************}
{ work_tree wandert die Objektstruktur eines Baumes ab und führt dabei }
{ eine Routine aus, die das aktuelle Objekt bearbeitet Dieser Routine }
{ werden die Baumadresse und der Index des momentan aktuellen Objekts }
{ übergeben. 'Achtung': work_tree ruft sich rekursiv auf... }
{                                                                     }
{ PARAMETER; baum  - Baumadresse                                   }
{   ob_an    - Anfang der Wanderung                                }
{   ob_en    - Ende der Wanderung                                  }
{   aufruf   - Routine, die ausgeführt werden soll                 }
{                                                                  }
{   Soll der gesamte Baum bearbeitet werden, werden ob_an und      }
{   ob_en auf Null gesetzt.                                        }
{******************************************************************}

PROCEOURE work_tree( Var baum : Tree_Ptr; ob_an,ob_en : Tree_Index; 
			function aufruf( t : Tree_Ptr;ob : Tree_Index): integer );

LABEL 99;
VAR
		ob_ind : Tree_Index; 
		junk : Integer;
BEGIN

	ob_ind := ob_an; { aktueller Objektindex = 1. Objekt } 
	REPEAT			{ ENDLOS bis EXIT ! }
		junk := aufruf(baum,ob_ind); { Aufruf der Routine }
		IF baum^[ob_ind].ob_head <> Null_Index THEN 
		{ falls Verzweigung vorhanden }
			work_tree(baum,^[ob_ind].ob_head, baum^[ob_ind].ob_tail,aufruf);
		IF ob_ind = ob_en THEN
			GOTO 99;	{	letztes Objekt bearbeitet }
		ob_ind := baum^[ob_ind].ob_next; { nächstes Objekt }
	UNTIL baum^[ob_ind].ob_next = Null_Index; { bis Ende des Unterobjektes }

99:
END; { work_tree }

{--------------------}
{    Hauptprogramm   }
{--------------------}

BEGIN
	if init_gem >= 0 then 
	BEGIN
		IF load_resource('BAU.RSC') THEN 
		BEGIN
			Find_Dialog(Wurzel,bau); 
			center_dialog(bau);
			junk := Do_Alert('[1][Dialog-Baum ist eingelesen][OK]',1);
			p := do_dialog(bau,0);
			work_tree(bau,0,0,select_objc); 
			p := do_dialog(bau,0); 
			junk := Do_Alert([1][Alle Objekte wurden selektiert][OK]',1);
			work_tree(bau,0,0,Deselect_objc); 
			p := do_dialog(bau,0);
			junk := Do_Alert('[1][Alles wieder deselektiert][OK]',1);
			work_tree(bau,0,0,Pr_baum);
			junk := Do_Alert('[3][Das war''s!][OK]',1);
			End_Dialog(bau);
			Delete_Dialog(bau);
			exit_gem;
		END;
	END;
END.

Listing 3: Fehlende Zeilen der Goodblit-Rouline aus ST 6/88

move.l	(a1),(a0)			*	Alten Vektor in Befehl abspeiche;
move.l	#GOODBLIT, (a1)	*	und uns in den Zeiger schreiben
st		INSTAL_FLAG			*	"Installiert'-Merker setzen

Stefan Höhn
Aus: ST-Computer 07 / 1988, Seite 110

Links

Copyright-Bestimmungen: siehe Über diese Seite