Auf der Schwelle zum Licht: Dateizugriff auf Massenspeicher

Heute möchte ich Ihnen etwas darüber erzählen, wie GEMDOS mit den Massenspeichern des ST (Diskette, Harddisk, RAM-Disk) umgeht, wenn es um das Lesen und Schreiben von Daten geht. Dafür ist die unterste Ebene der GEMDOS-Dateiverwaltung zuständig.

Als erstes wollen wir uns ein wenig um die Struktur der Speichermedien kümmern, auf denen sich bekanntlich neben den eigentlichen Dateien noch die Directories (Verzeichnisse) und die File Allocation Tables (FAT) befinden.

GEMDOS informiert sich über die Aufteilung eines Mediums mit der BIOS-Funktion ‘Getbpb’, die für jedes Laufwerk einen Zeiger auf einen ‘BIOS Parameter Block’ (BPB) liefert (Abb. 1). In Klammern sind die Werte für Disketten im Standard-Format angegeben. Die ‘b_flags’ werden bei Disketten vom BIOS für interne Zwecke benutzt. GEMDOS interessiert sich nur für ‘b_flags[0]’ (s.u.).

typedef struct
{ int b_recsiz;	/*	Bytes pro Sektor	(512)	*/
	int b_clsiz;	/*	Sektoren pro Cluster'	(2) */
	int b_clsizb;	/*	Bytes pro Cluster	(1024) */
	int b_rdlen;	/*	Sektoren fUr Root Directory (7) */
	int b_fsiz;	/*	Sektoren pro FAT (5) */
	int b_fatrec;	/*	erste Sektornummer dei£ zweiten FAT	(6) */
	int b_datrec;	/*	Sektornummer des ersten Daten-Clusters (18) */
	int b_numcl;	/*	Anzahl Daten-Cluster auf Diskette (711) */
	int b_flag[8];	/*	Flags (Bit 0 von b_fXug[0]: FAT-Typ)	(0)  */

} BPB;

Abb. 1: Struktur des BIOS Parameter-Blocks (BPB)

Bei Disketten werden die Daten für den BPB vom BIOS zwar aus dem Bootsektor gewonnen, doch GEMDOS interessiert sich überhaupt nicht für Bootsektoren, welche daher bei anderen Speichermedien auch überhaupt nicht erforderlich sind.

Intern werden FAT und Directories ganz ähnlich wie Dateien verwaltet, da so wesentliche Teile der Dateiverwaltung mitbenutzt werden können. Aus diesem Grund wird im folgenden meistens nur von ‘Dateien’ gesprochen werden; gemeint sind aber auch FAT und Directories.

Sektoren und Cluster

Ein Speichermedium ist in Sektoren, die die kleinste Einheit bilden, auf die GEMDOS Zugriff hat, unterteilt. Wie die Sektoren physikalisch auf z.B. der Diskette verteilt sind, soll ganz das Problem des BIOS bzw. der Laufwerks-Treiber bleiben, daher werden sie mit einer “logischen Sektornummer” angesprochen.

Für das Lesen und Schreiben von Sektoren ist die BIOS-Funktion ‘Rwabs’ zuständig. Hier sind alle Sektoren einfach von Null bis zu einem Maximalwert durchnumeriert. Diese Art der Numerierung wird von mir als ‘BlOS-Zählung’ bezeichnet. Daten-Sektoren werden zu sogenannten Clustern zusammengefaßt. Ein Cluster besteht aus 1,2,4,.- (einer Potenz von 2) aufeinanderfolgenden Sektoren. Für jeden Cluster gibt es einen Eintrag in der FAT. Cluster werden von 2 an aufwärts durchnumeriert.

Viele Sektoren pro Cluster haben den Vorteil, daß die FAT wesentlich kleiner ist und daher schneller durchsucht werden kann. Außerdem arbeitet die unten erläuterte Pufferung von Sektoren bei wenigen FAT-Sektoren effektiver. Ein weiterer Vorteil ist die geringere “Zersplitterung” von Dateien, die sich darin äußert, daß eine Datei aus kleinen, auf das Medium verstreuten “Stücken”, besteht.

Der Nachteil der Bildung von Clustern ist die größere Verschwendung von Speicherplatz, da der für eine Datei benötigte Platz immer auf ein Vielfaches der Cluster-Größe “aufgerundet” werden muß.

Im BPB steht die Sektomummer des ersten Daten-Sektors (‘b_datrec’).

File Allocation Table (FAT)

Über die FAT werden die Daten-Cluster den Dateien zugeordnet. Jeder Eintrag eines Clusters gibt an, ob er unbenutzt oder defekt ist oder gibt die Nummer des nächsten zur Datei gehörenden Clusters bzw. das Dateiende an. Im Directory ist der erste Cluster einer Datei vermerkt.

Jeder Eintrag der FAT ist 12 oder 16 Bit lang, je nach Speichermedium. Das Format wird durch Bit 0 von 'b_flags[0]’ des BPB bestimmt (gesetztes Bit für 16-Bit-For-mat). Die genaue Struktur finden Sie in den ‘Floppy-Spielereien’ (ST 6/87, 1/88) ausführlich beschrieben (Gruß zurück an Claus!).

Die ersten zwei Einträge sind unbenutzt und werden von GEMDOS nicht beachtet (bei Standard-Disketten steht dort $F7FFFF). Dies ist wohl der Grund dafür, daß die Cluster-Numerierung bei 2 beginnt. Die Länge einer FAT steht im BPB unter ‘b_fsiz’.

