← ST-Computer 03 / 1988

Auf der Schwelle zum Licht: Dateizugriff auf Massenspeicher

Grundlagen

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