CP/M Plus ­ wie es drinnen aussieht ...

Das Betriebssystem CP/M Plus besteht wie so ziemlich alle Systeme aus vielen Tabellen. Wer diese Systembereiche kennt und weiß, welche Informationen da verborgen sind, versteht das System besser und kann es dann auch effizienter einsetzen. Wir wollen uns deshalb heute mit der sog. »Zero Page« (= »Nullseite«) beschäftigen.

Wenn der Mikroprozessor Z80A in unserer Joyce aktiv ist, dann kann er auf maximal 64 KB (= 65536 Bytes) zugreifen. Das Betriebssystem CP/M Plus unterteilt diesen Bereich in »Pages« (= »Seiten«), die jeweils 256 Bytes umfassen. Warum gerade eine so krumme Zahl wie 256? Nun, als hexadezimale Zahl ist 256 = 100h, also gar nicht so krumm...

Wenn wir auf unserer Joyce ein Programm aufgerufen haben, dann ist der dem Anwenderprogramm zugängliche Speicher wie folgt belegt:

        ________________
        FFFFh = 65535        oberes Ende
        ...
        FC00h = 64512        Beginn des BIOS
        ...
        F606h = 62982        Beginn des BDOS
        ...
        C000h = 49152        Beginn Common Area
        ...
        0100h = 00256        Programmanfang
        ...
        0080h = 00128        Beginn DMA
        ...
        0000h = 00000        Beginn Zero Page
Diese Nullseite ist immer vorhanden und darf von unserem Programm nie überschrieben werden; ein wohl erzogenes Anwenderprogramm beginnt deshalb stets oberhalb von 0100h, also nach der Nullseite. Ein Anwenderprogramm darf den gesamten Speicher ab der Nullseite bis zum Beginn des BDOS belegen. Ab hier ist für Anwenderprogramme absolute Sperrzone, da ab dieser Adresse die Einsprungspunkte in die Systemroutinen liegen.

Ganz am Anfang der Nullseite befinden sich zwei Sprungbefehle; der erste springt direkt in die BIOS-Routine »Warmstart«; damit wird nämlich ein aktuelles Programm beendet und der CCP (= »Command Console Processor«) meldet sich wieder und erwartet die Eingabe eines neuen CP/M-Befehles.

Dies können auch Basic-Coder ausnutzen: Anstatt »SYSTEM« können wir auch das Basic-Programm mit »warmstart=0:CALL warmstart« beenden.

Der zweite Sprungbefehl steht ab der Adresse »5« und führt direkt in die BDOS-Funktion mit der Nummer »0«, mit der ebenfalls ein Programm ordentlich beendet werden kann. Aber in der Nullseite stehen noch viel mehr Informationen, wie folgende Tabelle zeigt:


Von        Bis        Bedeutung

0001h        0003h        Einsprungsadresse ins BIOS
0006h        0007h        Beginn des BDOS
0008h        003Ah        Restartvektoren
003Bh        004Fh        unbenutzt
0050h        0050h        Nummer des Laufwerkes (1..16)
0051h        0052h        Adresse des ersten Paßwortes
0053h        0053h        Länge des ersten Paßwortes
0054h        0055h        Adresse des zweiten Paßwortes
0056h        0056h        Länge des zweiten Paßwortes
0057h        005bh        unbenutzt
005Ch        007Bh        Dateileitblock der ersten Datei
006Ch        007Bh        Dateileitblock der zweiten Datei
007Ch        007Ch        Satzzähler für die erste Datei
007Dh        007Fh        Satzposition der ersten Datei
0080h        00FFh        Standard-Ein-/Ausgabe-Bereich
Da einige sich vielleicht unter manchen Begriffen nichts vorstellen können, wollen wir etwas näher darauf eingehen:

Restartvektoren
Dies hört sich ja ganz geheimnisvoll an, ist aber ein Relikt aus den 8080-Zeiten. Der Mikroprozessor 8080 von Intel war ja der Vorgänger des Z80 von Zilog. Der 8080 war weniger leistungsfähig, vor allem, was die Möglichkeit von Unterbrechungen angeht. Für die Behandlung der Peripherie stellte man deshalb besonders schnelle Einsprungbefehle zur Verfügung ­ die sog. »Restart-Befehle«. Diese Befehle sind nur ein Byte lang, funktionieren aber wie ein CALL-Befehl, mit dem ein Unterprogramm aufgerufen werden kann.

