Atarium: Anatomie eines DOS-Treibers für MetaDOS

In diesem Monat möchte ich ein Thema aufgreifen, nach dem ich in EMails immer wieder befragt werde: die Programmierung von MetaDOS-Treibern. Grund für das Informationsdefizit ist sicherlich, daß es zu diesem Thema nur die offizielle Dokumentation von ATARI gibt und das Interesse an MetaDOS erst in den beiden letzten Jahren wegen der Einbindung von CD-ROM-Laufwerken wieder erwacht ist.

In den vergangenen Jahren habe ich mich bereits mehrfach mit diesem Thema befaßt (siehe zum Beispiel Ausgabe 7/93 des ST-Magazins und Ausgabe 7-8/94 der ST-Computer). Dennoch ist es leider unvermeidlich, einige grundsätzliche Dinge über MetaDOS zu wiederholen, um die Zusammenhänge richtig verstehen zu können. Danach will ich dann auf einen einfachen DOS-Treiber eingehen.

Die Hauptaufgaben von MetaDOS lassen sich wie folgt zusammenfassen:

BOS-Treiber sind Treiber, über die die Kommunikation mit ,realen' Geräten abgewickelt wird. Sie sind in der Funktionalität in etwa mit Festplattentreibern vergleichbar (also etwa blockweises Lesen und Schreiben). Die von diesen Treibern installierten Funktionen werden allerdings nicht über BIOS-Funktionen wie Rwabs(), sondern über die XBIOS-Opcodes 48 bis 63 abgewickelt. Der CD-ROM-Treiber 'CDARGEN.BOS' aus dem ATARI MetaDOS-Paket ist ein Beispiel für einen solchen Treiber: er übernimmt die Ansteuerung von CD-ROMS an der SCSI-Schnittstelle des ATARI-TT (und das CDAR504 an ACSI-Schnittstellen).

Diesen Monat will ich mich allerdings auf die sogenannten DOS-Treiber konzentrieren. DOS-Treiber sind für die Schnittstelle zum Dateisystem zuständig. Ihre Funktionen werden nicht überneue Systemaufrufe sichtbar, sondern dadurch, daß man mittels der gewohnten GEMDOS-Aufrufe auf zusätzliche Laufwerke zugreifen kann. Der ISO-Dateisystemtreiber 'ISO9660F.DOS' aus dem MetaDOS-Paket ist ein gutes Beispiel: er stützt sich auf die Funktionen eines BOS-Treibers, um überhaupt mit einem CD-ROM sprechen zu können. Seine Aufgabe ist es allerdings, die von einem normalen TOS-Dateisystem unterschiedlichen Dateisystemstrukturen für GEMDOS genießbar zu machen.

Nicht jeder DOS-Treiber jedoch muß auf einen BOS-Treiber zugreifen. Ein Beispiel wäre zum Beispiel eine RAM-Disk oder auch das Cookie-Dateisystem, das ich hier vorstellen werde. Der vollständige Sourcecode ist in der Redaktions-Mailbox, im MausNet (COOKFS*.ZIP in der Maus MS2) und per FTP (ftp.uni muenster. de/pub/atari/Utilities) erhältlich.

Bevor wir uns den Details zuwenden, soll erst einmal ein Überblick gegeben werden. MetaDOS unterstützt bei DOS-ebenso wie bei BOS-Treibern das Konzept, daß ein und derselbe Treiber mehrere GEMDOS-Laufwerke (bzw. physikalische Geräte) bedienen kann. Dazu stellt er einen sogenannten Wakeup-Aufruf zu Verfügung, der per JSR aufgerufen wird und in DO und D1 Informationen zurückliefern muß. Die Funktion muß am Anfang des Textsegments der Treiberdatei liegen, das heißt ein MetaDOS-Treiber ist eine ganz gewöhnliche Programmdatei mit einem besonderen Startup-Code.

Dieser Wakeup-Aufruf dient lediglich dazu, allgemeine Initialisierungen vorzunehmen. In der Regel ist das nicht mehr als die Ausgabe einer Meldung auf dem Bildschirm. In D0 liefert man einen Zeiger auf eine weitere Tabelle mit Einsprungpunkten zurü und D1 enthält einen Zeiger auf ei bis zu 32 Zeichen lange nullterminierte Zeichenkette mit dem Namen des Treibers, der dann später von MetaDOS ausgegeben wird. Dies ist auch der. Zeitpunkt, um in der Basepage nach eventuellen Commandline-Argumenten zu sehen und diese auszuwerten.
Werfen wir kurz einen Blick auf eine *DOS-Zeile der CONFIG.SYS:

*DOS, C:\auto\cookieis.dos, Z:x

