The following article was printed in issue 4 1986 of the magazine „CT".
A base article how an RSX works.

Veränderlich

Systemerweiterungen unter CP/M-Plus

Hans-Peter Sauer
Eine der vielen neuen Eigenschaften von CP/M-Plus ist die Möglichkeit, den Betriebssystemkern modular zu erweitern. Hierzu dienen die 'festen Systemerweiterungen', original 'Resident System Extensions' (RSX) genannt. Eine RSX erlaubt dem Programmierer, eine oder mehrere beliebige BDOS-Funktionen zu erweitern, zu modifizieren und sogar neue Funktionen zu erzeugen.

Bekanntermaßen können CP/M-Programme die Systemfunktionen (BDOS-Funktionen) nutzen, indem sie mit festgelegten Parametern in den Prozessorregistern einen Unterprogrammaufruf zur Speicheradresse 0005h durchführen (CALL BDOS). Ist man jedoch aus irgendwelchen Gründen mit den vorhandenen Funktionen nicht zufrieden, besteht bei CP/M-Plus nunmehr auch offiziell die Möglichkeit, das Betriebssystem mit eigenen Routinen zu ändern oder zu erweitern. Dazu hat man sogenannte RSX-Module (Resident System Extensions, auf deutsch: feste Systemerweiterungen) eingeführt, die durch einen einfachen Trick so an das BDOS 'angeschlossen' werden, daß sie von einem Anwenderprogramm aus nicht mehr als eigenständige Module zu erkennen sind:

An der Adresse 0005h, also dort, wo ein BDOS-Call als erstes landet, steht normalerweise ein direkter Sprung zum Beginn des BDOS. Beim Laden eines RSX-Moduls wird dieser jedoch so verändert, daß der Aufruf anschließend am Anfang der RSX aufläuft. Der 'umgeleitete Sprung' ermöglicht somit dem RSX-Modul, alle künftigen BDOS-Aufrufe abzufangen und entsprechend den Vorgaben durch den Programmierer zu beeinflussen.

Um den Ladevorgang für RSX-Module möglichst einfach zu gestalten, bindet man sie an die Programme an, für die man sie benötigt. Dies geschieht mit dem CP/M-Dienstprogramm GENCOM, wobei sich jedem Programm eine oder mehrere RSX anhängen lassen. Die RSX werden mit dem Programm zusammen geladen und sind danach aktiv. Ob bis zum nächsten Warmstart oder 'für immer' (bis zum nächsten Kaltstart), entscheidet ein Flag in der RSX, dazu später mehr.

Die Idee zu meiner ersten RSX kam mir durch ein Spielprogramm. In diesem Programm gab es erstens keine Möglichkeit, den Spielverlauf zu unterbrechen und nach einer Pause mit dem Spiel fortzufahren, zweitens ertönte bei jedem nur erdenklichen Spielzustand der 'Beeper' meines Terminals.

Die notwendigen Routinen waren schnell geschrieben: 37 Zeilen Assembleranweisungen fangen alle BDOS-Aufrufe für die Funktionen 'Console Output' (BDOS-Funktion 2 und 6) sowie 'Console Input' (BDOS-Funktion 1 und 6) ab. Will das Programm ein Zeichen zur Konsole ausgeben, so vergleicht die RSX das Zeichen mit dem 'Klingel'-Code. Bei Übereinstimmung blockiert das RSX-Modul die Weitergabe dieses Zeichens und kehrt zum aufrufenden Programm zurück. Andernfalls wird das Zeichen an das BDOS weitergeleitet.

Fordert das Programm eine Konsoleingabe an, führt die RSX ihrerseits den zugehörigen BDOS-Aufruf durch und vergleicht alle vom BDOS zurückgegebenen Zeichen mit dem 'Pausezeichen' (zum Beispiel 'ESC'). Beim Empfang dieses Zeichens verzweigt die RSX zu einer 'Direct Console I/O' (BDOS-Funktion 6) mit 0FDh in Register E. Das BDOS wartet dann auf den nächsten Tastendruck, bis dahin wird das aufrufende Programm unterbrochen.

