Der folgende Text ist ein Auszug, der sich auf die Dateigröße und den freien Laufwerksplatz unter CP/M Plus bezieht. Das Original wurde in der Ausgabe 35 vom November/Dezember 1988 des Computer Journal veröffentlicht.

Erweitertes CP/M
ZSDOS und Dateisysteme
Von Bridger Mitchell

The Computer Journal, Ausgabe 35
Wiedergabe mit Erlaubnis
des Autors und Herausgebers

Bridger Mitchell schrieb die CP/M Plus Variante Z3PLUS. Z3PLUS schafft ein "Norm-Environment", also eine TCAP (Terminal-Definition), wie unter UNIX, die automatisch, also uninstalliert, von ZCPR-Programmen benutzt werden kann und selbst die vorhandenen Block-Grafik-Zeichen nutzen kann.

Auch wenn in der Literatur etliche englische Fachausdrücke verwendet werden, habe ich dennoch versucht, diese ins Deutsche zu übersetzen. Da sich aber nicht alle optimal ins Deutsche übersetzen lassen, findet sich am Ende eine Zusammenstellung einiger Ausdrücke.

Text gelöscht. Der gelöschte Text befasste sich mit dem ZSDOS-System.

Der zentrale Inhalt dieses Artikels ist das CP/M Dateisystem. Betriebssysteme teilen die Organisation und Pflege eines Dateisystems von der Speicherung und dem Abrufen von Daten auf Datenträgern. Dateien werden meistens auf magnetischen Datenträgern (Disketten oder Festplatten) abgelegt und der Teil des CP/M Betriebssystems, der zuständig ist für das Dateisystem, wird in der Tat Basic Disk Operating System (BDOS1) genannt.

Im Gegensatz dazu werden die Aufgaben auf unterer Ebene, das eigentliche Schreiben auf und Lesen vom Datenträger übertragen an einen Disktreiber, Code, der Teil des BIOS2 ist — das Basic Input/Output System, das die Einzelheiten der spezifischen Hardware des Hostrechners kennen muss.

Die Trennung in Funktionen des Dateisystems und hardware-spezifische Funktionen ist wesentlich für die Entwicklung jedes bedeutenden Betriebssystems und hat tiefgreifende Auswirkungen.

Erstens: dies ermöglicht den Einsatz derselben Programme auf unterschiedlichen Computern mit unterschiedlichen Laufwerken, die ausgelegt sind, um mit demselben Betriebssystem zu arbeiten.

Zweitens: hält man den logischen Aufbau einer "Disk" und die physikalische Ausführung in unterschiedlichen Ebenen, so kann eine große Vielfalt von Speichermedien mit demselben Dateisystem Verwendung finden. Eine RAM-"Disk" dreht sich schließlich nicht mit 300 U/min und ein Kasettenband oder lokales Netzwerk ist auch kaum eine herkömmliche Disk. Jedoch aus Sicht eines Programmes und des BDOS ist eine Datei eine Datei ist eine Datei.

Drittens: mit einigen Erweiterungen des Betriebssystems ist es möglich unterschiedliche Dateisysteme auf demselben Computer einzurichten. Zum Beispiel laufen einige FORTH-Betriebssysteme auf CP/M und bieten gleichermaßen Zugriff auf FORTH-Datei-"Screens" und CP/M-Dateien. Im Unterschied dazu unterstützt DosDisk direkten, transparenten Programmzugriff auf MSDOS Dateien in einem CP/M Umfeld.

 

Die frühen CP/M Rechner verfügten nur über ein einziges Format, dem Single-Sided Single-Density 8" IBM "Standard" - einseitig (SS), einfache Dichte (SD) - ohne Berücksichtigung weiterer Formate. Als dann andere Formate mit höherem Durchsatz und größerer Kapazität auf den Markt kamen, wurden diese im BIOS fest programmiert. Jedes neue Format benötigte eine erneute Programmierung und damit die Erzeugung eines neuen Systems.

Heute leidet CP/M unter einem Übermaß an physikalischen Floppy Disk Formaten. Es hat den Anschein, dass sich jeder Hersteller gezwungen sieht, seinen eigenen Aufkleber auf ein weiteres, nicht kompatibles Format zu kleben, so dass es nun über 100 verschiedene Möglichkeiten gibt, dieselbe Datei auf eine 5¼" Diskette abzuspeichern! Dies hat auch eine Art Identitätskrise hervorgebracht, weil es nicht immer möglich ist, durch magnetisches Lesen ihrer Daten das eindeutige Format einer Diskette zu bestimmen

Die meisten modernen BIOS Versionen sind erhaben über diesen Sumpf durch Flexibilität und Stufe von Intelligenz. Sie können einen Satz von "bekannten" Formaten erkennen und diese automatisch an die Diskette in jedem Laufwerk anpassen. Zusätzlich erlauben sie mit einem externen Dienstprogramm das Laufwerk auf ein "Fremd"-Format zu setzen, eins, das das BIOS nicht durch seine eingebauten Daten erkennt, das aber dem Dienstprogramm bekannt ist.