GEMDOS verwaltet zwei identische FATs, wie schon aus der Struktur des BPB ersichtlich wird. Beim Schreiben eines FAT-Sektors wird er stets in beide FATs geschrieben, die somit jederzeit identisch sind. Gelesen wird jedoch immer aus der zweiten FAT. Die erste FAT ist also nur eine Sicherheitskopie, auf die bei einer beschädigten zweiten FAT zurückgegriffen werden könnte. GEMDOS berücksichtigt dies allerdings nicht; man müßte sich also selbst ein Programm schreiben, das eine defekte FAT durch die Kopie ersetzt. Es ist leider nicht möglich, auf eine FAT zu verzichten, sie müssen sogar unmittelbar hintereinander liegen. Im BPB ist explizit nur der Beginn der zweiten FAT vermerkt (‘bjatrec’). Die erste FAT muß direkt davor liegen, also bei 'b_fatrec’ minus ‘b_fsiz’.

Directories

Das Root Directory (Hauptverzeichnis) genießt eine Sonderstellung unter den Directories, da es eine feste Größe und einen festgelegten Platz auf dem Speichermedium hat.

Es beginnt nach der zweiten FAT. Seine Länge ist im BPB festgelegt (‘b_rdlen’). Aus der Größe eines Sektors (i.a. 512 Byte) und der eines Directory-Eintrags (32 Byte) ergibt sich die maximale Anzahl von Einträgen. Da alle Directory-Sektoren hintereinander liegen, braucht auf sie nicht mittels der FAT zugegriffen zu werden. Subdirectories werden dagegen wie normale Dateien behandelt. Sie haben einen Eintrag im Parent Directory, ihre Cluster liegen im Datenbereich der Diskette und werden daher auch mittels der FAT verwaltet. Ihre Länge wächst mit der Zahl der Einträge und ist nur durch die Kapazität des Massenspeichers begrenzt. Ein Subdirectory wird jedoch nicht automatisch verkürzt, wenn Dateien gelöscht werden. Wenn von “Daten” gesprochen wird, sind Subdirectories stets mit eingeschlossen; “Directory” meint nur das Root Directory. Den genauen Aufbau eines Directories können Sie in den ‘Floppy-Spielereien’ (ST 8/87) nachlesen.

Sektorzählunq des GEMDOS

Um die Sache noch ein wenig zu komplizieren, zählt GEMDOS die Sektoren intern nicht wie das BIOS, sondern hat seine eigene Zählweise, in der die eben besprochene Strukturierung des Mediums zum Ausdruck kommt.

Daten-Sektoren haben in der GEMDOS-Zählung positive Sektornummem. Die Nummer des ersten Sektors des ersten Clusters (also von Cluster 2) ist zwei mal die Anzahl der Sektoren pro Cluster.

Die Sektoren des Root Directories und der FAT haben bei der GEMDOS-Zählung negative Nummern. Sie werden nach einem komplizierten Verfahren aus den Daten des BPB bestimmt, worauf wir erst nächsten Monat zurückkommen werden. Hier seien als Beispiel nur die Werte für Disketten im Standard-Format angegeben (Abb. 2). Wenn Sie es nicht abwarten wollen, können Sie ja versuchen, das “System” zu erraten (viel Spaß!).

Abb. 2: Aufteilung einer Standard-Diskette

Bei der Zählung der Datensektoren sind sich BIOS und GEMDOS allerdings nicht ganz einig. Der ‘b_numcT-Wert des BPB gibt die Gesamtzahl der vorhandenen Daten-Cluster an. GEMDOS ist der Meinung, dies sei die Nummer des letzten Daten-Clusters in seiner Zählweise. Da GEMDOS die Cluster von 2 an zählt, rechnet es mit zwei Clustern weniger, als eigentlich da sind. Dies ist durchweg bei allen internen GEMDOS-Routinen der Fall, so daß die letzten zwei Cluster eines Mediums von GEMDOS ungenutzt bleiben. Bei Disketten werden somit vier Sektoren (= 2 kB) Speicherplatz verschenkt.

typedef BCB
{ BCB *b_link; /* Zeiger auf nächsten BCB dieser Liste */ 
int b_bufdrv; /* Laufwerksnummer-, -1 für' ungültigen BCB */
int b_buftyp; /* FAT (0), DIR (1), DATA (2) */
int b_bufrec; /* Sektor-Nummer in GEMDOS-Zählung */ 
int b_dirty; /* ungleich Null: Pufferinhalt geändert */
DMD *b_dmd; /* Zeiger auf DMD von b_bufdrv */ 
char *b_bufr; /* Zeiger auf eigentlichen Sektor-Puffer */
} BCB;

Abb. 3: Struktur des Buffer-Control-Blocks (BCB)

Das Konzept der Sektor-Pufferung

Damit Sie auch wissen, warum ich Ihnen dies alles so genau erklärt habe, kommen wir nun zur Anwendung dieser Grundlagen, indem wir uns ansehen, wie GEMDOS nun eigentlich seine Zugriffe auf Massenspeicher abwickelt.

GEMDOS hat drei allgemeine Routinen zum Lesen bzw. Schreiben von Sektoren auf bzw. von Massenspeichern. Sie sind auch für die Übersetzung der GEMDOS-Zählung in die BlOS-Zählung (mit Hilfe des DMD) zuständig. Die übergeordneten Dateifunktionen kennen also nur GEMDOS-Sektornummern.

Es müssen einzelne Sektoren übertragen werden können, von denen nur einige Zeichen benötigt bzw. geändert werden sollen (vor allem bei FAT und Directories). Um zu verhindern, daß sie bei jedem Zugriff erneut geladen werden müssen, was eine ziemliche Zeitverschwendung wäre, ist es sinnvoll, solche Sektoren zwischenzuspeichern.

GEMDOS verwaltet hierfür zwei Pufferlisten, eine für FAT-Sektoren, die andere für Directory- und Daten-Sektoren. Mit Directory-Sektoren sind hier wieder nur die Sektoren des Root Directory gemeint. Zu jedem Puffer existiert ein sogenannter Buffer Control Block” (BCB). Jeder BCB enthält Angaben über den zugehörigen Sektor (Abb. 3), damit GEMDOS den Überblick behält (genauer gesagt: es versucht). Dazu gehören die logische Sektornummer in GEMDOS-Zählung, die Laufwerkskennung (0...15), der Puffer-Typ (0,1.2 für FAT-, Directory- bzw. Daten-Sektoren) und die Adresse des eigentlichen Puffers, wo der Inhalt des Sektors zu finden ist.

