F68KANS portieren



1. Generelles


Dieses Dokument sollte von Programmierern, die Anwendungen in 
Forth erstellen sollen, niemals gelesen werden mssen. Es wendet 
sich ausdrcklich an den Systementwickler, der F68KANS auf eine 
neue Maschine zu portieren hat und dessen Leistungsmerkmale dem 
Anwendungsprogrammierer zugnglich machen mchte. 


Die hier beschriebenen Dinge lassen sich auch allein mit F68KANS 
nicht nachvollziehen. Es ist immer das Entwicklungswerkzeug des 
Systemimplementierers, typischerweise ein Assembler oder 
C-Compiler, notwendig. Zustzlich sogar noch das Wissen, wie man 
diese Dinge bedient. Wenn Sie dieses Dokument bentigen, verlassen 
Sie die Sicherheit der Forth-Programmierung  

F68KANS ist von Anfang an mit der Idee geschrieben worden, ein 
leicht portierbares System fr eine ganze Anzahl verschiedener 
Rechner mit gleicher CPU zu erhalten.
Um dieses Ziel zu erreichen, wurden im grundlegenden Design die 
entsprechenden Vorkehrungen getroffen. So mu ein portables System 
mindestens zwei Anforderungen erfllen:

-	Es mu unabhngig von speziellen Ein-/Ausgabegegebenheiten 
sein.
-	Es mu unabhngig von speziellen Adrekonfigurationen um 
komplizierte Lademechanismen zu vermeiden. 

Beides wurde bei F68KANS sichergestellt. Mehr noch: F68KANS 
erlaubt trotz seiner prinzipiellen Systemunabhngigkeit trotzdem 
die Verwendung smtlicher Ressourcen eines speziellen Systems.

Um dies alles zu ermglichen, Teilt sich das System in zwei 
Bereiche:

-	Den Forth-Kern oder das Forth-System. Dieser Teil liegt 
(typischerweise) als ein Binrfile vor. Alle 
Forth-Definitionen des Anwenders sind in diesem File 
enthalten. Wenn der Programmierer sie nicht explizit eingefgt 
hat,  enthlt dieses File aber keine Bezge zu der jeweiligen 
Hard-/Softwareumgebung. 

-	den Lader, der die Anbindung des Forth-Systems an die 
jeweilige Umgebung bernimmt. Dieser Lader kann je nach 
Anforderung an den Einsatz von Systemressourcen 
unterschiedlich komplex gehalten sein.


Dieses Dokument nun erklrt Aufgaben des Laders und die 
Schnittstelle zwischen Lader und Forth im Detail. Die Kenntnis 
dieser Dinge ist wichtig fr die Portierung des gesamten 
Forth-Systems in andere Umgebungen oder die Erweiterung eines 
bestehenden Laders, um zustzliche Funktionalitt dem Forth-System 
zur Verfgung zu stellen.

Die zu erklrenden Speicherstrukturen werde ich in Form von 
C-Quelltext darstellen, da C eine weit verbreitete Sprache ist, 
von der jeder zumindest schon einmal gehrt hat, zum anderen eine 
Darstellung in einem Pseudocode auch nicht viel anders ausshe.
Fr das Studium dieser Beschreibung empfehle ich, sich einen 
Ausdruck des Original-Atari-Laders in der Datei 'LOADER.C' 
danebenzulegen, und die Beschreibung anhand dieser exemplarischen 
Implementation zu verfolgen.



2. Laden des Systems

Die erste Aufgabe des Laders besteht darin, das binre Abbild des 
Forth-Systems in geeigneter Form in den Hauptspeicher des 
Computers zu laden. Sollte sich das System oder Teile davon 
bereits im ROM befinden, sorgt der Lader dafr, die Teile 
entsprechend zu initialisieren.
Das Forth-System besteht aus zwei Teilen:

-	dem Codesegment
-	dem Datensegment 

Meist sind die beiden Teile zusammen mit einem Header gemeinsam in 
einer Binrdatei untergebracht. In der Auslieferungsform hat der 
Header folgendes Format:

	struct header
		{
          int magic;
          unsigned long codesize;
          unsigned long datasize;
          int dont_care[9];
        } header; 