Sie besteht aus mindestens drei durch Kommata voneinander getrennten Teilen. '*DOS' informiert MetaDOS darüber, daß es sich um einen Eintrag für einen Dateisystemtreiber handelt. Der zweite Teil enthält den Namen der Treiberdatei und eventuelle Parameter (wie zum Beispiel die Cache-Größe für einen CD-ROM-Treiber). Alle restlichen Bestandteile der Zeile ordnen dem Treibereinen GEMDOS-Laufwerksbuchstaben und eine Gerätenummer zu. Für jede dieser Laufwerksbezeichnungen macht MetaDOS einen Initialisierungsaufruf, wobei die hinter dem Doppelpunkt angegebene Gerätenummer als Parameter übergeben wird. Die Adresse dieser Initialisierungsroutine steht als erster Eintrag in deroben erwähnten Einsprungtabelle.

In unserem Beispiel würde also die Initialisierungsroutine einmal mit dem Parameter 'x' auf dem Stack aufgerufen. Dies ist der richtige Zeitpunkt, um Speicher für alle Datenstrukturen zu reservieren, die der Treiber pro Laufwerk benötigt. Da ein MetaDOS-Treiber 'beliebig' viele unabhängige Laufwerke bedienen können muß, dürfen also keinerlei Informationen statisch gespeichert werden -alle Daten gehören in diesen per Malloc() angeforderten Speicherbereich. Als Ergebnis liefert man MetaDOS in DO einen Zeiger auf diesen Puffer zurück (-1 im Fehlerfall). Bei allen weiteren Aufrufen wird MetaDOS dann diesen Zeiger als Parameter übergeben.

Zusätzlich muß man in Register D1 verschiedene Flags setzen. Bit 0 wird gesetzt, wenn alle Dateinamen automatisch auf Großschrift konvertiert werden sollen. In unserem Beispiel wird jedoch zwischen Upperease und Lowercase unterschieden, daher bleibt das Bit ungesetzt.

Über Bit 1 kann man verhindern, daß MetaDOS Pfade automatisch in absolute Pfade konvertiert. Mir ist allerdings bislang kein Fall bekannt, wo das wünschenswert wäre.

Bit 2 wird seit MetaDOS 2.40 ausgewertet und stellt eine Vereinfachung für Read-Only-Medien wie CD-ROMs dar. Es sorgt dafür, daß die Fehlermeldung EWRPRO (schreibgeschützt) gar nicht erst als Critical-Error angezeigt wird (der Benutzer kann an der Tatsache ja sowieso nichts ändern). Alle weiteren Bits sind reserviert und müssen gelöscht bleiben.

Alle weiteren Einträge der Tabelle sind die Einsprünge für die einzelnen GEMDOS-Funktionen, beginnend mit GEMDOS-Opcode 0 bei Offset 4 der Tabelle. Dabei werden nicht alle Funktionen gebraucht. Beispiele dafür sind etwa die Zeichenausgabefunktionen oder Aufrufe wie Dsetdrv(), die das Dateisystem nichts angehen. Für solche Funktionen und diejenigen, die man nicht implementieren will, kann man -1 eintragen.

Wer ,neue' GEMDOS-Funktionen wie Dopendir() unterstützen will, muß MetaDOS mitteilen, wo die Tabelle genau endet. Dazu trägt man vor der Tabelle bei Offset -12 als Kennung „MAGICMET" und bei Offset-2 den höchsten unterstützten GEMDOS-Opcode ein.

Nun ist es allerdings nicht so, daß MetaDOS einfach alle GEMDOS-Aufrufe in unseren Treiber umlenkt. Tatsächlich ist es eine der Hauptaufgaben von MetaDOS, herauszubekommen, welcher Treiber bei welchem Aufruf gemeint ist und die Calls entsprechend zu verteilen oder an das 'normale' GEMDOS durchzureichen.

Ingesamt müssen drei Hauptfälle behandelt werden. Grundsätzlich wird zunächst ein Datei- oder Pfadname inspiziert und geprüft, ob er auf ein MetaDOS-Laufwerk zeigt. in einfachen Fällen wie Fdelete() wird dann die entsprechende Treiberfunktion aufgerufen und die Angelegenheit ist erledigt.

Schwieriger wird es, wenn man eine Datei öffnet (bzw. zu öffnen versucht). Zu diesem Zweck verwaltet MetaDOS eigene Datei-Handles im Bereich über 100. Jeder auf einem MetaDOS-Gerät geöffneten Datei ist so ein Handle und ein acht Longs umfassender Puffer zugeordnet, über den das Dateisystem frei verfügen kann. Bei Fcreate() etwa erhält das Dateisystem Zeiger auf Dateinamen und diese Dateistruktur. Wenn sich die Datei erzeugen läßt, füllt der Treiber die Dateistruktur mit eigenen Informationen und liefert 0 (E_OK) zurück. MetaDOS liefert dann dem Aufrufer ein MetaDOS-Datei-Handle zurück. Bei weiteren Aufrufen wie Fwrite() erhält dann der Dateisystemtreiber wieder einen Zeiger auf diese Dateistruktur. Typische Informationen, die in dieser Struktur gespeichert sein müssen, sind der aktuelle Datei-Offset und eventuell der Open-Modus.