Eine Laufwerkskennung von -1 gibt an, daß der Puffer zur Zeit unbenutzt ist. Damit sind die anderen Daten des BCB bis auf ‘b_bufr’ ungültig.

Des weiteren gibt es ein “Dirty-Flag”. Wenn es ungleich Null ist, wurde der Sektor geändert und ist noch nicht auf das Laufwerk zurückgeschrieben worden. Zusätzlich zu seiner Kennung wird das Laufwerk noch durch den “Drive Media Descriptor” (DMD) identifiziert, bei dem ich Sie erneut auf die nächste Folge vertrösten muß.

Alle BCBs einer Pufferliste sind miteinander verkettet, d.h. ‘b_link’ zeigt auf den nächsten BCB, beim letzten BCB einer Liste ist ‘b_link’ gleich 0L (Abb. 4).

Die Anfänge der beiden Listen ist in der globalen Systemvariablen ‘buff ($4B2) vermerkt: ‘bufl[0]\ also $4B2, enthält den Anfang der FAT-Liste, ‘bufl[l']’, also $4B6, zeigt auf den ersten BCB der DIR/ DATA-Liste.

Die Sektoren sind in der Reihenfolge des letzten Zugriffs in der Liste sortiert. Der erste Sektor ist der zuletzt angesprochene, usw.

Abb. 4: Struktur der GEMDOS-Pufferlisten (Beispiel)

Sektor über Pufferliste übertragen

Zum Lesen eines einzelnen Sektors über die Pufferliste dient die im folgenden ‘f_sread’ genannte interne Routine. Sie findet auch bei Schreibzugriffen Verwendung, da auch dort der Sektor vor seiner Änderung erst einmal geladen werden muß.

Falls der zu lesende Sektor schon in der Pufferliste vorhanden ist, wird mittels der BIOS-Funktion 'Mediach’ geprüft, ob das Speichermedium (i.a. die Diskette) gewechselt wurde. Bei einem “sicheren Mediumwechsel" wird die ganze GEMDOS-Funktion sofort mit der BIOS(! (-Fehlermeldung E_CHNG (-14) abgebrochen, wie in der Januar-Ausgabe beschrieben.

Tritt “nur” ein “möglicher Mediumwechsel” auf, so wird der Sektor einfach nochmal geladen, allerdings ohne Rücksicht darauf, ob er schon geändert wurde (“Dirty”-Flag)l Allerdings wird der “mögliche Mediumwechsel” vom BIOS nur (?) bei Disketten mit Schreibschutz gemeldet (dort allerdings fast immer!), so daß dieser Fall nicht eintreten sollte.

Wenn der Sektor noch nicht gepuffert ist, wird er in einen freien Puffer der jeweiligen Liste geladen. Ist keiner mehr unbenutzt, so wird der “älteste” Puffer (also der letzte) aus der Liste entfernt. Dadurch wird erreicht, daß die am häufigsten benötigten Sektoren am längsten gepuffert bleiben. Dabei wird der Sektor natürlich zurückgeschrieben, falls er geändert wurde. Er wird mit "BIOS-Rwabs’ gelesen. Tritt hierbei ein Fehler auf. wird die GEMDOS-Funktion wie üblich abgebrochen.

Der zu lesende Sektor wird konsequenterweise in jedem Fall an die erste Stelle der Liste gehängt, egal, ob er tatsächlich geladen wurde oder schon vorhanden war. Am Ende von ‘f_sread’ wird das "Dirty-Flag’ gesetzt, wenn ein Schreibzugriff geplant ist. Die übergeordneten Funktionen zum Schreiben rufen ‘f_sread‘ kurz vorher auf, so daß diese Methode gerechtfertigt ist. Dieses Verfahren hat den Vorteil, daß nur die auf der untersten Ebene angesiedelten Funktionen für die Pufferliste sich mit den Details der BCBs herumschlagen müssen.

Nun sind auch noch ein paar Worte zur oben schon erwähnten Routine zum Schreiben einzelner Sektoren (‘f_swrite’) angebracht. Sie wird außer bei ‘f_sread’ immer dann gebraucht, wenn explizit bestimmte Sektoren zurückgeschrieben werden sollen (z.B. beim Schließen einer Datei). Bei FAT-Sektoren finden die Schreibzugriffe für die beiden identischen FATs unmittelbar nacheinander statt. Bemerkenswert ist, daß vor dem eigentlichen Schreibvorgang (mit Rwabs) der Puffer ungültig gemacht (b_btifdrv = -1). und erst danach wieder für gültig erklärt wird (außerdem wird ‘b_dirty’ natürlich gelöscht). Dies hat zur Folge, daß nach einem Abbruch der GEMDOS-Funktion bei einem Schreibfehler der Puffer ungültig ist, d.h. daß sein Inhalt als verloren angesehen wird.

In einigen Fällen wird ‘f_swrite’ auch mit einem ungültigen oder nicht veränderten Puffer aufgerufen. Diese werden zwar nicht geschrieben, wie es auch selbstverständlich sein sollte, aber veränderte werden ungültig gemacht. Dies hat weitreichende Folgen, wie Ihnen bald klar werden wird.

Direkter Sektor-Zugriff

Wenn alle Zugriffe nach dem oben beschriebenen Verfahren ablaufen würden, wäre das Laden von Programmen vermutlich genauso langsam wie das Lesen von Texten mit 1st Word+.

Daher gibt es eine weitere elementare Routine für den Massenspeicher-Zugriff, die für das Übertragen einer zusammenhängenden Folge von Sektoren zuständig ist (‘f_mrw’). Sie arbeitet also wie ‘Rwabs’ auf BIOS-Ebene, hat ähnliche Parameter und ruft im Prinzip diese Funktion direkt auf.

Hier müssen allerdings Kollisionen mit der Pufferliste berücksichtigt werden. Aus diesem Grund werden alle Sektoren der Pufferlisten, die nun durch ‘f_mrw’ übertragen werden sollen, zuerst mit ‘f_swrite’ zurückgeschrieben. Dann erst erfolgt die Übertragung mit ‘Rwabs’.

Beim Lesen wird dadurch sichergestellt, daß Änderungen in der Pufferliste nicht unter den Tisch fallen. Der Schreibzugriff könnte zwar vermieden werden, indem ‘f_mrw’ sich die geänderten Sektoren aus der Pufferliste holt, doch soviel Mühe wollten sich die GEMDOS-Programmierer mit ‘f_mrw’ offensichtlich nicht machen. Beim Schreiben ist dies sogar ganz überflüssig, da der Sektor ja sowieso gleich ganz neu geschrieben wird. Hier wäre es allerdings wichtig, den entsprechenden Puffer ungültig zu machen (wenn er schon nicht auf den neuesten Stand gebracht wird), da nachfolgende Schreibzugriffe über die Pufferliste sonst auf den alten Sektorinhalt gehen. Wie wir gerade gesehen haben, wird dies von ‘f_swrite’ aber nur gemacht, wenn der Sektor nicht geändert wurde.

Und GEMDOS ist doch nicht fehlerfrei

Welche fatalen Folgen dies haben kann, sehen Sie an Listing 1. Ihre Kenntnisse in C oder einer ähnlichen Sprache sollten Ihnen sagen, daß nach Ablauf dieses Programms in der Datei ‘Test’ ein ‘c’ und 511 ‘b’s stehen. Ein kurzer Blick in ‘Test’ nachdem Programmlauf wird Ihnen allerdings ein ‘c’ und 511 ‘a’s bescheren! Die Erklärung dürfte nach den vorangegangenen Erläuterungen und den Kommentaren im Listing nicht schwerfallen.

Moral von der Geschichte: Benutzen Sie ‘Fwrite’ immer nur für Datenmengen größer oder kleiner als 512 Byte. Eine ‘Mischung’ ist allerdings erlaubt, wenn Sie rein sequentiell arbeiten, also kein ‘Fseek’ verwenden.

Hiermit dürfte klar geworden sein, daß die “Zusammenarbeit” zwischen den beiden Arten des internen Datenzugriffs nicht gerade besonders gut ist. Glücklicherweise treten diese Fehler in der Praxis oft nicht auf. da die Pufferliste hauptsächlich bei FAT und Directories in Erscheinung tritt, der Mehr-Sektor-Zugriff vornehmlich bei größeren Dateien.

Es gibt noch einen weiteren Fehler im Zusammenhang mit der Pufferliste. Unter bestimmten, nicht geklärten Umständen hat plötzlich ein Daten-Sektor der Pufferliste die GEMDOS-Sektornummer 0. Diese Sektornummer kommt ja normalerweise gar nicht vor, was beim Zurückschreiben aber nicht bemerkt wird. Bei 2 Sektoren pro Cluster hat der erste Daten-Sektor die GEMDOS-Nummer 4, also wird dieser “Sektor 0" auf den vierten Sektor vor den ersten Daten-Sektor geschrieben. Beim Standard-Diskettenformat ist dies der viertletzte von sieben Sektoren des Root Directorys (RD). Da das RD selten mehr als 48 Einträge (das sind drei Sektoren) hat, merkt man von diesem Fehler normalerweise nichts.

Bei der Programmierung eines RAM-Disk-Treibers war ich nun zufällig der Meinung, vier RD-Sektoren würden ’ s auch tun. Daraufhin überschrieb mir GEMDOS regelmäßig meinen ersten RD-Sektor... und ich brauchte zwei Tage, um den Fehler GEMDOS und nicht dem RAM-Disk-Treiber zuzuschreiben. Auch auf einigen Disketten konnte ich mittels eines Diskettenmonitors einen vermurksten vierten RD-Sektor finden.

Bei mir trat dieser Fehler immer nur auf, wenn ein Programm Schreibzugriffe in “kleinen Einheiten” (also über die Pufferliste) machte und dabei das Speichermedium voll wurde. Aber vielleicht weiß jemand von Ihnen ja mehr darüber?

Datei-Zugriff

Nachdem wir nun die elementaren Zugriffsroutinen besprochen haben, und Sie hoffentlich noch interessiert dabei sind, geht es nun um den Zugriff auf Dateiebene. Dazu gibt es eine umfangreiche Routine(vonmir ‘f_frw’ genannt), über die alle Dateizugriffe laufen. In der Programmhierarchie direkt darüber “sitzen” die GEMDOS-Funktionen ‘Fread’ und ‘Fwrite’, so daß Sie eine Vorstellung davon haben, was von ‘f_frw’ geleistet werden muß - nämlich die Umsetzung der relativen Datei-Positionen in die logischen Sektornummem (GEMDOS-Zählung), die von ‘f_sread’ & Co. verstanden werden. Es muß ferner möglich sein, von einer beliebigen Position innerhalb einer Datei eine beliebige Anzahl von Zeichen zu übertragen.

Die Parameter sind ähnlich ‘Fread’/ ’Fwrite’, statt des Datei-Handles wird ein interner ‘File Descriptor’, der auch für Directories usw. existiert, übergeben (dazu mehr in einer späteren Folge).

Aus der aktuellen Dateiposition und den Laufwerks-spezifischen Daten wie Cluster-Größe usw., die im DMD stehen, wird die logische Sektornummer und die Position des ersten Zeichens, auf das zugegriffen werden soll, errechnet.

Im allgemeinen wird der Zugriff mitten in einem Sektor beginnen. Falls dies der Fall ist, wird er mit ‘f_sread’ über die Pufferliste geladen. Der tatsächlich interessierende Teil der Daten wird dann vom bzw. zum vom Aufrufer bereitgestellten Speicherbereich kopiert. Falls alle zu übertragenden Bytes in diesem Sektor liegen, ist alles erledigt.

Ansonsten werden alle nun folgenden Sektoren, die komplett übertragen werden müssen (das sind also alle restlichen, eventuell bis auf den letzten), gelesen bzw. geschrieben. Hierbei entfällt der Umweg über die Pufferliste.

Zuerst werden die restlichen Sektoren bis zum Ende des aktuellen Clusters auf einen Schlag mit ’f_mrw’ übertragen. Dies ist möglich, da alle Sektoren eines Clusters in der logischen Sektornumerierung aufeinander folgen.

Nun kommen die komplett zu lesenden Cluster dran. Dabei kann es natürlich Vorkommen, daß die Cluster über das Medium verstreut sind. GEMDOS ist nun so schlau, die Cluster in möglichst großen Gruppen zu übertragen, was die Geschwindigkeit erhöht. Es geht die Cluster-Nummern, die es aus der FAT holt, durch, bis es eine “Lücke” entdeckt. Dann wird die auf diese Weise ermittelte zusammenhängende Cluster-Gruppe in einem Rutsch übertragen.

Beim letzten Cluster wird es im allgemeinen so sein, daß nicht alle Sektoren gebraucht werden. Diese werden separat behandelt, genauso wie die letzten Sektoren des ersten “angebrochenen” Clusters. Auch hierbei wird natürlich direkt über ‘f_mrw’ gearbeitet.

Zu guter Letzt bleibt unter Umständen ein Sektor übrig, bei dem nur auf den ersten Teil zugegriffen werden soll. Dies geschieht wie beim ersten Sektor über die Pufferliste und anschließendes Kopieren. Zurückgegeben wird die Anzahl der tatsächlich übertragenen Bytes. Dies wird von 'Fread' und ‘Fwrite’ direkt an den Aufrufer zurückgegeben. Bei voller Diskette oder Dateiende wird einfach abgebrochen, so daß dies beim Vergleich des Rückgabewertes mit der gewünschten Zahl der zu schreibenden Zeichen bemerkt werden kann.

Sie sehen also, daß GEMDOS hier recht wirkungsvoll arbeitet, insbesondere, wenn große Datenmengen auf einmal übertragen werden.

Bei der Übertragung ganzer Cluster wird die FAT gelesen, um aus der Dateiposition die Clusternummer zu bestimmen. Da ja auch die FAT intern wie eine Datei verwaltet wird, geschieht dies mit einer Routine, die letztendlich wiederum ‘f_frw" aufruft. Da bei FAT-Zugriffen aber immer nur einzelne Bytes gelesen werden, bricht diese Rekursion immer hier schon ab.

Das Lesen von ganzen Dateien mit nur einem ‘Fread’ ist daher sehr schnell; eine Verzögerung tritt dann auf, wenn ein neuer FAT-Sektor, der nicht in der Pufferliste vorhanden ist, geladen werden muß. Programme, die Dateien in sehr kleinen Portionen, im Extremfall byte-weise, lesen, sind nicht langsam, weil die Sektoren zu oft geladen werden (dies wird durch die Pufferliste ja verhindert), sondern weil das ganze Drumherum einfach zu lange dauert. Bis das Programm das letzte Byte verarbeitet, den nächsten Aufruf von ‘Fread’ gemacht und GEMDOS sich bis zu der Stelle vorgekämpft hat, wo der Sektor wirklich geladen wird, ist so viel Zeit vergangen, daß das BIOS den nächsten Sektor gerade verpaßt hat (die Diskette dreht sich bekanntlich immer weiter). Und bis der Sektor sich mal wieder unter dem Schreib-Lese-Kopf vorbeibewegt, dauert es schon eine Weile.

Anwenderprogramme haben normalerweise die Möglichkeit, die Dateizugriffe in großen Einheiten durchzuführen, GEMDOS selbst muß jedoch bei FAT- und Directory-Operationen immer auf einzelne FAT- bzw. Directory-Einträge (2 bzw. 32 Bytes) zugreifen.

Im Falle des Directories wird hier noch ein wenig zeitsparender verfahren. Dazu wird ‘f_frw’ eine Null als Adresse des für die Übertragung benötigten Speicherbereichs übergeben. Daraufhin wird die Anfangsadresse der zu übertragenden Daten im Sektorpuffer zurückgeliefert (an Stelle der Zahl der übertragenen Zeichen), nachdem der Sektor über ‘f_sread’ geladen wurde. Das Kopieren der Daten aus dem Puffer heraus entfällt also.

Dies ist nur möglich, wenn die Daten garantiert alle innerhalb des gleichen Sektors liegen, da nach dem Laden des ersten Sektors auf jeden Fall abgebrochen wird. Bei Directory-Sektoren ist dies der Fall, da in jeden Sektor genau 16 Einträge (32*16=512 Byte) passen. Außerdem muß gewährleistet sein, daß die Daten auch möglichst bald verarbeitet werden, da unter Umständen schon beim nächsten Dateizugriff der Sektor aus der Pufferliste entfernt wird.

Bei der FAT ist dies nicht möglich, da durch das komplizierte Format ein Eintrag bei einer 12-Bit-FAT auch auf zwei Sektoren verteilt sein kann. Dies ist wohl ein Grund dafür, warum gerade FAT-Zugriffe bei GEMDOS sehr langsam sind. Übrigens läßt sich dieser Spezial-Modus auch von eigenen Programmen aus verwenden, da man über ‘Fread’ direkten Zugang zu ‘f_frw’ hat. Da dies aber nicht dokumentiert ist und bei zukünftigen TOS-Versionen nicht mehr zu funktionieren braucht, sollt man davon absehen; außerdem gibt es wohl auch nur wenige Anwendungen, die davon Gebrauch machen könnten.

Größe von Sektoren und Clustern

Beim ST haben Sektoren immer eine Größe von 512 Byte, und Cluster bestehen immer aus zwei oder einem Sektor. GEMDOS erlaubt theoretisch auch andere Größen (jeweils Potenzen von 2), entsprechende Versuche führen allerdings nur zu Mißerfolgen.

Die entsprechenden Daten des BPB werden zwar korrekt interpretiert und intern wird mit ihnen auch richtig gerechnet, um Datei-Positionen usw. zu bestimmen.

Die Begrenzung der Sektorgröße liegt in der einheitlichen Größe der Sektorpuffer. GEMDOS kennt deren Größe nämlich nicht. Da jeder Puffer für Sektoren eines jeden Laufwerk in Frage kommt, müssen alle Puffer die im System maximal vorkommende Sektorgröße haben. Die vom BIOS installierten Puffer sind - wie nicht anders zu erwarten war - nur 512 Byte groß.

GEMDOS hat übrigens keine absoluten Zeiger auf BCBs oder Sektorpuffer. Daher ist eine Umgestaltung der Pufferlisten jederzeit möglich (natürlich nicht während der Abarbeitung einer GEMDOS-Funktion). Die Verwendung eines Massenspeichers, der mit 1024-Byte-Sektoren arbeitet, ist also durchaus möglich; das Treiberprogramm muß bei seiner Installation nur die Standard-Sektorpuffer gegen eigene der richtigen Größe austauschen. Man kann nur hoffen, daß es keine weiteren Schwierigkeiten gibt.

Cluster mit mehr als zwei Sektoren scheitern an einem Fehler in ‘f_frw’. Ein Patch zur Behebung dieses Fehlers brachte allerdings nicht den erhofften Erfolg, so daß hierzu noch nicht das letzte Wort gesprochen ist.

Verwaltung der FAT

Zu den Routinen zur FAT-Verwaltung gibt es nicht allzuviel anzumerken. Sie sind in der Lage, Folge-Cluster, freie Cluster usw. zu ermitteln. Zum eigentlichen Zugriff auf die FAT wird ‘f_frw’ aufgerufen. Einzig und al lein die Fehler machen wieder einmal zu schaffen. Der erste Fehler betrifft das Indizieren eines Eintrages in der FAT an Hand seiner Cluster-Nummer. Bei Cluster-Nummern größer als $3FFF (also nur bei 16-Bit-FATs) geht das schief. Bei zwei Sektoren pro Cluster ergibt sich somit die maximal erlaubte Größe eines Mediums zu 16 MB. Dieser Fehler wurde im Blitter-TOS sogar korrigiert (Cluster-Nummern bis $7FFF möglich)!! Allerdings möchte ich nicht dafür garantieren, daß Harddisk-Partitions bis 32 MB jetzt möglich sind, da es vielleicht weitere “Hindernisse” gibt. Der zweite Fehler tritt nur bei 12-Bit-FATs auf. Er führt dazu, daß FAT-Einträge größer als $7FF falsch ausgewertet werden (GEMDOS arbeitet mit $Fxxx statt $0xxx weiter). Dies passiert aber nur bei ungeraden Clustem(!). Auch die Datei-Ende-Kennung $FFF wird nicht erkannt, aber sie wird zufällig(l) richtig weiterverarbeitet. Bei zwei Sektoren pro Cluster wird somit “nur” die Kapazität auf 2 MB statt möglicher 4 MB beschränkt.

Beide Fehler resultieren übrigens aus für C typischen Vorzeichen-Fehlern.

Es sind nur $FFF bzw. $FFFF als Dateiende-Marke gedacht. Es gibt keine weiteren besonderen FAT-Einträge (wie $FF0-$FFE bei PC-DOS).

Nutzbarkeit der Sektor-Pufferung

Nach diesen Ausführungen über GEMDOS fragen Sie sich vielleicht schon verzweifelt, wamm Sie (fast) noch nie etwas von der Pufferung bemerkt haben, warum z.B. beim Öffnen oder Schließen eines Ordners das Directory jedesmal neu von Diskette gelesen wird.

Die Ursachen hierfür sind über die verschiedensten Teile des Betriebssystems verstreut, fangen wir also beim BIOS an. Beim Systemstart ist das BIOS für das Anlegen der (leeren) Pufferlisten verantwortlich. GEMDOS verwaltet diese nur, ändert aber nie die Größe der Listen. Da bei anderen Gelegenheiten kräftig mit dem Speicherplatz geaast wurde, sollte wohl hier ein wenig gespart werden. Beide Listen bestehen nämlich nur aus jeweils zwei Sektoren, wodurch der Nutzeffekt der Sektor-Pufferung fast verschwindet. Dieser Umstand läßt sich relativ leicht beheben, da die Systemvariable ‘bufl’ legal zugänglich ist. Das Programm 'EXTBUFL’ (Listing 2) erlaubt es, beide Listen beliebig zu erweitern. Es kann sogar mehrmals hintereinander gestartet werden, da es nur den für die Erweiterung benötigten Speicherplatz verbraucht und sich selbst vollständig freigibt.

Doch auch nach Einrichtung geradezu riesiger Pufferlisten wird Ihre Begeisterung sich in Grenzen halten.

~~~~~~~~~~~~~~~~~~~~~
     Adresse

RAM-TOS ROM-TOS ROM-TOS Bytes (in Hex) 6.2.86 6.2.86 22.4.87

Fehler in 'f_mrw'

00b62a fc579c fc5a54 2a 78 04 b6 60 30 20 6e 00 12 30 28 00 06 bO 6d 00 04 66 20 30 2d 00 08 32 2e 00 Oc bO 41 6d 14 d2 6e 00 0a b2 40 6f 0c 2e 8d 61 00 fe e4 3b 7c ff ff 00 04 00b663 fc57d5 fc5a8d cc

Fehler bei 12-Bit-FAT 00bd03 fc5e75 fc612b 4f

Fehler bei 16-Bit-FAT 00bb98 fc5dOa —----- 7c 00 3c 07 e3 86 4e 71


**Abb. 5: Patch für TOS-Fehler**
</div>

Bei Disketten mit Schreibschutz ändert 'ich überhaupt nichts, da BIOS hier immer (sobald eine bestimmte Zeitspanne nach dem letzten Diskettenzugriff vergangen :-t) einen “unsicheren Diskettenwechsel” meldet, so daß die Puffer wie bei ‘f_sread’ beschrieben immer neu geladen werden. Bei Disketten ohne Schreibschutz werden Sie eine Verbesserung bemerken, wenn Sie >;ch z.B. in einer Fileselector-Box befinden. Hier können Sie nun fleißig Ordner auf- und zumachen, ohne sich über ein anlaufendes Floppylaufwerk zu ärgern.

Auf dem Desktop hat sich wiederum nichts geändert. Vor den meisten (vielleicht sogar allen) Diskettenoperationen “suggeriert” das Desktop dem BIOS nämlich einen “sicheren Diskettenwechsel”! Daraufhin gibt GEMDOS die gesamte Pufferliste frei, also Pufferung ade! Mit dieser zweifelhaften Methode wird über die Unzulänglichkeiten der BlOS-Diskettenwechsel-Erkennung hinweggetäuscht, wobei die Probleme jetzt auf die Anwenderprogramme verlagert werden, die nicht auf diese Scheinlösung zurückgreifen (wollen oder können). Dies erklärt, warum das Desktop so "hervorragend” (verglichen mit anderen Programmen) mit Diskettenwechseln zurechtkommt.

Nun könnten die Harddisk-Benutzer frohlocken, da es bei ihnen keine Medienwechsel gibt. Und siehe da, das Desktop macht keine Schwierigkeiten mehr.

Doch halt! Wer sich noch an die Beschreibung von ‘f_swrite' erinnert, wird wissen, daß nicht veränderte Sektoren (was wohl der Normalfall sein dürfte) für ungültig erklärt werden. Nach jedem ‘Fclose’, welches alle Sektoren mit ‘fswrite’ zurückschreibt, ist die Pufferliste somit leer. Das passiert unter anderem beim Kopieren und Laden von Programmen, so daß die Sektor-Pufferung auch bei Harddisk nur begrenzte Erfolge bringt. Trotzdem kann es unter Umständen sinnvoll sein, die Pufferliste für die Benutzung bestimmter Programme zu vergrößern. Dabei sollten Sie vor allem die damit zusammenhängenden GEMDOS-Fehler beachten. Einige Programme mit raffinierten Dateizugriffen sorgen da womöglich für einige Überraschungen.

Falls Sie selbst ein wenig mit diesen Problemen herumspielen wollen, können Sie dies mit ‘SHOWBUFL’ (Listing 3) tun. Dieses Accessory zeigt den aktuellen Zustand der Pufferlisten an. Die Ausgaben stammen direkt aus den BCBs, bis auf ‘ sec ’. Hierbei handelt es sich um die mittels des DMD aus der GEMDOS-Sektornummer errechnete BlOS-Sektornummer. Nicht-Harddisk-Besitzer können auch mit einer RAM-Disk vorlieb nehmen, da für diese das Gleiche wie für Harddisk gilt, nur daß man von der Pufferung auf Grund ihrer Schnelligkeit nichts merkt.

# Änderungen des TOS

In Abb. 5 sind Patches für TOS-Fehler angegeben. Dabei handelt es sich um die oben beschriebenen Fehler in der internen Funktion ‘f_mrw’ und der FAT-Verwaltung.

‘f_mrw’ macht nun alle mit ‘f_swrite’ geschriebenen Puffer ungültig, egal ob sie geändert waren oder nicht.

# Schlußbemerkung

So, damit wären wir für heute mal wieder am Ende angekommen. Nächsten Monat beschäftigen wir uns u.a. mit der Verwaltung der Laufwerke, wozu auch die versprochene Erklärung des DMD gehört.

<div class="textkasten" markdown=1>

/* Demonstrationsprogramm für Fehler in der Verwaltung der GEMDOS-Pufferliste */

#include <osbind.h> #define NAME "test"

main() { int fh; /* Datei-Handle */

int i; char c;
char buf[512];

if ((fh = Fcreate(NAME,0)) < 0) return;

/* Datei läßt sich nicht öffnen: Abbruch */ 
c = ' a' ;

for(i=0; i<512; i++)

/* GEMDOS schreibt dies über Pufferliste */ 
Fwrite(fh,1L, &c);

/* (ein Zeichen würde auch reichen) */

Fseek(0L,fh,0);

/* zurück zum Dateianfang */ 
for(i=0; i<512; i++)

/* 'buf' voller 'b's schreiben */ 

buf[i] = 'b';
Fwrite(fh,512L,buf);

/* GEMDOS schreibt dies direkt
...und lä_t Puffer so wie er war! */

Fseek(0L,fh,0);	/*	zurück	zum	Dateianfang	*/
c = 'c';

/* ein 'c' schreiben (über Pufferliste) */ 

Fwrite(fh,1L,&c);

/* Datei sollte jetzt 1 'cr und 255 
'b's enthalten, aber... */

Fclose(fh);
/*GEMDOS schreibt jetzt Puffer mit 'b's zurück*/

}

</div>

<div class="textkasten" markdown=1>

/* Erweiterung der GEMDOS-Pufferlisten 24.1.1988 by A. Esser entwickelt mit MEGAMAX C */

#include <osbind.h> #define bufl (BCB *)0x4b2L / Systemvariable 'bufl' */

/* Definition BCB */ typedef struct bctrl { struct bctrl b_link; int b_bufdrv; int b_buftyp; int b_bufrec; int b_dirty; long b_dmd; / korrekt: 'DMD *b_dmd', aber DMD hier nicht def. */ char *b_bufr; } BCB;

int install(buflp,nsec) BCB **buflp; int nsec; { long mp; int i; BCB *bcb;

/* Speicherplatz für 'nsec' BCBs und Puffer reservieren */
if ( (mp = Malloc((long)nsec* (512+sizeof(BCB)))) == 0L)
{ puts("Nicht genug Speicher."); 
return -1; -
}
for (i=0; icnsec; i++)
{	bcb = (BCB *)mp;
	/* BCB initialisieren */
	bcb->b_bufdrv = -1;
	/* Puffer ungültig */
	bcb->b_buftyp = -1;
	/* kein gültiger Puffer-Typ */
	bcb->b_bufrec =0;
	/* keine gültige Sektor-Nummer */
	bcb->b_dirty = 0;
	/* nichts geändert */
	bcb->b_dmd = 0L;
	/* BCB keinem Laufwerk zugeordnet */
	/* Puffer direkt hinter BCB legen */
	bcb->b_bufr = (char *)(mp+sizeof(BCB));
	/* BCB vorne in gewünschte Puffer-Liste einhängen */
	bcb->b_link = *buflp;
	*buflp = beb;
	mp += 512+sizeof(BCB);
		/* weiter zum nächsten */
}
return 0;

}

main()

{ int nsec; long stack; /* gemerkter Supervisor-Stackpointer / stack = Super(0L); / Supervisor-Mode notwendig / puts("\rWieviele zusätzliche FAT-Puffer?"); scanf("%d",&nsec); install(bufl,nsec); / Puffer für FAT nach Liste 'bufl[0]' / puts("\rWieviele zusätzliche DIR/DATA-Puffer?"); scanf("%d",&nsec); install (buf1+1,nsec); / Puffer für DATA/DIR nach Liste'*’'buf 1 [1] ' / Super(stack); / zurück nach User-Mode / / Programm komplett freigeben, aber reservierten Speicher behalten */ Ptermres(0L,0); }

</div>

<div class="textkasten" markdown=1>

/* Accessory zur Anzeige der GEMDOS-Pufferlisten 24.1.1988 by A. Esser entwickelt mit MEGAMAX C */

#include <osbind.h> #include <gemdefs.h> #include <obdefs.h> #define bufl (BCB **)0x4b2L /Systemvariable 'bufl'/

/* Definition DMD / typedef struct { int d_roff[3]; int d_drive; int d_fsiz; int d_clsiz; int d_clsizb; int d_recsiz; int d_numcl; int d_lclsiz; int d_mclsiz; int d_lrecsiz; int d_mrecsiz; int d_lclsizb; long d_fatfd; / korrekt: FD *d_fatfd / long d_dummy; long d_rdd; / korrekt: DD *d_rdd */ int d_flag; } DMD;