Dieses Format ist kein Zufall, sondern entspricht genau dem 
Format, das von einem Atari Assembler abgeliefert wird.

In den ersten zwei Byte befindet sich eine sog. 'Magic Number', 
die es dem Lader ermglichen soll, ein F68KANS Imagefile zu 
identifizieren und andere Dateien abzuweisen. In der von mir 
gelieferten Version enthlt dieser Wert die beiden Buchstaben 
'JP'. Hier habe ich mich also in Form meiner Initialen verewigt.

Die beiden folgenden long-Werte (4 Byte) geben die Lnge von Code- 
und Datensegment wieder, so wie sie hintereinander im File 
abgelegt sind.

Die darauf folgende 18 Byte habe keine Bedeutung. Der gesamte 
Header ist im Ganzen also 28 Byte lang.


Wenn man nun den Header ausgewertet hat, mssen die beiden Teile 
sorgsam im Hauptspeicher verstaut werden. Dazu mu zunchst 
jeweils ein Puffer fr Code- und Datensegment bereitgestellt 
werden. Bei der Wahl der Gre dieser Puffer sollte man ein Auge 
darauf haben, was man mit dem Forth-System eigentlich vorhat. Dazu 
ist es wichtig zu wissen, da F68KANS Code und Daten strikt 
voneinander trennt. Das bedeutet, da bei Anwendungen, bei denen 
sehr viel Programm compiliert wird, die sich aber nur mit einer 
geringen Menge von Daten befassen, ein groes Codesegment, bei 
Anwendungen, die viel Daten handhaben oder die auch nur eine Groe 
Menge von Forth-Worten produzieren (Header sind Daten!), ein 
groes Datensegment vorzusehen ist (oder eben auch beides).
Fr den ersten Einstieg reichen 64kB Code und 128kB Daten immer.

Wichtig ist natrlich, da beide Puffer grer sind, als die 
jeweiligen Segmente im Binrfile!

Nachdem die Puffer angelegt worden sind, liest man nun einfach die 
beiden Segmente aus dem File (oder sonstwoher) in ihre Puffer. 
Wenn nicht compiliert werden soll, kann das Codesegment, wenn es 
den Code nun endlich enthlt, schreibgeschtzt werden.


Damit ist bezglich des Speichers noch nicht alles getan. F68KANS 
braucht desweiteren noch Speicherbereiche fr Daten- und 
Returnstack sowie fr seinen 'Terminal Input Buffer' TIB. Hier 
wrde ich fr den Anfang 2kB fr den Datenstack, 1kB fr den 
Returnstack und 256 Byte fr den TIB empfehlen.

Da man nun alle notwendigen Adressen beisammen hat, mu man dafr 
sorgen, da F68KANS auch davon erfhrt. Dazu ist eine Struktur 
vorgesehen, die dem System spter beim Start als Parameter mit 
bergeben wird. Hierin werden alle notwendigen Zeiger eingetragen. 
Die Struktur ist folgendermaen definiert:
 

	typedef struct {
		long registers[16];		/* to be filled by F68K */
 		void *data;				/* A3 */
		void *code;				/* A5 */
		void *datastack;		/* A6 */
		void *retstack;  		/* A7 */
		void *TIBptr; 	
		long codelen;
		long datalen;
		SI_group *si;
	} FORTHPARAS; 

	FORTHPARAS forthparas; 


Das erste Feld registers[] wird von F68KANS beim Systemstart mit 
den Registerinhalten gefllt. Damit soll kritischeren Ladern die 
Mglichkeit gegeben werden, bei der Ausfhrung von Laderfunktionen 
wie zum Beispiel Basis-I/O den Registerkontext des Laders wieder 
herzustellen.
Die folgenden beiden Eintrge sind Zeigervariablen, die die 
Adressen von Code- und Datensegment aufnehmen. Die nchsten drei 
enthalten die Adressen von Daten- und Returnstack sowie des TIB. 
Die beiden Variablen codelen und datalen enthalten nicht die 
aktuellen Lngen, wie sie im File vorgefunden wurden, sondern die 
Gren der zur Verfgung gestellten Puffer.
Der letzte Eintrag *si wird erst in den folgenden Kapiteln 
interessant. 