Text gelöscht. Der gelöschte Text befasste sich mit BIOS-Versionen und Hilfsprogrammen der Firma Plu*Perfect Systems.

Jede Dateianordnung hat zwei grundlegende Beschaffenheiten — eine Methode zur Benennung von Dateien and eine Methode zur Bereitstellung von Platz für die zu speichernden Daten.

Jede Datei hat einen eindeutigen Namen innerhalb des Bereiches für Dateinamen auf der Disk. (Unter CP/M bedeutet der Dateinamenbereich eine Benutzernummer, User Number, unter MSDOS und UNIX ein Unterverzeichnis). Neben dem Namen gibt es typischerweise eine Reihe von Dateiattributen, die den Dateizugriff sowie möglicherweise auch Datenstempel regeln.

Die Speicherung für die Dateien wird in Blöcke verteilt — Stücke von 128 oder mehr Datenbytes. Jedem Dateinamen ordnet das Dateisystem eine sortierte Liste von Blöcken sowie die komplette Dateigröße zu.

Das Dateisystem muss diese Information systematisch für jede Datei auf der Diskette pflegen. Dafür braucht es ein Verzeichnis (Directory) der Dateinamen, eine freie Liste (free list) der unbenutzten Datenblöcke und eine Belegungsliste (allocated list) der Blöcke, die von den Dateien benutzt werden.

Das Verzeichnis benötigt (mindestens) einen Eintrag für jeden Dateinamen. Der Eintrag beinhaltet üblicherweise Rechte oder Eigenschaften, die den Zugriff auf die Datei selbst regeln und vielleicht die Datenstempel für die Datei. Außerdem sind Verweise auf die Datenblöcken der Datei enthalten.

Die freie Liste ist eine Art von Datenstruktur, die anzeigt, welche Blöcke auf der Disk nicht in Gebrauch sind und zum Schreiben von Daten zur Verfügung stehen. Auf einer neuen Disk stehen alle Blöcke zur Verfügung. Keiner ist reserviert für das Inhaltsverzeichnis, den Boot Code oder andere Verwendungen durch das Betriebssystem. Sobald eine Datei geschrieben wird, werden Blöcke aus der freien Liste in die Belegungsliste übertragen und der Datei zugewiesen.

Bisher wurde nichts gesagt darüber, wie das Verzeichnis und die Belegungsliste tatsächlich abgelegt werden. Dies sind grundlegende Entscheidungen, die der Entwickler des Betriebssystems fällt. Es ist aufschlussreich, wie sich diese Entwicklungen unterscheiden können.

Bei MSDOS liegt die Blockliste kodiert in einer File Allocation Table (FAT). Die FAT hat einen Eintrag für jeden Datenblock auf der Disk (bei MSDOS Cluster genannt). Ein Eintrag zeigt an, dass der Block frei ist (und somit ein Teil der freien Liste), dass er einer Datei zugewiesen oder anderweitig reserviert ist.

Die FAT ist wie folgt kodiert um zwei Funktionen abzudecken — sie erfasst die belegten und freien Blöcke und zeigt an, welche Blöcke zu welchen Dateien gehören. Blöcke, die zu einer Datei gehören, bilden eine verkettete Liste (linked list). Jeder Eintrag in der FAT ist ein Zeiger auf den nächsten Block in der Liste für diese Datei und der letzte Eintrag ist eine spezielle Ende-der-Liste Markierung.

Der Eintrag in das MSDOS Verzeichnis besteht lediglich aus einem Zeiger auf den ersten Block der Datei. Die restlichen Blöcke erhält man durch Verfolgung der verketteten Liste in der FAT. Die FAT selber ist auf der Disk abgelegt und das MSDOS System hält eine Kopie davon im Arbeitsspeicher. Somit existieren zwei unterschiedliche Datenstrukturen auf einer MSDOS Disk — die FAT (die tatsächlich doppelt abgelegt ist) und das Verzeichnis.

Unter CP/M gibt es einen unterschiedlichen Ansatz — im Verzeichniseintrag stehen sowohl die Informationen über die Belegung als auch über den Dateinamen. Jeder Verzeichniseintrag enthält einen Satz von Blocknummern für die Daten und es gibt keine Datei-Belegungstabelle. Um die Datenblocks einer CP/M-Datei zu ermitteln, sucht das System den ersten Verzeichniseintrag und liest daraus die Blocknummern.

Wo steht die freie Liste für CP/M? Sie steht implizit im Verzeichnis. Wenn eine Disk angemeldet wird, liest sich das CP/M BDOS durch das Verzeichnis der Disk und merkt sich jeden Block, der einer Datei zugewiesen ist. Es verschlüsselt diese Information ein einem Belegungsvektor (Allocation Bitmap) für die Disk, wobei jeweils ein Bit für jeden genutzten Block gesetzt wird. Die nicht gesetzten Bits entsprechen dann den freien Blöcken.