/* Definition BCB */ typedef struct bctrl { struct bctrl *b_link; int b_bufdrv; int b_buftyp; int b_bufrec; int b_dirty; DMD *b_dmd; char *b_bufr; } BCB;

/* globale Variable für GEM */

extern int gl_apid; /* Applikations-ID / GRECT desk; / Desktop-Ma_e / int idum; / int-Dummy */

/* Daten einer Pufferliste anzeigen */'

show_bufl(beb) register BCB *bcb; { int btype; char stype[10]; char srec[10];

while (bcb)
{	btype = bcb->b_buftyp;
	/* GEMDOS-Sektornummer aus BIOS-Sektornummer 
	berechnen falls möglich */
	if (bcb->b_dmd)
		/* nur wenn Laufwerk vorhanden */
		sprintf(srec,"%d",bcb->b_dmd->d_roff[btype] + bcb->b_bufrec);
	else
		strcpy(srec,"-");
	switch (btype)
	{ case 0:
		strcpy(stype,"FAT");
		break;
	case 1:
		strcpy(stype,"DIR");
		break;
	case 2:
		strcpy(stype,"DATA");
		break;
	default:
		sprintf(stype,"%d",btype);
		break;
	}
	printf
	("%061x	%04x	%4s	%4d	%4s	%04x	%061x	%061x \n",
	bcb, bcb->b_bufdrv, stype, bcb->b_bufrec,srec, bcb->b_dirty,
	bcb->b_dmd, bcb->b_bufr);
	bcb = bcb->b_link;
}

}