Wenn die Struktur soweit gefllt ist, kann man sagen, da das 
System geladen ist. Nun mu man daran gehen, es mit einigen 
Basisfunktionen fr Ein- und Ausgabe zu versorgen. 


3. Basis I/O

Ein normales F68KANS Binrfile enthlt keinerlei Informationen 
darber, wie z.B. Zeichen einzulesen bzw. Auszugeben sind. Dies 
ist aber fr ein textgesteuertes System von nicht zu 
unterschtzender Wichtigkeit.
Die Art, wie solche Dinge zu behandeln sind, ist jedoch in hohem 
Mae systemabhngig. Deshalb werden die dazu notwendigen 
Funktionen vom jeweiligen Lader zur Verfgung gestellt. Whrend 
der Initialisierung trgt F68KANS die Adresse dieser Funktionen 
dann in Variablen ein, die F68KANS fr die Abwicklung seines 
Basis-I/O benutzt. Damit das funktionieren kann, mssen die 
Funktionen gewissen Anforderungen gengen, da sie ja schlielich 
mit dem Forth-System Parameter austauschen mssen. Kurz: die 
beiden Seiten mssen sich verstehen.

Vier Basisfunktionen werden vom Forth erwartet:

	KEY		Zeichen einlesen
	EMIT	Zeichen ausgeben
	KEY?	Eingabestatus der Konsole testen
	EMIT?	Ausgabestatus der Konsole testen

Mehr nicht! F68KANS luft in seiner Basisfassung auch bereits mit 
nur den beiden ersten Funktionen.

Bei der Definition der Parameterschnittstelle zu diesen Funktionen 
habe ich mich einfach an den Mechanismen des von mir verwendeten 
C-Compilers orientiert, wobei ich dessen besonderen 
Leistungsmerkmale auer Acht gelassen habe. Das gibt mir die 
Hoffnung, da sich die Schnittstelle auch unter anderen Systemen 
so einrichten lt.

Um es kurz zu machen:

-	alle Funktionen bernehmen ihre Parameter auf dem Returnstack, 
der durch das Register A7 indiziert wird. Alle Werte auf 
diesem Stack sind als 32 Bit Werte dort abgelegt. Dies gilt 
genauso fr das spter zu beschreibende Interface zu weiteren 
Bibliotheken.
-	Alle Funktionen liefern maximal einen 32 Bit Wert zurck, und 
zwar im Register D0.  

Kurzes Beispiel:

KEY bernimmt keinen Parameter vom Forth, deshalb ist der 
Returnstack nicht wichtig, liefert aber das gelesene Zeichen immer 
auf 32 Bit expandiert im Register D0 zurck.


	long cdecl key(void);
	{
		return (long)Bconin(CONSOLE);
	} 


EMIT dagegen liefert keinen Wert zurck, bernimmt aber einen (das 
Zeichen) vom Stack, der zwingend long sein mu:


	void cdecl emit(ch)   
	long ch;
	{
		Bconout(CONSOLE,(int)ch);
	} 


Dem C-Kenner mag hier das 'cdecl' auffallen. Damit wird MEIN 
C-Compiler gezwungen, Parameter vom Stack zu bernehmen. 
Andernfalls wrde er die Parameter in Registern erwarten, womit 
F68KANS natrlich nicht dienen kann.

Die Einhaltung dieser Konvention ist extrem wichtig! F68KANS wird 
unter keinen anderen Umstnden laufen!

Sollten Sie Ihren C-Compiler nicht zur Einhaltung dieser 
Konvention berreden knnen, so werden Sie wohl kaum umhinkommen, 
sich entweder einen anderen Compiler zu besorgen, oder Ihre 
Funktionen mit einem kleinen Assemblermntelchen zu umgeben. Wenn 
Sie Ihren Lader sowieso in Assembler schreiben, gibt es natrlich 
keine Probleme.