Text gelöscht. Der gelöschte Text befasste sich mit dem UNIX Betriebssystem.

Ein ewiges Desaster mit frühen CP/M Programmen war - berühmt und undurchsichtig - das Schreiben einer Datei auf eine fast volle Disk. War während des Schreibens nicht mehr genug Platz vorhanden, wurde das Programm abgebrochen und die kostbaren Daten waren für immer verloren. Natürlich bricht ein gut geschriebenes Programm nicht ab, wenn ein BDOS Fehler auftritt; es räumt die unvollständige Datei auf, erlaubt dem Nutzer die Disk zu wechseln, setzt das Disk System zurück und schreibt die Datei neu.

Aber ein gut ausgearbeitets Programm würde niemals versuchen, auf die fast volle Disk zu schreiben. Vor dem Schreiben wird es stattdessen bestimmen, ob genügend Platz auf der Disk verbleibt, um die Datei aufzunehmen.

Dafür muss das Programm die gesamte Anzahl der freien Blöcke bekommen. Das ist naturgemäß eine Aufgabe für das Disk Betriebssystem und unter CP/M Plus gibt es dafür einen Systemaufruf (46). Allerdings passte dieser nicht auf die System-Spuren eines Original 8" CP/M 2.2 Systems. Deshalb gibt es im BDOS einen anderen Systemaufruf (27), der den Laufwerksvektor zurückgibt, womit das Programm selbst die freien Blöcke ermitteln muss.

Bild 1 zeigt die Z80 Routine, get_freek, die den freien Platz in Kilobytes des aktuell angemeldeten Laufwerks zurückgibt. Es ist übertragbar — es arbeitet unter CP/M 2.2, CP/M Plus und sogar mit einer MS-DOS Disk, wenn DosDisk ausgeführt wird. Die Routine enthält Beiträge von Jay Sage, Joe Wright und anderen. Sie wird, in leicht veränderter Form, eingesetzt im Befehl SP (Space) unter Z3PLUS und NZ-COM.

Diese Routine bestimmt zunächst die aktuelle CP/M Version. Wird CP/M Plus gefunden, erledigt das BDOS die ganze Arbeit. Tatsächlich ist das notwendig, weil in den meisten CP/M Plus Versionen der Belegungsvektor in einer anderen Speicherbank abgelegt ist, und deshalb nicht einfach durch das Programm zugreifbar ist. (Wenn die Routine versucht, die Adresse des Vektors zu verwenden, fügt es Bits vom Programm oder Daten aus dem Teil des Hauptspeichers hinzu, was zu einem fehlerhaften Resultat führt).

Die Funktion 46 von CP/M Plus gibt den freien Platz der Disk zurück als eine 24-Bit Zahl, abgelegt in den ersten drei Bytes der DMA, in Einheiten von 128-Byte Rekords. Um das Unterprogramm get_freek zu nutzen, setzt dieses die DMA-Adresse auf den Zwischenspeicher (Adresse 80h) und ruft die Funktion 46 auf. Der Code zur Division durch 8 (Label div) berechnet daraus die Einheiten in kiloBytes.

Bei CP/M 2.2 wird zuerst die Funktion 31 aufgerufen, um einige Parameter für das angemeldete Laufwerk zu holen — den Blockschiebefaktor (block-shift factor), die Extentmaske (extent mask), und die maximale Anzahl von Blöcken auf dem Datenträger. Dann wird die Funktion 27 zur Ermittlung der Adresse des Vektors aufgerufen. Der Code beim Label "cntfree" zählt dann die Anzahl nicht gesetzter Bits in der Bitmap im Register DE.

Da jeder Block das Vielfache von 1K (1024 = 210 Bytes) darstellt, multipliziert der Code beim Label "free2k" die Anzahl freier Blöcke mit der Länge eines Datenblocks. Der Blockschiebefaktor ist der Logarithmus zur Basis 2 der Anzahl von 128-Bytes Rekords in einem Datenblock. Mit anderen Worten: er ist der Exponent der folgenden Gleichung:

Blocklänge in Rekords = 2Blockschiebefaktor

Ist die Blocklänge 1K (8 Rekords), dann beträgt der Blockschiebefaktor 3 (d.h. 8 = 23) und die Anzahl freier Blöcke steht bereits als 1K Einheit. Andernfalls wird multipliziert mit der Zahl K in einem Block; dies ist ein einfaches 16-Bit Linksschieben durch Verdopplung des Ergebnisses in Register HL (blkshf-3)-mal.

Ein CP/M Verzeichniseintrag enthält die folgenden Komponenten:

Nutzernummer - eine logische Aufteilung des Datenträgers (Disk)
Dateiname
Dateiattribute
Nummer des Verzeichniseintrages
Größe (der Teil) der Datei indiziert durch diesem Eintrag
die Anzahl Datenblöcke in diesem Eintrag