Diese Restart-Befehle besitzen aber einen großen Nachteil: sie springen an feste Adressen. So verzweigt z.b. »RST 0h« zur Adresse »0000h«, »RST 8h« zur Adresse »0008h« etc. Es stehen deshalb nur kümmerliche acht Bytes für die Behandlung einer Unterbrechung zur Verfügung.

Der Z80-Prozessor wurde deshalb erweitert: man verpaßte ihm gleich drei verschiedene Unterbrechungsmodi: »IM 0«, »IM 1« und »IM 2«. Der »Interrupt Modus 0« (= IM 0) funktioniert wie beim 8080. Unsere Joyce arbeitet aber im Modus 1: Immer wenn eine Unterbrechung eintritt (z.b. durch Tastatureingabe), dann wird der Befehl »RST 38H« ausgeführt. An dieser Stelle muß nun in die Analyse-Routine verzweigt werden, denn das Betriebssystem muß ja schließlich feststellen, welcher Peripherie-Baustein sich da gemeldet hat.

In der Nullseite seht also an der Adresse 0038h ein Sprungbefehl in die Unterbrechungsbehandlung des Betriebssystems. Da der Timer alle 1/50  Sekunden sich auch hier meldet, können wir uns an dieser Stelle »vordrängeln« und eigene »Interrupt-gesteuerte« Routinen einbauen (z.b. eine permanente Zeitanzeige).

Die anderen RST-Adressen ab 0008h sind bei der Joyce unbenutzt. Wir können hier also kleine Routinen unterbringen. Das ist vor allem für Basic-Coder von Interesse, weil wir dann unsere (kleinen) Assemblerprogramme mittels POKE in die Nullseite schreiben können. Als Beispiele sind zwei kleine Basic-Programme abgedruckt:

  • HARDCOPY.BAS: damit wird direkt von BASIC aus in die Hardcopy-Routine verzweigt; ihr braucht also nicht mehr den Anwender auffordern, die Tasten SHIFT, EXTRA und PTR zu drücken...
  • PRTMENU.BAS: hier wird von BASIC aus das Drucker-Menü in der untersten Zeile aufgerufen; damit kann dann die gewünschte Schriftqualität eingestellt werden.
Beide Programme laufen aber nur unter der Version CP/M Plus 1.4, da die System-Routinen direkt angesprungen werden.

HARDCOPY.BAS

LISTING >HARDCOPY<, REMARK = >REM<.

100        REM HARDCOPY.BAS: ruft mittels XBIOS die Hardcopy-Routine auf
110        DEFINT a-z
120        REM Maschinencode ab Adresse &H0016 installieren
130        hardcopy=3D16
140        RESTORE 210
150        FOR offset=3D0 TO 5
160        .. READ code.byte$
170        .. code=3DVAL("&H"+code.byte$)
180        .. POKE hardcopy+offset,code
190        NEXT offset
200        PRINT "Hardcopy-Routine ist installiert - Aufruf: CALL hardcopy"
210        REM Maschinencode
220        DATA CD,5A,FC...... :REM call XBios
230        DATA 72,14......... :REM defw KM_PTR_COPY
240        DATA C9............ :REM ret
250        REM Ende Maschinencode
260        REM
270        REM Hardcopy von Basic aus aufrufen
280        PRINT "Test: Hardcopy von Basic aus aufrufen"
290        PRINT "Papier in Drucker einlegen..."
300        CALL hardcopy
310        PRINT "Test beendet"
320        REM
330        REM EOF: HARDCOPY.BAS

PRTMENU.BAS
LISTING >PRTMENU <, REMARK =>REM<.

100        REM PRTMENU.BAS: ruft mittels XBIOS das Drucker-Menü auf
110        DEFINT a-z
120        REM Maschinencode ab Adresse &H0008 installieren
130        prtmenu=3D8
140        RESTORE 220
150        FOR offset=3D0 TO 5
160        .. READ code.byte$
170        .. code=3DVAL("&H"+code.byte$)
180        .. POKE prtmenu+offset,code
190        NEXT offset
200        PRINT "Printer-Menü-Routine ist installiert - Aufruf: CALL prtmenu"
220        REM Maschinencode
230        DATA CD,5A,FC...... :REM call XBios
240        DATA 6F,14......... :REM defw KM_PTR_MODE
250        DATA C9............ :REM ret
260        REM Ende Maschinencode
270        REM
280        REM Printer-Menü von Basic aus aufrufen
290        PRINT "Test: Printer-Menü von Basic aus aufrufen"
300        PRINT "Druckereinstellung mit EXIT beenden..."
310        CALL prtmenu
320        PRINT "Test beendet"
330        REM
340        REM EOF: PRTMENU.BAS