hnlich wie bei den Speicherbereichen mu auch hier natrlich 
F68KANS von Ihren Bemhungen Kenntnis erhalten. Dazu dient die 
wohl komplizierteste Struktur in dem ganzen Geschft. Diese 
Struktur ist dann auch fr die Nutzung weiterer Funktionen, die 
dann aus beliebigen Bibliotheken stammen knnen, von Bedeutung.

Dazu wird dem Forth in der gleichen Struktur, in der ihm auch die 
gesamten Speicherparameter bergeben werden, auch ein Zeiger auf 
eine Struktur vom Typ 'SI_group' mitgeteilt.
'SI' steht dabei fr 'System Interface'. Der Zeiger weist damit 
also auf eine Gruppe von System-Interfaces. Die Gruppierung 
besteht darin, da der Zeiger wiederum auf ein Feld von Zeigern 
weist, die dann erst auf das eigentliche SI zeigen.

Ein solches SI hat folgende Struktur:


	typedef void* cdecl SI_funcarr;
	typedef struct { 	
		char  		SI_id[4];
		SI_funcarr 	SI_fa;
 	} SI_group; 


Ein SI besteht demnach immer aus einer eindeutigen Kennzeichnung 
aus vier Buchstaben und darauf folgend ein Zeiger auf ein Feld aus 
Funktionen. Dies hat den Sinn, eine ganze Gruppe von Funktionen 
unter einem Namen zusammenzufassen (z.B. BIOS, GRAF, FLOT). Damit 
kann F68KANS spter leicht erkennen, ob ein bestimmter Lader eine 
vom Programm gewnschte Funktionsgruppe bereitstellt und 
entsprechend reagieren.

Fr die Herstellung der Basisfunktion verlangt F68KANS, da immer 
ein SI Namens 'BIOS' existieren mu und zustzlich, da dieses in 
dem Zeigerfeld den ersten Eintrag bildet. In meiner C-Quelle seiht 
das so aus:


	SI_funcarr SI_BIOS_fa[] = {
		key, 
		key_quest, 
		emit, 
		emit_quest
	};   


	SI_group SI[2];

	/*
	 * initialisation of system interface
	 */
	strcpy(SI[0].SI_id, "BIOS");
	SI[0].SI_fa = SI_BIOS_fa; 

	strcpy(SI[1].SI_id, "    ");
	SI[1].SI_fa = NULL; 


Zunchst wird das Funktionsfeld mit den Zeigern auf die passenden 
Funktionen gefllt. Dann wird das Feld aller SI's angelegt und 
anschlieend mit den Werten gefllt. Wie man sieht, wird der 
String 'BIOS' als Name und zugehrig der Zeiger auf das 
Funktionsfeld SI_BIOS_fa in die nullte SI-Struktur eingetragen.
Zustzlich sollte immer ein berzhliges SI existieren, das als 
Name einen String bestehend aus vier Leerzeichen hat. Dies ist 
aber erst bei der Verwendung zustzlicher SI neben 'BIOS' von 
Bedeutung, da ein solcher Eintrag dann das Ende der Liste der SI 
kennzeichnet.
Schluendlich mu nun noch die Adresse der SI in die 
FORTHPARAS-Struktur eingetragen werden, etwa so:


	forthparas.si	 = SI; 


4. Start des Systems 

Nachdem das System nun vorbereitet ist, gengt eigentlich ein 
Sprung an den Anfang des Codesegments, nachdem die Adresse der 
FORTHPARAS-Struktur auf dem Returnstack abgelegt wurde:


	typedef void cdecl FUNC(FORTHPARAS*); 
	(*(FUNC*)codeseg)(&forthparas); 


F68KANS sieht daraufhin erst einmal nach, ob der erste Zeiger im 
SI-Feld auf ein SI mit Namen 'BIOS' zeigt. Falls nicht, macht 
F68KANS gar nicht erst weiter. Ist das SI vorhanden, ldt F68KANS 
die vier darin enthaltenen Zeiger in interne Vektoren und benutzt 
sie fortan fr seine Ein- und Ausgaben, die mit KEY und EMIT 
durchgefhrt werden.
Anschlieend werden auf hnliche Art auch alle Speicherparameter 
entnommen und in die entsprechenden Register bzw. Variablen 
gesetzt. 