Durch die modulare Struktur des RSX-Konzepts ist es möglich, eine einmal definierte RSX an beliebige Programme anzubinden, sei es um ein bestehendes Programm zu erweitern oder die um Hauptfunktionen des Betriebssystems zu verändern.

RSX - wo?

Die an ein Programm angebundenen RSX werden also mit dem Programm zusammen in den Speicher geladen. Es dürfte einleuchten, daß das nicht alles gewesen sein kann. Denn schließlich brauchen die RSX ein sicheres Plätzchen, wo sie nicht überschrieben werden können, aber auch das auszuführende Programm nicht stören. Bei CP/M kann das nur das obere Ende der 'Transient Program Area' (TPA) sein.

Der Sprungbefehl auf Adresse 0005h dient ja nicht nur dazu, von einem Anwenderprogramm aus die BDOS-Funktionen aufzurufen. Sein Operand markiert zugleich das Ende des nutzbaren Speichers: Der Sprung führt zum ersten Byte des BDOS, ab hier ist keine TPA mehr.

So betrachtet erhält der 'umgeleitete Sprung' noch eine ganz andere Bedeutung. Auf diese Weise ist eine RSX mit ihrer Einbindung in den Sprung von Adresse 0005h zum BDOS automatisch überschreibgeschützt. Fragt ein Anwenderprogramm den Operanden ab (um das obere TPA-Ende zu bestimmen), findet es die Anfangsadresse der RSX - und 'denkt', es wäre die des BDOS.

Die Kehrseite der Medaille: Der Speicherplatz für RSX geht der TPA verloren. Aber normalerweise braucht man, was die Zahl der RSX-Bytes anbelangt, deshalb nicht gleich knausrig zu werden. Eine RSX ist meist konzentrierter Programmcode, und da ist 1 KByte schon ziemlich viel. Oft gibt es auch gar keinen Grund 'Platzangst' zu bekommen, beispielsweise wenn man eine Spezial-RSX für ein Programm erstellt, das die TPA gar nicht ausnutzt. Außerdem kann die Größe einer RSX ohnehin nur ganzzahlige Vielfache von 256 (100h) betragen.

Damit wäre geklärt, wo sich die RSX im Speicher aufzuhalten haben, aber noch nicht, wie sie dorthin gelangen. Diese Aufgabe fällt dem CP/M-Programmlader zu, der praktisch eine RSX für den 'Command Control Processor' (CCP) darstellt. Sie erinnern sich: Bei CP/M-Plus liegt der CCP wie ein Anwenderprogramm ab 100h im Speicher. Um nun nicht mit dem zu ladenden Programm ins Gehege zu kommen, muß der Programmabschnitt, der das Laden übernimmt, aus dem CCP ausgelagert werden. Auch dieses Modul landet im Speicherbereich unterhalb des BDOS, und zwar immer als erstes (vom BDOS aus gesehen). Erkennt nun der Lader an einem speziellen Vorspann, daß ein Programm eine oder mehrere RSX mit sich führt, so trennt er diese einzeln vom übrigen Programmcode, verschiebt sie ans Ende der TPA und 'verbiegt' den Sprungbefehl auf Adresse 0005h wie beschrieben. Dabei initialisiert er noch einige Bytes im sogenannten RSX-Präfix. Die erste RSX kommt direkt unter den CP/M-Lader zu liegen, alle weiteren unter die jeweils zuletzt geladene RSX. Abschließend wird der Programmcode richtig plaziert (durch den Vorspann beginnt er nicht mehr bei 100h sondern bei 200h) und das Programm gestartet.

RSX - wie?

Das RSX-Präfix (oft auch englisch mit 'e' geschrieben) sind die ersten 27 Bytes eines RSX-Moduls. Hier stehen diverse Informationen, die sowohl der Lader als auch die RSX selbst braucht. Einige davon muß der Programmierer festlegen, die anderen werden beim Laden gesetzt. Auch der CP/M-Programmlader besitzt dieses Präfix, das im einzelnen folgende Felder umfaßt:

SERIAL NUMBER
In diese sechs Platzhalter-Bytes kopiert der Lader die Seriennummer des vorliegenden CP/M. Dadurch enthalten die sechs Bytes vor dem Systemeinsprung, dessen Adresse bei 0006h steht, immer die Seriennummer, gleich, ob RSX vorhanden sind oder nicht. Dies dient der Kompatibilität mit den älteren CP/M-Versionen.

START
enthält die Sprunganweisung zum Beginn des RSX-Programmcodes. Diesen Befehl muß der Programmierer definieren.

NEXT
Bei diesem Sprungbefehl - er führt zum Beginn des nächsten RSX-Moduls oder zum BDOS - muß der Programmierer lediglich das Opcode-Byte setzen (0C3h), die Zieladresse bestimmt der Lader.

PREVIOUS
zeigt auf den Anfang des vorherigen RSX-Moduls oder auf die Adresse 0005h (wird vom Lader eingesetzt).

REMOVE
ist ein Flag, das der Programmierer setzen muß. Hat dieses Byte den Wert 0FFh, wird die betreffende RSX beim nächsten Aufruf des Programmladers über die BDOS-Funktion 59 (Load Overlay) aus dem System entfernt, zum Beispiel beim Warmstart. Ist das Flag gelöscht (00h), bleibt die RSX im System, bis entweder ein Kaltstart erfolgt oder die RSX selbst dieses Flag setzt.

NONBANKED
Noch ein Byte für den Programmierer. Mit diesem Flag legt er fest, ob die RSX immer geladen werden soll (00h) oder nur dann, wenn das vorliegende CP/M-Plus ein 'nonbanked System' ist (0FFh). So kann man zum Beispiel die in einem 'kleinen' System fehlenden BDOS-Funktionen mit einer RSX nachrüsten, die aber nur dann installiert wird, wenn es wirklich erforderlich ist.

NAME
Hier ist der Name des RSX-Moduls zu hinterlegen (maximal acht Zeichen, linksbündig, bei Bedarf mit Spaces aufzufüllen).

LOADER
markiert diejenige RSX, die direkt unterhalb des BDOS (0FFh) liegt. Dies ist immer der CP/M-Programmlader, bei allen übrigen RSX-Modulen wird das LOADER-Flag automatisch gelöscht (00h).

RESERVED
Für welchen Zweck, ist unbekannt - jedenfalls sind hier zwei Bytes freigehalten.

Wie zu Beginn erläutert, landen BDOS-Aufrufe zunächst in dem (den) RSX-Modul(en), ehe sie das BDOS erreichen. Diese als 'Preprocessing' bekannte Verfahrensweise erlaubt nicht nur, nicht vorhandene BDOS-Funktionen hinzuzufügen, sondern auch bestehende zu verändern oder völlig neu zu definieren. Im letzten Fall braucht man lediglich die zugehörigen RSX-Routinen mit einem Return-Befehl enden zu lassen statt mit einem Sprung zur nächsten RSX beziehungsweise zum BDOS.

Zumeist benutzt man RSX, um das BDOS zu manipulieren. Dazu muß eine RSX die übergebene BDOS-Funktionsnummer prüfen. Fühlt sie sich nicht angesprochen, so wird der BDOS-Aufruf durch einen Sprung zum NEXT-Feld im Präfix weitergereicht.

Wenn ein RSX-Modul selbst einen BDOS-Aufruf durchführen muß, ist auch dafür das NEXT-Feld zu verwenden. Nur so ist gewährleistet, daß das BDOS (eventuell nach Durchlaufen weiterer RSX) in jedem Fall erreicht wird. Der übliche 'CALL 0005h' kann unter Umständen zu einem bildschönen 'Hänger' führen, weil die RSX dann ja erneut durchlaufen wird.