Ein einzelner Verzeichniseintrag kann entweder 16 8-Bit Blocknummern oder 8 16-Bit Verzeichnisnummern enthalten. Die Größe eines CP/M Datenblocks ist entweder 1K, 2K, 4K, oder 16K Bytes (der Blocking Faktor ist Teil der Festlegung des Disk Formats), die großen Blöcke benötigen 16-Bit Werte. Darum kann der Wert für einen einzelnen Verzeichniseintrag von maximal 16*1K bis 8*16K = 128K Bytes an Daten betragen, abhängig vom Blockfaktor der Disk.

Natürlich kann eine Datei größer sein als die Anzahl an Bytes, die in einem einzigen Verzeichniseintrag aufgenommen werden können. Für diesen Fall erzeugt CP/M zusätzliche Verzeichniseinträge für weitere Datenblöcke. Diese Einträge haben denselben Dateinamen, dieselbe Nutzernummer und dieselben Attribute wie der anfängliche Eintrag, jedoch besitzen sie einzigartige Nummern für den Verzeichniseintrag. (Im Gegensatz zu MSDOS, das einen einzigen Verzeichniseintrag hat, dafür aber eine längere verkettete Liste von FAT Clustern für eine große Datei.)

 

Die eigentliche Nummerierung der CP/M Verzeichniseinträge ist etwas umständlich, deshalb wird sie später besprochen. Zuerst sollen Einzelheiten gezeigt werden. Angenommen, es gäbe eine große Datei und man betrachtet zuerst, was das Betriebssystem macht, wenn ein Anwendungsprogramm die Datei liest.

Als erstes ruft das Programm das BDOS auf, um die Datei zu öffnen, deren Name im File Control Block (FCB3) angegeben ist. Das CP/M BDOS sucht nach dem allerersten Verzeichniseintrag und speichert die gefundenen Daten mitsamt den Datenblocknummern in den FCB des Aufrufers.

Danach ruft das Programm wiederholt das BDOS auf, um die Datei von Anfang an nacheinander (sequentiell) zu lesen. Das (CP/M 2.2) BDOS holt die erste Datenblocknummer aus dem FCB, wandelt diese um in Spur- und Sektornummer, und ruft das BIOS auf, um einen 128-Byte Rekord zu lesen. Danach erhöht es die Sektornummer (unter Berücksichtigung des Endes einer Spur) und ruft das BIOS erneut auf. Dies wird wiederholt für die Anzahl von Rekords in einem Datenblock (8 in einem 1K Block, usw.) Dann holt es die zweite Datenblocknummer aus dem FCB, wandelt sie in Spur/Sektor und liest eine weiter Reihe von Rekords.

Schließlich (nach Vearbeitung von 8 oder 16 Blöcken) sind alle Datenblöcke des ersten Verzeichniseintrags verabeitet und das BDOS muss nach dem nächsten Eintrag suchen und diesen einlesen. (An diesem Punkt kann man von einem physikalischen Laufwerk häufig die Bewegung des Lesekopfes hören, wenn er sich zurück zur Verzeichnisspur bewegt; diese zusätzliche Bewegung verlangsamt den Zugriff auf große CP/M Dateien deutlich.) Das BDOS wiederholt dann den Vorgang der Spur/Sektorberechnung und den BIOS Aufruf zum Lesen der Rekords.

 

Das Schreiben einer Datei bedeutet die Umkehr dieser Schritte mit einigen zusätzlichen Funktionen, weil der Platz auf der Disk zugewiesen werden muss. Es wird nun angenommen, dass eine neue Datei geschrieben wird.

Zuerst ruft das Programm das BDOS auf, um eine Datei anzulegen, deren Name im FCB angegeben ist. Das BDOS sucht im Inhaltsverzeichnis nach einem leeren (ungenutzten) Verzeichniseintrag. Dann wird der neue Dateiname in diesem Eintrag gespeichert und die Blocknummern auf Null gesetzt.

Nun betrachten wir, was das BDOS machen muss, wenn das Programm sequentiell in die Datei schreibt. Zuerst muss das BDOS einen freien Datenblock auf der Disk finden. Dazu wird seine freie Liste (der Belegungsvektor) dieser Disk befragt und ein Block für die neue Datei zugewiesen. Es markiert den Block als benutzt und schreibt die Blocknummer in den FCB. Da nun die Blocknummer bekannt ist, sind die nächsten Schritte ähnlich dem Lesen — das BDOS übersetzt die Blocknummer in Spur- und Sektornummern und ruft das BIOS auf, um 128-Byte Rekords zu schreiben, bis der Block voll ist. Wenn ein neuer Block benötigt wird, holt das BDOS den nächsten freien Block aus der freien Liste und wiederholt den Ablauf.