5. Nutzung erweiterter Funktionalitt

Viele Forth-Systeme leiden darunter, da sie im wesentlichen in 
sich abgeschlossene Systeme sind, die, wenn sie gut sind, Zugriff 
auf wesentliche System-Ressourcen ermglichen. Was aber passiert, 
wenn man z.B. eine vllig schicke Grafik-Bibliothek oder eine 
Datenbankschnittstelle, die als C-Bibliothek geliefert wird, 
benutzen mchte?
Fr diesen Fall bietet F68KANS die Mglichkeit, den Lader um die 
entsprechende Funktionalitt zu erweitern und diese somit fr 
Forth zu erschlieen. Von dieser Mglichkeit wurde auch von mir 
bei der Implementation von vielen Systemfunktionen (File, Float, 
...) ausgiebig Gebrauch gemacht, so da F68KANS eines der wenigen 
Forth-Systeme ist, die das Rad nicht allzu oft neu erfunden haben, 
sondern die Leistung, die viele, viele, bestimmt nicht dumme 
C-Programmierer in einer endlosen Zahl von Stunden z.B. in 
gepufferte I/O oder Float-Routinen gesteckt haben, fr sich 
ausnutzen.

Doch wie geht das nun?

Natrlich durch Hinzufgen weiterer 'System Interfaces'! 

Dazu mu man sich die bentigten Funktionen beschaffen und mit der 
weiter oben bereits beschriebenen Schnittstelle ausstatten. Hier 
vielleicht ein paar Beispiele aus der Anbindung an die 
Standard-C-Bibliothek:


	void *cdecl _fopen( char *filename, long len, 
						char *mode, long mlen )
	{ 
	char str1[256];
	char str2[256];

		memcpy( str1, filename, (size_t)len );
		str1[len] = '\0'; 	
		memcpy( str2, mode, (size_t)mlen );
		str2[mlen] = '\0'; 	
	
		return (void *)fopen( str1, str2 ); 
	}


	long cdecl _fputc( long ch, void *file )
	{ return (long)fputc( (int)ch, (FILE*)file ); }


	long cdecl _fputs( char *s, long len, void *file )
	{ 
	char str[256];

		memcpy( str, s, (size_t)len );
		str[len] = '\0'; 	
		return (long)fputs( str, (FILE*)file ); 
	}


	long cdecl _fread( void *ptr, long size, 
						long count, void *file )
	{ 
		return (long)fread( ptr, (size_t)size, 
							(size_t)count, (FILE*)file ); 
	} 



Auf hnliche Weise wie weiter oben bei 'BIOS' wird nun ein SI 
Namens 'CLIB' erzeugt und in die Liste aller SI's eingefhrt:



	SI_group SI[3];

	/*
	 * initialisation of system interface
	 */
	strcpy(SI[0].SI_id, "BIOS");
	SI[0].SI_fa = SI_BIOS_fa; 

	strcpy(SI[1].SI_id, "CLIB");
	SI[1].SI_fa = SI_CLIB_fa; 

	strcpy(SI[2].SI_id, "    ");
	SI[2].SI_fa = NULL; 


Im Gegensatz zu 'BIOS' mu aber nun auch auf Forth-Seite ein 
Mechanismus existieren, mit dem die Anwesenheit des neuen SI 
getestet und seine Funktionen auch genutzt werden knnen. Dazu 
dienen einige wenige, einfache Forth-Worte.

Das wichtigste drfte das definierende Wort 'systeminterface' 
sein. Es wird folgendermaen benutzt, um den Grundstein fr die 
Verfgbarkeit eines neuen SI zu legen.

	systeminterface initCLIB CLIB  CONSTANT CLIBBASE  