Außerdem sollten RSX einen eigenen Stack einrichten, wie es auch das BDOS tut. Im Vertrauen darauf, daß der Stack ab dem BDOS-Aufruf nicht weiter belastet wird, ist er in vielen Anwenderprogrammen relativ knapp bemessen. Selbstverständlich sind die ursprünglichen Parameter vor Verlassen der RSX wiederherzustellen.

SERIAL:	db	0,0,0,0,0,0	; Hierher kopiert der Lader die
				; BDOS-Seriennummer
START:	jmp	RSXStart	; Sprung zum Beginn des RSX-Codes
NEXT:	jmp	$-$		; Sprung zum START des folgenden
				; Moduls (Adresse wird vom Lader
				; eingesetzt)
PREV:	dw	0		; Platzhalter fuer die Adresse des
				; vorhergehenden RSX-Moduls
REMOVE:	db	0ffh		; 'Remove'-Merker
NONBANK:db	0		; 'Nonbank'-Merker
NAME:	db	'RSX Name'	; Name des RSX-Modules
LOADER:	db	0		; 0FFh markiert den Lader
RESERVE:dw	0		; reserviert

RSXstart:			; Hier folgen die RSX-Routinen
		.
		.
		.
Mit diesen 27 Bytes beginnt jede RSX.

RSX - wozu?

Anfangs mag man meinen, der RSX-Technik seien schnell Grenzen gesetzt. Dieser Eindruck verliert sich in der Regel jedoch schnell, wenn man die ersten Gehversuche auf diesem ungewohnten Boden hinter sich hat. Tatsächlich sind die Möglichkeiten, dem BDOS mit RSX 'auf die Sprünge zu helfen', schier unbegrenzt. Im folgenden sind einige wenige Beispiele aufgeführt:

Eine RSX kann Parameter verändern, ehe der ursprüngliche BDOS-Aufruf weitergegeben wird. In diesem Fall ist die letzte Anweisung innerhalb des RSX-Moduls ein 'JMP NEXT'.

Sie kann einen BDOS-Aufruf durch einen oder mehrere andere BDOS-Aufrufe ersetzen und danach entweder per RET-Befehl zum aufrufenden Programm zurückkehren oder den Aufruf durch einen 'JMP NEXT' weitergeben.

Oder sie kann eine BDOS-Funktion komplett innerhalb der RSX nachbilden, ohne irgendeine BDOS-Funktion zu benötigen. So kann man zum Beispiel direkt auf spezielle Hardware zugreifen, die nicht ins CP/M eingebunden ist.

Der letztgenannte Fall reißt einen Aspekt an, von dem bisher noch gar nicht die Rede war: Per RSX kann man auch vorübergehende BIOS-Änderungen realisieren, etwa um das CP/M-Plus-BIOS kompatibel zum BIOS von CP/M 2.2 zu machen. Dann muß die RSX eine eigene BIOS-Sprungleiste enthalten und diese beim ersten Aufruf in das System 'einklinken', indem sie den Warmstartsprung auf Adresse 0000h 'verbiegt'. Bei der Gelegenheit kann die RSX auch gleich die erforderlichen Initialisierungen vornehmen. Danach sind die zugehörigen Routinen lahmzulegen. Sofern die RSX nicht auch BDOS-Funktionen beeinflussen soll, braucht sie dazu bloß das START-Feld mit einem 'JMP NEXT' zu überschreiben. Eins darf man bei der ganzen Aktion allerdings nicht vergessen - den ursprünglichen BDOS-Aufruf weiterzugeben.

Nach der ganzen Theorie zum Abschluß noch ein praktisches Beispiel, das zum einen sehr genau die Struktur eines RSX-Moduls zeigt und außerdem eine der Unverträglichkeiten zwischen CP/M 2.2 und CP/M-Plus beseitigt:

Unter CP/M 2.2 ist es zulässig, daß das Namensfeld in einem FCB Fragezeichen enthalten darf, auch wenn die aufzurufende BDOS-Funktion die Funktion 15 ist ('Open File'). In dem Fall wird das BDOS die erste Datei im Directory eröffnen, deren Name mit dem angegebenen übereinstimmt. Wenn nun ein Programm, das dies ausnutzt, unter CP/M-Plus gestartet wird, gibt das BDOS die Fehlermeldung '? in filename' aus und kehrt mit dem Parameter für 'file not found' zum aufrufenden Programm zurück.