Ganz genauso verhält es sich mit den Funktionen, die über Directory-Handles angesprochen werden:
Dopendir()
Dreaddir()
Dclosedir()
Drewinddir()
Dxreaddir()

Kompliziert wird es aber bei Fsfirst()/Fsnext(), einem mittlerweile klassischen Beispiel für ein mangelhaftes Design eines OS-Interfaces. Wo liegt das Problem? Nun, zum einen werden Datei- und Directory-Handles vom System vergeben. Die DTA-Adresse hingegen kann jedes Programm beliebig setzen. Hinzu kommt, daß es im Gegensatz zu Datei- und Directory-Handles gibt keine klare Methode gibt, um zu erkennen, wann eine DTA Struktur nicht mehr gebraucht wird. Schließlich kann es ja vorkommen, daß nur ein Aufruf von Fsfirst() gemacht wird und die DTA dann nie wieder benutzt wird. MetaDOS bleibt also nichts anderes übrig, als zunächst bei Fsfirst() zu prüfen, ob eines der MetaDOS-Laufwerke betroffen ist. In dem Fall wird ein Zeiger auf die zur Zeit aktuelle DTA zusammen mit der Laufwerkskennung !n einer Tabelle vermerkt. Einträge in dieser Tabelle werden gelöscht, wenn Fsnext() einen Fehler liefert. Läuft die Tabelle über, wird der älteste Eintrag entfernt. Für viele mag das nach einer völlig hinzureichenden Lösung klingen, aber mehr kann MetaDOS nicht leisten, da der gesamte Inhalt der DTA tabu ist: die erste, reservierte' Hälfte ist für das betreffende Dateisystem bzw. das 'normale' GEMDOS reserviert, während der Rest offiziell dokumentiert ist und daher nicht für eigene Zwecke mißbraucht werden kann. Zur Beruhigung mag beitragen, daß auch das Filesystem-Interface im MiNT-Kernel mit solchen Kompromissen leben muß.

Fsfirst() und Fsnext() können also den normalerweise reservierten Teil der DTA benutzen, um interne Informationen zu speichern. Dazu gehören die aktuelle Position im Verzeichnis, die Suchmaske und die Suchattribute ähnlich funktioniert GEMDOS selbst auch. Im dokumentierten Teil der DTA liefert man dann die Suchergebnisse zurück.

Alle Einsprünge erhalten genau die gleichen Stack-Parameter wie die eigentliche GEMDOS-Funktion. Zusätzlich sind folgende Register gesetzt:

A3 zeigt auf den 'Logical Device Buffer Pointer'. Dies ist der laufwerksbezogene Speicherbereich, der bei der Initialisierung angelegt worden ist.

A4 zeigt auf eine ,vorbehandelte' Version des Datei- oder Pfadnamens (Wandlung auf absoluten Pfad etc.).

A5 zeigt für Dateifunktionen auf die Dateistruktur und bei Fsfirst()/Fsnext() auf die DTA.

Reichlich überflüssig ist schließlich A6, das auf die Tabelle mit den Einsprungpunkten zeigt.

Das ganze klingt sicherlich recht trocken, und gerade darum habe ich auch ein einfaches Beispiel als Quelltext zu Verfügung gestellt. Das Cookie-Dateisystem ist ein Read-Only-Dateisystem. Für jeden Eintrag im CookieJar wird eine Datei dargestellt, deren Namen mit dem zugehörigen Cookie identisch ist (aufgepaßt: ausnahmsweise mal kurze anstelle von langen Dateinamen, aber mit Unterscheidung von Groß- und Kleinschreibung). Jede Datei ist vier Bytes lang und enthält den Wert des Cookies (siehe Abbildung 1).

Wer mag, hat allerlei Erweiterungsmöglichkeiten, um mit den Möglichkeiten von MetaDOS herumzuspielen: Freigabe des Schreibzugriffs zum Ändern von Cookie-Inhalten, Löschen, Umbenennen oder Neuanlegen von Cookies usw.

Zum Schluß will ich noch kurz erwähnen, wo denn die Unterschiede zu einem MiNT-Dateisystem sind. Der Hauptunterschied ist, daß MiNT einem XFS-Treiber erheblich mehr Arbeit abnimmt.
Ein paar Beispiele:

Falls Interesse besteht, werde ich mich gerne in einer der nächsten Ausgaben mit einem MiNT-XFS befassen. Bis zum nächsten Monatwünsche ich viel Spaß beim Experimentieren mit MetaDOS!


Julian F. Reschke
Aus: ST-Computer 01 / 1996, Seite 38

Links

Copyright-Bestimmungen: siehe Über diese Seite