Dabei werden das auch spter sehr wichtige Wort 'initCLIB' sowie 
die Konstante 'CLIBBASE' definiert.
Das von 'systeminterface' unter Angabe des zur SI gehrigen Namens 
erzeugte Initialisierungswort (hier: initCLIB) dient spter immer 
dazu, festzustellen, ob der Lader ein SI mit diesem Namen 
bereitstellt, und, wenn dies der Fall ist, dessen Adresse zu 
bestimmen und abzulegen. Dies ist deshalb notwendig, weil die in 
der Folge erzeugten Forth-Worte, die auf die einzelnen 
SI-Funktionen zugreifen, nur den Index im Funktionsfeld des SI 
kennen, und deshalb die Basisadresse, die sich ja immer wieder 
ndern kann und nur vom Lader abhngt, zu ihrem korrekten 
Funktionieren dringend bentigen.

Um die Parameterbergabe auch auf Forth-Seite auf die Reihe zu 
bekommen, ist eine Parameterbeschreibung notwendig. Hierbei sind 
Ein- und Ausgabeparameter zu unterscheiden. Standardmig sind 
drei verschiedene Eingabeparameter mglich:

-	_n		Integerzahl
-	_a		Adresse
-	_s		String

Man beachte die Unterstriche!

Es gibt vier verschiedene Ausgabeparameter:

-	nothing		Keine Rckgabewert
-	outint		Integerzahl
-	outptr		Adresse
-	outstr		String


Strings sind sowohl bei Eingabe als auch Ausgabe gewhnliche 
Forth-Strings, d.h. sie werden durch eine Adresse und eine Lnge 
gekennzeichnet.

Um eine Funktion nun zu definieren, existiert das definierende 
Wort 'SI:'. Es legt ein Wort mit dem anzugebenden Namen an, das 
die zugehrigen Funktion aus dem SI aufruft und die 
Parameterbeschreibung auswertet.

Das geht so:


	CLIBBASE 0
			_n	SI: _fgetc	outint ( FILE* -- char )
		_n	_a	SI: _fgetpos outint ( FILE* long* -- ior )
	_a	_n	_n	SI: _fgets	outstr ( char* n FILE* -- c-addr u )
			_n	SI:	_fileno	outint ( FILE* -- handle )
		_s	_s	SI: _fopen	outint 
							( c-addr1 u1 c-addr2 u2 -- FILE* )
		_n	_n	SI:	_fputc	outint ( char FILE* -- char )
		_s	_n	SI:	_fputs	outint ( c-addr u FILE* -- len2 )   
	2DROP

(Zu diesem Beispiel ist zu bemerken, da 'FILE*', eigentlich ein 
Zeiger, auf Forth-Seite wie eine Integerzahl behandelt wird.) 

Die Funktion '_fputs' ist dann z.B. folgendermaen einzusetzen:

	S" Hallo Welt"  FILEID @  _fputs   . 


Die Konstante CLIBBASE wird danach nicht weiter verwendet, so da 
man auf ihre Definition sogar verzichten kann. 

Ich denke, da diese Beispiele die Benutzung der Schnittstelle mit 
der Parameterhandhabung bereits sehr gut deutlich machen.

Als einziger kritischer Punkt in dem ganzen Geschft ist darauf zu 
achten, da die Reihenfolge der Funktionen in der SI auf Lader- 
und auf Forth-Seite identisch sein mssen. 

Bevor man nun diese Definitionen wirklich benutzen kann, ist es 
wichtig, da das mit 'systeminterface' definierte 
Initialisierungswort (z.B. initCLIB, s.o.) UNBEDINGT ausgefhrt 
wird. Andernfalls droht der Systemabsturz. Das gilt insbesondere 
bei Systemen, deren Image mit 'SAVE-SYSTEM' abgespeichert wurde. 
Auch hier mu zur Laufzeit des Systems das Initialisierungswort 
ausgefhrt werden. An dieser Stelle bin ich selbst schon mehrfach 
gescheitert und habe dann damit angefangen, nach Fehlern in den 
Bibliotheken oder im SI zu suchen. Deshalb betone ich diesen Punkt 
hier so ausdrcklich. 