Mit dem Beispiel-RSX-Modul kann man dieses unerwünschte Verhalten ausschalten. Es modifiziert die Systemfunktion 'Open File' wie folgt: Bei einem BDOS-Aufruf mit der Funktionsnummer 15 wird der übergebene FCB auf '?' im Namen überprüft. Enthält der Name kein '?', so wird der Aufruf unverändert an das BDOS weitergeleitet. Andernfalls wandelt die RSX den Open-Aufruf in einen Aufruf der BDOS-Funktion 17 um ('Search First"). Findet diese Funktion keine passende Datei, gibt die RSX die Meldung 'file not found' an das aufrufende Programm zurück. Ist Funktion 17 jedoch erfolgreich, überträgt die RSX den gefundenen Dateinamen anstelle des ursprünglichen in den FCB und ruft danach die Funktion 'Open File' auf. Das BDOS öffnet nun wie gewünscht die Datei und kehrt zum aufrufenden Programm zurück.

Natürlich muß das neuerstellte RSX-Modul auch getestet werden, um seine Funktionstüchtigkeit sicherzustellen. Hierzu dient das als zweites abgedruckte Testprogramm. Es fordert die Eingabe eines Dateinamens an, erstellt mittels der BDOS-Funktion 'Parse Filename' (Nummer 152) einen entsprechenden FCB und ruft dann Funktion 'Open File' auf. Ist das RSX-Modul eingebunden und man gibt einen Mehrfach-Dateinamen ein (ambiguous filename), dann wird die erste Datei aus dem Directory eröffnet, auf die die Beschreibung paßt. Ohne RSX erscheint wie üblich eine Fehlermeldung.

Werdegang

Damit man sich eines RSX-Moduls erfreuen kann, muß es in 'maschinenlesbarer' Form vorliegen. Die ersten Schritte dahin sind dieselben wie bei jedem Programm: Man erstellt den Quelltext und läßt ihn übersetzen. Ein geeigneter Assembler ist zum Beispiel der bei CP/M-Plus mitgelieferte RMAC-Makroassembler, man kann aber auch andere verwenden, sofern sie den Objekt-Code in einer REL-Datei ablegen.

Die Objekt-Datei wird dann mit dem Linker von Digital Research (LINK) weiterbehandelt. Dieser ist mit der Option [OP] aufzurufen und erzeugt aus der REL- eine PRL-Datei (Page Relocatable). Der Unterschied zwischen beiden Arten verschiebbaren Objekt-Codes besteht darin, daß man die Anfangsadresse für den lauffähigen Code bei einer REL-Datei frei wählen kann, bei einer PRL-Datei dagegen muß das niederwertige Byte der Adresse Null sein. Das PRL-Format hat gegenüber dem REL-Format den Vorteil, daß der Aufwand für das Verschiebeprogramm sehr gering ist. Das PRL-Format ist übrigens der Grund dafür, daß die Größe einer RSX nur in ganzen 256-Byte-Blöcken 'gehandelt' wird.

Der vorletzte Schritt ist, die PRL-Datei in eine RSX-Datei umzubenennen. Ohne dies führt der letzte Schritt, das Anbinden der RSX an ein Programm mit Hilfe von GENCOM, nicht zum Erfolg.

Literatur

CP/M Plus Programmer's Guide, Digital Research, 1982
Garry M. Silvey, Resident System Extensions, Dr. Dobbs Journal, Juli 1984

Eine einfache RSX, um die 'Open File'-BDOS-Funktion von CP/M-Plus auf den Stand von CP/M 2.2 'zurückzuwerfen'. [8080 and Z80 source]
Ein Testprogramm für die Beispiel-RSX. [8080 and Z80 source]

Scanned by Werner Cirsovius
December 2002, June 2013
© Heise Verlag