do_work() { long stack; graf_mouse(M_OFF); /* Maus ausschalten / Cconws("\033H\033B\033B"); / Cursor auf Beginn Zeile 2 / printf ("BCB drv type rec sec dirty DMD bufr \n") ; stack = Super (0L); / Supervisor-Mode notwendig / show_bufl(bufl); / Pufferliste für FAT ausgeben / printf (" \n"); / 80 Spaces / show_bufl((bufl+1)); / Pufferliste für DIR/DATA ausgeben / Super(stack); / zurück nach User-Mode / graf_mouse (M_ON) ; / Maus wieder an / Cnecin(); / Anzeige bis Tastendruck halten / / Redraw des Desktop-Bildschirm über GEM */ form_dial(FMD_FINISH,0,0,0,0, desk.g_x,desk.g_y,desk.g_w,desk.g_h); }

main() { int msg[8]; /* Message buffer */ int event; int menu_id;

appl_init(); 
if (gl_apid >= 0)
	/* 'appl_init' geglückt ? */
{	wind_get(0,WF_WORKXYWH,
		&desk.g_x,sdesk.g_y,&desk.g_w,sdesk.g_h);
	menu_id = menu_register(gl_apid, " Show buffer list");
	/* dafür sorgen, da_ printf-Puffer-Malloc unter 
	AES geschieht */ 
	printf("\n");
}
while (1)	/*	Endlos-Schleife	*/
{	event = evnt_multi(MU_MESAG,
		0,0,0,
		0,0,0,0,0,
		0,0,0,0,0,
	msg, 0,0, &idum, &idum, &idum, &idum, &idum, &idum);
	if (event & MU_MESAG)
		if (msg[0] == AC_OPEN)
			/* nur AC_OPEN berücksichtigen */
			if (msg[4] == menu_id)
				/* eigenes Accessory? */
				do_work();	/*	los geht's */
}	/*	end of while */

}

</div>

Alex Esser
Aus: ST-Computer 03 / 1988, Seite 137

Links

Copyright-Bestimmungen: siehe Über diese Seite