Irgendwann ist der FCB gefüllt mit 8 oder 16 Datenblocknummern und das BDOS muss einen zweiten Verzeichniseintrag anlegen. Aber davor "schließt" es den den anfänglichen Eintrag durch Schreiben des FCBs in diesen Verzeichniseintrag auf die Disk. Dann sucht es einen weiteren freien Eintrag, legt den zweiten Verzeichniseintrag für diese Datei an (mit demselben Namen, aber mit unterschiedlicher Eintragsnummer). Schließlich setzt es den Ablauf zum Belegen eines Datenblocks und Schreiben der Rekords fort.

Wenn die komplette Datei geschrieben ist, ruft das Programm das BDOS auf, um die Datei zu schließen. Genau wie beim "internen" Schließen des ersten Verzeichniseintrags schreibt das BDOS die Datenblocknummern im FCB in den letzten Verzeichniseintrag auf der Disk.

Im Fehlerfall während des Schreibens kann man einige Reste des unvollendeten Ablaufs erkennen. Schnelltest: Wie lautet das Ergebnis für beide Funktionen:

  1. Dateiname im Verzeichnis, Datei als in Ordnung angezeigt.
  2. Dateiname im Verzeichnis, Datei angezeigt als 16K (oder 32K oder ...), aber das Ende der Datei fehlt.

Kommen wir zur Sache und es ist unvermeidlich verwirrend! Es ist außerdem erforderlich, wenn CP/M Dateien wirklich verstanden werden sollen.

Die CP/M Verzeichnisstruktur ist wie ein Baumhaus, das wächst, wenn die Kinder größer werden. Zunächst war es ein einfacher Aufbau (für CP/M 1.4 Dateien). Räume wurden umgebaut, um größere Dateien und Laufwerke handhaben zu können, und der FCB wird erweitert, um direkten Dateizugriff zu bieten (CP/M 2.2). Schmale Durchgänge werden vollgestopft mit Dateigrößen, Datumsstempel und Passworten (CP/M 3).

Einige Verwirrung erklärt sich einfach durch die Fachausdrücke. Ein Verzeichniseintrag besteht aus 32 Datenbytes. Manchmal wird er als physikalische Verzeichniserweiterung (physical directory extent) bezeichnet — "physikalisch" weil er sich auf die aktuellen Bytes auf dem Laufwerk bezieht. Immer wenn dieses Thema auftaucht, wird vorgeschlagen, alle Bezüge von "physikalischen Erweiterungen" (physical extents) zu übersetzen in "Verzeichniseinträge" (directory entries), und "Erweiterungen" (extents) ausschließlich in "logische Erweiterungen" (logical extents), was bald betrachtet wird.

Der Verzeichniseintrag hat etliche Felder, wie in Bild 2 gezeigt. Die Information ist dicht gepackt. Man kann sich einen tatsächlichen Sektor, der vier Verzeichniseinträge beinhaltet, mit dem Hilfsprogramm DU (oder DU3) anschauen. Oder man lässt den folgenden Code mit Hilfe eines Debuggers ablaufen und schaut sich dann den Puffer ab Adresse 0080h an.
        ld      c,11
        ld      de,5C
        ld      a,3F
        ld      (de),a
        call    5
        rst     38
Byte 0 des Verzeichniseintrages (gekennzeichnet mit "u") ist die Nutzernummer der Datei. Ein hexadezimaler Wert E5 zeigt an, dass dieser Eintrag nicht genutzt wird. Sonst kann der Wert bei CP/M 2.2 zwischen 0 und 31 liegen. Bei CP/M Plus sind die Nutzernummern beschränkt auf 0 bis 15. Höhere Werte zeigen besondere Einträge wie Datumsstempel, Passworte und Datenträgernamen an.

Die Bytes 1-8 sind der Dateiname und 9-11 der Dateityp. Sie müssen aus 7 Bit Großbuchstaben, Ziffern oder einigen anderen Symbolen bestehen. Jedes der 11 höheren Bits (des achten Bits) des Namens und Typen ist ein Dateiattribut. Attribute 5-11 sind reserviert für das System, um Dateien zu kennzeichnen als schreibgeschützt, archiviert usw.

Die nächsten vier Bytes kodieren die Eintragsnummer und die Länge der Datei. Dazu im Detail später mehr.

In den Bytes 16-31 (10h-1Fh) werden die Blocknummern gespeichert. Abhängig vom Diskformat sind das entweder 16 1-Byte oder 8 2-Byte Werte. Wenn nicht mehr als 255 (FF hex) Blocknummern auf einer Disk vorhanden sind (z.B. auf einer einseitigen Diskette mit einfacher Dichte), dann ist es möglich, 1-Byte Werte zu verwenden. Sonst werden 2-Byte Werte benötigt.

 

Nun, da das Baumhaus an einem Tag erbaut wurde, wäre die Verzeichnisnummer ein 16-Bit Wort. Stattdessen die Besteigung von verschlungenem Astwerk. Aber abwarten!