Dateileitblock (FCB)
CP/M Plus sieht ja vor, daß wir beim Programmaufruf gleich Parameter mitgeben können; so könnte z.B. ein Kopierprogramm mit »COPY eingabe ausgabe« aufgerufen werden. Da jedes Programm eine Datei erst dann bearbeiten kann, wenn zuvor ein sog. Dateileitblock (= FCB, File Control Block) angelegt wurde, baut der CCP gleich für die ersten beiden übergebenen Namen solche Bereiche in der Nullseite auf. Wir brauchen also keine großen Verrenkungen mehr anzustellen, sondern können die fix und fertig zubereiteten FCB-Bereiche direkt in unser Programm übernehmen.

Standard-Ein-/Ausgabe-Bereich (DMA)
Die zweite Hälfte der Nullseite ­ also genau 128 Bytes ­ ist als Pufferbereich für die Ein-/Ausgabe gedacht. Unmittelbar nach dem Programmstart stehen hier auch die übergebenen Kommandoparameter:
  1. Im Byte an der Adresse 0080h steht, aus wieviel Zeichen unsere Parameter bestehen. Wenn bei 0080h eine Null steht, dann haben wir keine Parameter übergeben.
  2. Wurden Parameter übergeben, dann folgt ein Leerzeichen und dann der Kommandostring, der mit einem Nullbyte abgeschlossen ist.

Für Basic-Coder ist dieser DMA-Bereich besonders interessant, weil das Mallard-Basic selbst einen eigenen Ein-/Ausgabebereich reserviert. Der Standard-DMA-Bereich von 128 bis 255 wird nicht verwendet und steht uns deshalb für etwaige Assemblerroutinen zur Verfügung. Als Beispiel ist das Programm FASTWRIT.BAS abgedruckt, mit dem die PRINT-Ausgabe um etwa 25 % beschleunigt wird.

FASTWRIT.BAS
LISTING >FASTWRIT<, REMARK =>REM<.

100        REM FASTWRIT.BAS: mittels XBIOS PRINT um 25 % beschleunigen
110        DEFINT a-z
120        REM Maschinencode ab Adresse &H0080 installieren
130        fastwrit=3D128
140        RESTORE 210
150        FOR offset=3D0 TO 5
160        .. READ code.byte$
170        .. code=3DVAL("&H"+code.byte$)
180        .. POKE fastwrit+offset,code
190        NEXT offset
200        PRINT "FastWrit-Routine ist installiert - Aufruf: CALL fastwrit"
210        REM Maschinencode
220        DATA CD,5A,FC...... :REM call XBios
230        DATA 48,05......... :REM defw TE_TEXT_OUTPUT
240        DATA C9............ :REM ret
250        REM Ende Maschinencode
260        REM
270        REM schnelle PRINT-Ausgabe installieren
280        OPTION PRINT=3Dfastwrit
290        PRINT CHR$(27)"E"CHR$(27)"H"
300        FOR i=3D1 TO 12
310        .. PRINT SPACE$(i*3);"FASTWRIT: schnelle PRINT-Ausgabe ist installiert"
320        NEXT i
330        OPTION PRINT
340        FOR i=3D12 TO 1 STEP -1
350        . PRINT SPACE$(i*3);"schnelle PRINT-Ausgabe ist wieder abgeschaltet"
360        NEXT i
370        PRINT "Test beendet"
380        REM
390        REM EOF: FASTWRIT.BAS
Wer nun selbst die Nullseite analysieren möchte, für den wurde das Pascal Programm ZEROPAGE.PAS geschrieben. Es gibt in aufbereiteter Form die wichtigsten Informationen in der Nullseite aus. Wer z.B. »ZEROPAGE A:EINGABE.DAT;PASS1 B:AUSGABE.DAT;PASS2« startet, kann dann leicht herausfinden, wo das CP/M Plus die angegebenen Paßwörter unterbringt.

Auf der PD-Diskette befindet sich noch ein weiteres Programm BIOSADDR.PAS, das die Adresse der verschiedenen BIOS-Routinen auflistet ­ das ist aber vielleicht Stoff für einen weiteren Artikel...

DangSoft

Abgedruckt in Klubzeitung Nr. 38