Das Dateisystem von CP/M verfügt über zwei grundlegende Maßeinheiten:

1 Rekord =128 Bytes
1 logische Erweiterung =128 Rekords = 16k Bytes

Rekords und logische Erweiterungen sind der Reihe nach nummeriert, beginnend mit 0.

Es wird nun eine Datei betrachtet mit einer Größe von 17k mit Kopien auf unterschiedlichen Datenträgern. Es kann sich folgendes Bild ergeben.

Beim Datenträger #1 füllen 16k Datenblöcke einen Verzeichniseintrag. Dann entspricht ein Eintrag einer logischen Erweiterung. Die 17k Datei hält zwei logische Erweiterungen und zwei Verzeichniseinträge.

Beim Datenträger #2 füllen 32k Datenblöcke einen Verzeichniseintrag. (Wie das? Angenommen ein Block ist 4k groß und die Blocknummern sind 2-Byte Werte. 8*4k = 32k.) Nun kann ein Eintrag zwei logische Erweiterungen verwalten. Die 17k Datei hält zwei logische Erweiterungen aber nur einen Verzeichniseintrag.

CP/M behält die Übersicht über die logischen Erweiterungen mittels des EXtent-Bytes, das im Bereich 0 bis 31 (0 bis 1F hex) liegt. Ist 31 erreicht, muss das Bytes wieder mit 0 beginnen.

Warum, so kann man fragen, erlaubt CP/M nicht mehr als 32 Werte für das EXtent-Byte? Nun, der Erbauer des Baumhauses war nicht sehr weitsichtig. Das BDOS verwendet in der Verzeichnissuche das Zeichen '?' als "Joker". Tritt ein '?' im EXtent-Byte des FCB auf, dann sucht das BDOS nach allen Extent-Werten. Und da '?' als Byte den Wert 3F hex = 00111111 binär hat, stehen nur fünf Bits für den Wert der logische Erweiterungen zur Verfügung!

Stünden tatsächlich nur fünf Bits zur Verfügung, dann wären CP/M Dateien begrenzt auf 32*Blockgröße. Um größere Dateien zuzulassen, fügte das Baumhaus das Byte S2 hinzu. Dieses enthält den "Überlauf" vom EXtent-Byte. Jede Einheit von S2 stellt somit 32 logische Erweiterungen dar und der Wert des S2 Bytes liegt zwischen 0 und 3F hex.

Der Gesamtwert der logischen Erweiterung wird wie folgt aus dem EXtent- und S2-Byte zusammengesetzt:

logische_Erweiterung = (EXT & 1Fh) + ((S2 & 3Fh) << 5)

(Es werden Operatoren wie in der Sprache C verwendet: '&' bedeutet bitweise und '<<' links schieben)

Es ist unbedingt darauf zu achten, dass die höherwertigen Bits tatsächlich maskiert werden; wenn nämlich der Verzeichniseintrag im FCB aktiv ist, benutzt das BDOS die höheren Bits des EXTent- und S2-Bytes zur internen Markierung.

Was ist denn nun die Verzeichniseintrags-Nummer ("physical extent")? Sie ist die Nummer der logischen Erweiterung dividiert durch die Anzahl logischer Erweiterungen in einem Verzeichniseintrag. Und das ist abhängig vom Format, einer Angabe, die sich nicht im Verzeichnis befindet, wohl aber im BIOS-Aufbau der Laufwerksdaten — dem Disk Parameter Block (DPB4).

Eintrags_Nr = logische_Erweiterung / logische_Erweiterungen_per_Verzeichnis

Das Byte der Extent-Maske (extent mask) im DPB kodiert die Anzahl logischer Erweiterungen in einem Verzeichniseintrag. Dieser Wert beträgt

Extent-Maske = 2 ** logische_Erweiterungen_per_Verzeichnis -1

Eine zunächst befremdliche jedoch handliche Darstellung, weil man die Anzahl der Rechtsschiebungen für die logische_Erweiterung zur Berechnung der Verzeichniseintrags-Nummer erhält. Und gleichzeitig ist es eine Bitmaske, die, angewendet auf das EXTent-Byte, die Anzahl logischer Erweiterungen innerhalb des aktuellen Verzeichniseintrages ergibt.

Eintrags_Nr = logische_Erweiterung >> Extent-Maske

= ((EXT & 1fh) >> Extent-Maske) + ((S2 & 3fh) << (5 - Extent-Maske))

Wie groß ist eine Datei, ausgedrückt in Rekords? Oder entsprechend: wie groß ist die die Rekordnummer der Datei? Es ist der Rekordzähler (RecordCount, RC) im letzten Verzeichniseintrag (die Anzahl Rekords in der letzen logischen Erweiterung) plus der Größe (in Rekords) aller voherigen Erweiterungen. Da das RC Byte den Wert 80 hex annehmen kann, muss es maskiert werden. Die Formel dafür lautet:

Rekordnr = logische_Erweiterung << 7 + (RC & 7Fh)

Vor der Überlegung geeigneter Antworten auf diese Frage soll betrachtet werden, wie groß eine Rekordnummer überhaupt werden kann. Der Rekordzähler besteht aus 7, das EXTent-Byte aus 5 und das S2-Byte aus 6 Bits, also insgesamt 18 Bits. Die größte mögliche Rekordnummer ist deshalb 218. Da in einem Kilobyte 8 = 23 Rekords enthalten sind, beträgt die maximale Dateigröße 215 k = 32 MB, in der Tat eine große Datei!

Dies ist die Grenze für CP/M Plus (und ZSDOS). Das normale CP/M 2.2 begrenzt die Rekordnummer auf einen 16-Bit Wert (mit dem größten Wert für S2 von 0F hex) und somit auf eine maximale Dateigröße von 4 MB. Aber man kann davon ausgehen, dass die meisten CP/M Anwendungen diese Begrenzung nicht überschreiten.

Die Dateigröße kann auf verschiedene Weise bestimmt werden. Die BDOS Funktion 35 gibt die Dateigröße im FCB Feld für die beliebige Rekordnummer (Random Record Number, RRN) zurück. Dies ist die einfachste Methode; das BDOS erledigt das lästige Rechnen und das RRN Feld ist 3 Bytes lang, so dass es die vollen 18 Bit beinhaltet, sollte je eine Datei so groß werden. Aber dies ist langsam, weil das BDOS bei jedem Aufruf das Verzeichnis von Anfang an durchsucht.

Eine zweite Methode lässt das Programm das komplette Verzeichnis lesen, wobei die Verzeichniseinträge für die Datei gespeichert werden, bis der letzte Eintrag gefunden wurde. Dies ist nicht schneller für eine einzelne Datei aber der klare Gewinner, wenn das komplette Verzeichnis sowieso gelesen wird (z.B. um es anzuzeigen). In diesem Fall wird die Dateigröße ermittelt, nachdem die Einträge abgelegt und nach Eintragsnummern sortiert sind (eventuell auch alphabetisch).

Oftmals benötigt ein Programm eine Dateigröße verbunden mit anderen Dateioperationen. In diesem Fall kann die Datei zuerst geöffnet oder nach ihr gesucht werden. Danach wird die Größe schnell aus den Daten der Verzeichniseinträge bestimmt. Bild 3 zeigt die Routine, get_filesize, die diese Funktion ausführt.

Wenn die Datei nur einen Verzeichniseintrag besitzt, dann stehen alle Informationen zur Berechnung der Größe (in Rekords) in den Bytes EXtent, S2 und RC, die zurückgegeben werden im FCB nach einem Aufruf zum Öffnen oder im DMA Puffer nach einem Aufruf zum ersten Suchen. Die Routine prüft zunächst, ob es sich tatsächlich bei der FCB-Information um die für die Eintragsnummer 0 handelt. Sie untersucht dann durch Prüfung des RC Bytes, ob keine anderen Einträge vorhanden sind. Wenn dieser Wert nämlich 80h (128) ist, dann ist der Eintrag voll und es kann ein weiterer existieren.

Sind alle Test durchlaufen, wird wie folgt gerechnet:

Rekords = Rekordzähler + 128 * Anzahl voriger logischer Erweiterungen

Andernfalls wird das BDOS aufgerufen, das die Anzahl Rekords im RRN-Feld des FCB zurückgibt.

Die Routine get_filesize gibt die Dateigröße als 3-Byte Wert im Akkumulator (A) und den Registern H und L zurück. Der Wert in A wird Null sein, mit Ausnahme von sehr großen Dateien, so dass die Dateigröße als 16-Bit Wert im Registerpaar HL benutzt werden kann.

 

Was aber, wenn die Größen mehrerer Dateien benötigt werden? Wenn die Routine viel Speicher zur Verfügung hat, um eine lange Liste von Verzeichniseinträgen abzulegen, dann können diese in einem Durchgang verarbeitet werden. Aber in einigen Anwendungen muss Speicher erhalten bleiben. Die Routine ist vielleicht ein kleiner Teil eines großen Programms, das Speicher für andere Funktionen benötigt.

Die grundlegenste Verzeichnisroutine sieht wie folgt aus:
          FCB mit einer Joker-Maske belegen
          DMA auf einen Puffer legen
          Erste Suche
            Falls nicht gefunden, beenden
Schleife: Ist die Eintragsnummer 0, Eintrag ab Pufferversatz anzeigen
          Nächste Suche
            Wenn gefunden, weiter bei "Schleife"
Wie kann man die schnelle Berechnung der Dateigröße zu dieser Routine hinzufügen? Hier ein Entwurf eines Versuches für das Kommando DIRectory für den BackGrounder ii, und später auch für JetFind. Das Kommando muss auch dann weiterlaufen können, wenn ein normales Programm unterbrochen wurde, ohne den Speicher dieses Programms anzutasten. Das ist eine besondere Herausforderung.
BackGrounder ii ist eine der komfortabelsten Erweiterungen für CP/M. JetFind von Bridger Mitchell ist im Wesentlichen ein Suchprogramm für Texte.

Die Zeile mit "Schleife" soll geändert werden in:
        ist der Verzeichniseintrag nicht voll, Dateigröße aus Eintrag berechen.
        sonst Aufruf der BDOS Funktion 35.
Auf den ersten Blick sieht das gut aus. Aber es gibt Probleme, sobald die BDOS Funktion für die Dateigröße aufgerufen wird, weil dieser Aufruf die internen BDOS Verzeichnis-Zeiger verändert und den Aufruf für die nächste Suche durcheinander bringt. Dies benötigt einer Erklärung.

Die BDOS Funktionen Erste Suche und Nächste Suche sind anders als die übrigen Datei-Funktionen. Logisch sind sie eine Funktion, die wiederholt an zwei Einsprungadressen aufgerufen wird. Tatsächlich bedeutet diese Operation: Finde den ersten Eintrag im Verzeichnis, der dem mitgegebenen FCB entspricht und gib diesen im DMA Puffer zurück. Danach, bei Aufruf der nächsten Suche, fahre mit der Suche fort für den nächsten passenden Eintrag.

Das BDOS verwendet interne Zeiger, die sowohl den FCB als auch den aktuellen Stand der Verzeichnissuche verwalten. Es setzt voraus, dass keine Dateioperationen dazwischenkommen mit Ausnahme weiterer Aufrufe für die nächsten Suchen.

Mit etwas Geschick kann die obige Routine aber so angepasst werden, dass diese Probleme umgangen werden. Nach Aufruf der BDOS Funktion 35 wird die erste Suche für den Eintrag 0 dieser Datei aufgerufen. Damit werden die internen Zeiger, die auf die Stelle der letzten passenden Suche zeigten, zurückgesetzt. Danach wird die nächste Suche aufgerufen.
Damit nimmt die Routine folgende Gestalt an:

          FCB mit einer Joker-Maske belegen
          DMA auf einen Puffer legen
          Erste Suche
            Falls nicht gefunden, beenden
Schleife: Ist der Verzeichniseintrag nicht voll, Dateigröße aus Eintrag berechen.
          Sonst
                Aufruf der BDOS Funktion 35
                FCB auf letzten gefundenen Eintrag setzen
                Erste Suche
          Nächste Suche
            Wenn gefunden, weiter bei "Schleife"
Dateisysteme sind ein umfangreiches Thema. Der Artikel endet hier mit dem Aufruf, die kleine Routine "als Übung für den Leser" selber zu programmieren.

Bild 1. Freier Platz auf einem Laufwerk
[Hier als Quelle]
Bild 2. Ein CP/M Verzeichniseintrag
           + Nutzernummer          +----EXtent Byte
          /                       / +---S1 Byte
         /                       / / +--S2 Byte
        /    Dateiname   Typ    / / / + Rekordzahl (RC)
       / --------------- ----- / / / /
00    u d a t e i n a  m t y p x 1 2 r
10    - - - - - - - -  - - - - - - - - Datenblöcke
Bild 3. Berechnung einer einzelnen Dateigröße
[Hier als Quelle]

Schaut man sich die Listings genauer an, dann stellt man fest, dass get_freek zwar das Byte extmsk belegt, jedoch nur für CP/M 2 (Routine dparams). Wird get_filesize anschließend von einem CP/M Plus System aufgerufen, so führt die Berechnung zu einem Fehler, weil extmsk nicht korrekt belegt ist. Deshalb habe ich die Datei zur Berechnung des freien Platzes auf einem Laufwerk aufgeteilt in die Berechnung und das Laden der DPB-Parameter. Ein CP/M Plus System sollte deshalb vor get_filesize die Routine dparams aufrufen.
Zusammenstellung einiger Fachausdrücke
DeutschEnglischDeutschEnglisch
Verzeichnisdirectory Verzeichniseintragdirectory entry
Jokerwildcard Nutzernummeruser number
Nutzerbereichuser area Belegungallocation
Vektorbitmap Belegungsvektorallocation bitmap
Blockfaktorblocking factor   

1. Das BDOS ist die eigentliche System-Schnittstelle für den Programmierer. Die Aufrufe erfolgen über Funktionsnummern und mögliche Parameter. Eine Übersicht über das BDOS findet sich hier, eine detaillierte Beschreibung hier.
2. Das BIOS ist der maschineabhängige Teil von CP/M. Eine Beschreibung darüber findet sich hier.
3. Der File Control Block definiert die Schnittstelle zum BDOS bei Dateiverarbeitungen. Der Aufbau findet sich hier. Die unterschiedlichen Verzeichniseinträge sind hier beschrieben.
4. Die einzelnen Felder des Disk Parameter Blocks sind hier beschrieben.