Auf der Schwelle zum Licht: Das Geheimnis des GEMDOS TEIL I

Diese Serie behandelt die innere Struktur von GEMDOS, einem der bisher noch kaum erforschten Teile des TOS. Dabei wird auch auf die GEMDOS-Funktionen ausführlich eingegangen, indem neben ihrer Anwendung auch die Funktionsweise und die internen Datenstrukturen erläutert werden. Dabei wurde keine Rücksicht genommen auf die unvollständigen und teilweise falschen „Dokumentationen“ von Atari und Digital Research. Daher sind Widersprüche zu bisherigen Veröffentlichungen, die vorwiegend auf den Original-Dokumentationen beruhen, vorprogrammiert.

Möglich wurde die genaue Beschreibung nur durch eine selbst angefertigte „Recompilation“ von GEMDOS, d. h. Rückübersetzung des Maschinensprache-Kodes in C, in dem GEMDOS und GEM bekanntlich größtenteils geschrieben sind. Bei der Recompilation habe ich, soweit bekannt, die Funktionsnamen, Strukturbezeichnungen und Variablennamen von Atari bzw. Digital Research übernommen. Ansonsten habe ich mir Bezeichner ausgedacht, habe aber versucht, mich an die bekannten anzupassen.

Zum besseren Verständnis sind in den einzelnen Abschnitten auch die globalen GEMDOS-Variablen als C-Definitionen mit angegeben. Deren Adressen sind für das alte TOS vom 6.2.1986 (RAM- und ROM-Version) und das Blitter-TOS vom 22.4.1987 angegeben. Diese Adressen sollten aber nicht in Programmen benutzt werden, da es sich nicht um, von Atari garantierte, Systemvariablen handelt, und die Adressen sich bei jeder neuen TOS-Version ändern. Selbstverständlich kann man aber mit ihrer Kenntnis eigene Untersuchungen am GEMDOS betreiben. Zwischen dem alten und neuen TOS gibt es - was GEMDOS angeht - keine nennenswerten Unterschiede, auf die aber jeweils hingewiesen wird. Insbesondere die angegebenen Fehler sind auch im Blitter-TOS noch unverändert vorhanden.

Bei den meisten Abschnitten sind Patches zur Korrektur von TOS-Fehlern oder Verbesserungen angegeben. Die meisten habe ich über längere Zeit mit TOS im RAM ausprobiert, und die Änderungen sind so vorsichtig, daß auch Programme, die nur mit bestimmten TOS-Versionen Zusammenarbeiten, damit laufen sollten. Trotzdem ist man natürlich schon bei der kleinsten Änderung nie vor irgendwelchen Inkompatibilitäten sicher.

Wenn Sie noch weitere Bugs kennen, bitte ich Sie, diese der ST-Computer-Redaktion mit genauer Beschreibung des Auftretens mitzuteilen, damit ich eine möglichst vollständige Liste (soweit es geht mit Patches) aufstellen kann.

Die „Speicher- und Programmverwaltung“ wird nicht abgehandelt, da dieser Abschnitt schon - in ähnlicher Form wie diese Serie - im ST-Computer-Sonderheft Nr. 2 („TOS intern“) erschien. Trotzdem wird öfter darauf verwiesen, da sein Verständnis an einigen Stellen erforderlich ist.

Allgemeines

GEMDOS ist das von Digital Research Inc. (DRI) entwickelte DOS, das alle (allerdings nicht sehr hohen) Anforderungen erfüllt, die GEM an ein Disketten-Betriebssystem stellt. Da GEM auch (oder gerade?) unter PC-DOS läuft, hat GEMDOS eine sehr ähnliche Struktur wie PC-DOS, selbst die Funktionsnummern sind weitgehend kompatibel.

Das hat leider auch zur Folge, daß einige Fähigkeiten des Atari nicht richtig ausgenutzt werden. So liegt die unflexible Speicher- und Programmverwaltung des PC-DOS, die letztendlich u.a. die Begrenzung der Accessories und Fenster des GEM bedingt, noch in 8-Bit-Rechnern mit segmentiertem Speicher begründet. Auch das software-mäßige Diskettenformat ist PC-DOS-kompatibel, was z. B. zur Begrenzung der Dateinamen auf 8 Zeichen führt. Die interne Verwaltung ist zum Glück etwas „modernisiert“, wie wir in späteren Folgen noch sehen werden, aber trotzdem kann man sagen, daß GEMDOS der „altmodischste“ Teil des TOS ist. Hinzu kommt noch, daß Atari einige Funktionen des GEMDOS bei der Implementation auf dem ST herausgeschmissen hat, vermutlich um ROM-Speicherplatz zu sparen.

Der 68000-Prozessor

Hier möchte ich ein paar allgemeine Eigenschaften des 68000 erwähnen, auf die im folgenden Bezug genommen wird. 68000-Kenner können diesen Abschnitt getrost überspringen.

Der 68000 kann in zwei „Betriebsarten“ laufen, nämlich im Supervisor-Mode und im User-Mode. Der User-Mode ist für Anwenderprogramme gedacht und der Supervisor-Mode für das Betriebssystem. Im User-Mode sind einige Befehle des 68000 nicht ausführbar (z. B. Verändern des Interrupt-Levels), im Atari ST ist außerdem der Zugriff auf bestimmte Speicherbereiche nicht erlaubt. Die Umschaltung zwischen beiden Modi geschieht mit einem Bit des Statusregisters (SR) und ist nur im Supervisor-Mode möglich.

Es gibt für jeden Modus einen Stack-Zeiger (USP und SSP), wobei der gerade aktive Stack-Zeiger identisch ist,- mit dem Register A7. Der vom USP adressierte User Stack (US) liegt unter TOS im freien Benutzerspeicher und wird von jedem Anwenderprogramm selbst verwaltet. Supervisor Stacks (SS) gibt es mehrere, da große Teile des TOS im Supervisor Mode abgearbeitet werden. Der vom GEMDOS benutzte SS wird noch näher erläutert.

Der TRAP-Befehl des 68000-Prozessors ist praktisch ein Software-Interrupt, der dem schnellen Aufruf oft benötigter Routinen dient. Da der Prozessor beim TRAP-Befehl über einen Vektor aus der vom TOS initialisierten 68000-Exception-Tabelle springt, benötigt der TRÄP-Befehl keine absoluten oder relativen Sprungadressen, was die Kompatibilität verschiedener TOS-Versionen ermöglicht.

Von den 16 TRAPs (0...15) werden vom TOS vier für den Aufruf von Systemfunktionen belegt: TRAP 1 für GEMDOS, TRAP2 für GEM (AES/ VDI), TRAP 13 für BIOS und TRAP 14 für XBIOS (ein spezieller Teil des BIOS). Die Parameter werden i. a. über den Stack übergeben. Auch die verschiedenen Teile des TOS selbst rufen sich i. a. über die TRAPs auf.

Stößt der 68000 auf einen TRAP-Befehl, so werden der Programm-Zähler (PC) und das Status-Register auf den Supervisor-Stack gebracht. Anschließend wird in den Supervisor-Mode gewechselt, d. h. TRÄP-Routinen werden automatisch im Supervisor-Mode abgearbeitet.

Prozessorabhängige Funktionen                       (0,6 kB)
Disk-,Datei-,Directory-Verwaltung (untere Ebene)    (3,4 kB)
Disk-,Datei-,Directory-Verwaltung (mittlere Ebene)  (3,8 kB)
Disk-,Datei-,Directory-Verwaltung (obere Ebene)     (5,1 kB)
interne Speicherverwaltung                          (0,3 kB)
Programmverwaltung                                  (2,1 kB)
Speicherverwaltung                                  (0,7 kB)
Zeichenorientierte Device-I/O                       (2,0 kB)
Initialisierung und Funktions-Dispatcher            (2,4 kB)
Timer-Funktionen                                    (0,6 kB)

Abb.1 - Innere Struktur von GEMOOS

Aufbau und Schnittstellen

GEMDOS ist ein abgeschlossener Teil des TOS und steht nur über wenige Schnittstellen mit dem Rest des TOS und Anwenderprogrammen in Verbindung. Dies erleichterte die Analyse beträchtlich. Das GEMDOS des alten TOS hat eine Länge von 20,2 kB (Code) plus 0,8 kB (initialisierte Daten). Im Blitter-TOS ist es nur unwesentlich länger.

GEMDOS ruft nur BIOS-Funktionen des TRAP 13 auf, darin begründet sich wohl die „künstliche“ Aufteilung der BIOS-Aufrufe auf zwei TRAPs. Daher kennt GEMDOS keine Mäuse, Joysticks, Grafik-Bildschirme, Farbpaletten, usw. GEMDOS arbeitet zwar mehr im Hintergrund, wird aber vom GEM, insbesondere dem Desktop, ausgiebig benutzt. Da sich GEM aber nur auf einige, spezielle Funktionen stützt, fallen viele Fehler im GEMDOS nur in Programmen auf, die gehobenere Ansprüche stellen. Auch gehen einige Fehlfunktionen der Command Line Interpreter auf GEMDOS zurück.

Es werden einige der Systemvariablen ab $400 verwendet, worauf bei den einzelnen Funktionen näher eingegangen wird. Es gibt sonst keine globalen Variablen, die GEMDOS mit einem anderen Teil des TOS teilt, obwohl dies auf den ersten Blick nicht so aussieht, da der Linker die globalen Variablen von BIOS, GEMDOS und VDI bunt durcheinander gewürfelt in einem Adreßbereich abgelegt hat.

GEM und BIOS rufen GEMDOS genau wie Anwenderprogramme nur über den TRAP1 auf. Ausgenommen sind lediglich zwei globale Funktionen, nämlich die vom BIOS aufgerufene GEMDOS-Initialisierungs-Routine und der vom GEMDOS aufgerufene VDI-TRAP2-Händler.

Dieses Konzept wurde beim Blitter-TOS allerdings ein wenig durchbrochen: Jetzt gibt es auch direkte Aufrufe von Teilen des BIOS, sowie XBIOS-Aufrufe (näheres siehe Folge „Timer-Funktionen“).

GEMDOS selbst ist halbwegs modular aufgebaut, denn es gliedert sich in mehrere Teile (Abb. 1), die relativ unabhängig voneinander arbeiten und sich nur über bestimmte Funktionen gegenseitig aufrufen. Trotzdem gibt es einige globale Datenstrukturen, die von den verschiedensten Teilen manipuliert werden. Hierdurch sind wohl etliche, zum Teil schwer zu findende Bugs entstanden. Assembler findet sich übrigens nur im ersten Teil (allerdings nicht ausschließlich), alles andere ist in C programmiert.

GEMDOS-Kern

So, nach diesen allgemeinen Bemerkungen geht’s jetzt zur Sache. Beginnen wir da, wo GEMDOS zum Leben erweckt wird, nämlich beim Reset. Heute geht’s leider noch ein wenig maschinennah zu, da Kenntnisse des 68000 für das volle Verständnis der GEMDOS-Einbindung ins TOS erforderlich sind.

Initialisierung durch das BIOS

Gegen Ende der Reset-Routine, unmittelbar vor dem Setzen des Default-Systemdatums und dem Booten von Floppy und Harddisk, ruft das BIOS die Routine ’dos_init’ auf, die noch zum BIOS selbst gehört (obwohl sie in einem bekannten BIOS-Listing fehlt). Hier werden vier 512-Byte-Disk-Puffer (vom BIOS als globale Variablen deklariert) für GEMDOS bereits gestellt, indem sie in die GEMDOS-Puffer-Listen (Systemvariable ’bufl’, näheres siehe spätere Folge über „FAT- und Dateiverwaltung“) eingehängt werden. Anschließend wird die GEMDOS-eigene Initialisierungsroutine ’os_init’ (DRI-Bezeichnung) aufgerufen und zu guter Letzt wird das in der Systemvariablen ’bootdev’ hoffentlich korrekt angegebene Boot-Laufwerk als aktuelles GEMDOS-Laufwerk gesetzt.

’os_init’ installiert GEMDOS im Betriebssystem. Dazu wird der TRAP1-Vektor auf die GEMDOS-TRAP1-Routine gesetzt. Kurioserweise kümmert sich GEMDOS auch um TRAP2, obwohl der nur vom GEM benutzt wird. Die GEMDOS-TRAP2-Routine wird dabei vor eine eventuell schon vorhandene TRAP2-Routine gehängt. Da das BIOS nur eine nichts-tuende TRAP2-Routine (sie besteht nur aus einem RTS-Befehl) vorher definiert hat, ist dieses „Feature“ ungenutzt. Desweiteren wird die Systemvariable ’etv_timer’ auf die GEMDOS-Timer-Interrupt-Routine, die für die GEMDOS-Uhr zuständig ist, gesetzt. Zu guter Letzt erfolgt der Aufruf von ’mc_init’ zur Initialisierung von Speicherverwaltung und zeichenorientierter Ein-/Ausgabe.

Dazu wird durch die BIOS-Routine Getmpb der Memory Parameter Block gefüllt und die Memory Descriptor Listen werden eingerichtet (s. „Speicherverwaltung“). Im internen GEMDOS-Speicher wird der „Ur-PD“ eingerichtet und als aktueller Prozeß definiert (s. „Programmverwaltung“). Hier werden die zeichenorientierten I/O-Standard-Devices (CON, AUX, PRN) festgelegt. Desweiteren werden die GEMDOS-eigenen Puffer für diese Devices gelöscht (s. spätere Folge „Zeichenorientierte Devices“).

Damit ist die Initialisierung des GEMDOS auch schon beendet. Bei allen anderen Datenstrukturen erledigt sich dies „von selbst“, da sie schon vom BIOS bei der allgemeinen Speicher-Lösch-Aktion des Resets auf ihren Ausgangswert (nämlich Null) gebracht wurden.

TRAP2-Handler

Wenn im Prozessor-Register DO der Wert $73.w steht, wird der VDI-TRAP2-Handler (DRI-Bezeichnung ’vdi__entry’) aufgerufen.

Bei einem Wert von $00.w wird der Stack-Zeiger auf das, durch die Variable ’err_stack’ (Abb. 6) bezeichnet, Ende eines besonderen Stacks gesetzt. Der aktuelle Prozeß wird mit Pterm(0) beendet. Falls das Pterm fehlschlagen sollte, gibt es 4 Bomben, da unmittelbar nach dem Pterm-Aufruf der nicht existierende Befehl $4afc und ein RTE-Befehl folgen. Oder sollte dies vielleicht noch ein zum Debuggen eingesetzter Breakpoint sein? Dies ist meines Wissens nicht dokumentiert, scheint allerdings auch nicht sehr sinnvoll, denn wer will sein Programm schon mit einem TRAP2-Befehl beenden?

Bei anderen Werten von D0 wird, wie schon erwähnt, eine bei der Initialisierung schon vorhandene TRAP2-Routine angesprungen.

Auch das AES wird ja bekanntlich über den TRAP2 aufgerufen, aber das AES hat seinen eigenen TRAP2-Handler, der erst später vom AES selbst installiert wird, also zuerst abgearbeitet wird und dann seinerseits den GEMDOS-Handler aufruft.

Abb.2 - GEMDOS-Funktionsaufruf

TRAP1-Handler

Als erstes wird überprüft, ob die Super-Funktion ausgeführt werden soll. Super ist die einzige Funktion, die nicht vom Funktions-Dispatcher verarbeitet wird, sondern schon im TRAP1-Handler abgefangen wird.

Die im folgenden beschriebenen Vorgänge sind in Abb. 2 grafisch dargestellt. Wurde die Super-Funktion nicht angesprochen, so werden die Register D0,A3 - A6 im PD (Prozeß-Descriptor) des aktiven Prozesses gespeichert (s. „Programmverwaltung“). Außerdem werden D1-A2,PC,SR und der nicht aktive Stack-Zeiger (USP bzw. SSP) auf den aktiven Stack in dieser Reihenfolge geschoben. Erfolgte der Aufruf aus dem Supervisor-Mode, so werden dabei die schon darauf befindlichen Register PC und SR überschrieben. Der Beginn dieser Register-Liste wird im PD (p_reg) vermerkt. Nun wird der SSP auf das Ende des GEMDOS-eigenen Stacks gesetzt. Dieser Stack wird nur für die Abarbeitung der eigentlichen GEMDOS-Funktionen verwendet. Auf diesen Stack wird sogleich der aktive Stack-Zeiger (Wert vor dem TRAP 1-Aufruf) geschrieben. Jetzt erfolgt der Aufruf von ’dos_entry’ wo der TRAP1 weiter ausgewertet wird, ’dos_entry’ und damit auch die GEMDOS-Funktionen werden immer inr Supervisor-Mode durchlaufen. Abb. 2 zeigt den Inhalt der verschiedenen Stacks unmittelbar vor dem Aufruf der eigentlichen GEMDOS-Punktion von ’dos_entry’ aus.

Nach Ausführung der Funktion wird der Rücksprung ins Programm durchgeführt; dies geschieht mit der gleichen Routine, die auch von den Programmverwaltungs-Funktionen verwendet wird. Das Register D0, das den Rückgabewert für den Aufrufer enthält, wird im PD zwischengespeichert. Anschließend werden alle Register einschließlich PC in umgekehrter Reihenfolge wie oben restauriert, was zur Beendigung des TRAP1-Befehls führt.

Abb.3 - Super-Funktion aus User-Mode aufgerufen

GEMDOS-Funktionen

Funktion $20 Super

long Super(long newsp)

Anwenderprogramme werden von GEMDOS immer im User-Mode gestartet. In einigen Fällen, z. B. beim nur im Supervisor-Mode möglichen Zugriff auf die Systemvariablen von $0-$7ff, ist es jedoch erforderlich, einige Mode ablaufen zu lassen. Mit der Super-Funktion ist es nun "möglich, zwischen beiden Modi hin- and herzuschalten.

Wird Super erstmals, also aus dem User-Mode heraus aufgerufen, wird in den Supervisor-Mode gewechselt. ’newsp’ wird als neuer SSP gesetzt. Soll der US auch als SS benutzt werden, d. h. der USP als SSP übernommen werden, so muß 0L als ’newsp’ übergeben werden. Der alte SSP (vor dem Funktionsaufruf) wird zurückgegeben. Der USP wird nicht verändert.

Im ersten Fall (’newsp’ ungleich OL), sollte die nach TRAP-Befehlen übliche Stack-Korrektur (hier ADDQ.L #6,SP) entfallen, da die Funktions-Parameter noch auf dem US liegen und der SSP, auf den sich die Stack-Korrektur beziehen -würde, schon auf die durch ’newsp’ vorgegebene Stack-Spitze zeigt.

Aus dem Supervisor-Mode heraus aufgerufen, erfolgt ein Wechsel in den User-Mode, ’newsp’ wird als neuer SSP gesetzt. Dies sollte i.a. der, vor dem Wechsel in der Supervisor-Mode gültige, also der vom ersten Super zurückgegebene, SSP sein.

Es muß jedoch darauf geachtet werden, daß der SSP vor dem zweiten Aufruf von Super gleich dem aktuellen USP ist, da der USP nur in diesem Fall unverändert bleibt. Diese Bedingung ist erfüllt, wenn die beiden Super-Befehle in der gleichen Prozedur-Ebene liegen und keine weiteren Stack-Manipulationen gemacht wurden. Wird diese Bedingung nicht eingehalten, so wird auf Grund eines merkwürdigen Verhaltens der Super-Funktion, um nicht zu sagen eines Fehlers, der User Stack durcheinander gebracht, was normalerweise zu einem Absturz in Form von Bömbchen führt (genauere Beschreibung für den Spezialisten s. „Arbeitsweise“).

Hat ’newsp’ den Wert 0L, so bleibt der SSP unverändert und wird in den USP übertragen, so daß ab nun derselbe Stack in beiden Modi benutzt wird.

Wird mit Super in den User-Mode umgeschaltet, so wird nichts Gescheites zurückgeliefert.

Ist der übergebene Parameter 1L so wird 0L bzw. -1L zurückgegeben, wenn der User- bzw. Supervisor-Mode aktiv ist. In, der DRI-Dokumentation sind hier die Bedeutungen der Werte 1L und -1L vertauscht. Ein Patch ist hier nicht sinnvoll, da es vermutlich Programme gibt, die Super so benutzen, wie es tatsächlich funktioniert, und daher nicht mehr laufen würden, wenn man Super an die Dokumentation „anpassen“ würde.

Beim Programm-Ende (Pterm) werden auch SSP, USP und SR auf die, vor dem zugehörigen Pexec des parent-Prozesses geltenden, Werte gesetzt. Veränderte Stack-Zeiger sollten sich somit nach Programm-Ende nicht negativ auswirken.

An dieser Stelle ist auch noch eine Bemerkung über den Supervisor Stack angebracht. Wie schon bei „Programmverwaltung“ erklärt, richtet Pexec beim Start eines jeden Programms einen eigenen 3 kB großen Supervisor-Stack ein. Da GEMDOS bei jedem Pexec denselben SS anlegt, wird also der SS des parent-Prozesses überschrieben.

Abb.4 - Super-Funktion aus Supervisor-Mode aufgerufen

Normalerweise kommt es aber nach Beendigung des Tochterprozesses nicht zur erwarteten Katastrophe, da dieser SS kaum benutzt wird, d. h. leer ist. Programme ändern den SSP beim Wechsel in den Supervisor-Mode. Hauptsächlich dient der SS als Kurzzeit-Stack für Exception-Behandlungen (u. a. Hardware-Interrupts) usw., die nicht mit einem Pexec-Aufruf kollidieren können. Vermutlich hängt hiermit aber ein Fehler der GEM-AES-Funktion shel_write zusammen, die offenbar einen Pexec-Aufruf mit nicht-leerem SS macht.

Arbeitsweise

Die Arbeitsweise der Super-Funktion ergibt sich im wesentlichen schon aus seiner Funktionsweise. Der Zustand des Stacks unmittelbar nach Beenden des TRAP1-Befehls, ist in den Abbildungen 3 und 4 dargestellt. Die Bezeichnungen „vorher“ und „nachher“ bei den Stack-Zeigern beziehen sich dabei auf die Zustände unmittelbar vor und nach dem TRAP1-Befehl. $20 ist die GEMDOS-Funktionsnummer von Super.

Der Wechsel in den jeweils anderen Modus wird dadurch erreicht, daß die immer auf dem aktuellen SS liegenden PC und SR auf den neuen SS geschoben werden. Dann wird das S-Bit im SR auf dem Stack geändert. Der TRAP1-Handler endet mit einem RTE-Befehl, der PC und SR vom SSP lädt. Der neue Modus wird also unmittelbar nach Ende des TRAP1-Befehls aktiv.

Noch ein Wort zur Zerstörung des USP beim Wechsel von Supervisor- nach User-Mode: Meiner Meinung nach wäre es sinnvoll, entweder den USP so zu lassen wie er ist, und damit den Programmierer dafür sorgen zu lassen, daß er einen sinnvollen Wert hat, oder automatisch den SSP in den USP zu übertragen, was einen Wechsel in den User-Mode zu jeder Zeit ermöglicht, vorausgesetzt, das erste Super wurde mit Null aufgerufen. Statt dessen wird, wie oben schon gesagt, extra abgefragt, ob USP und SSP „zufällig“ gleich sind. Ist dies nicht der Fall, wird das erste Longword vom SS auf den US gebracht, der USP wird demnach um 4 verkleinert. Dies ist ein ziemlich unsinniges Verhalten, da das erste Longword aus der Funktionsnummer (Word $20) und dem upper word des übergebenen Parameters ’newsp’ besteht. Der US wird also mit sinnlosen Daten bestückt, so daß ein Absturz fast zwangsläufig ist.

Anwendung

Im allgemeinen wechselt man zuerst in den Supervisor-Mode mit Übernahme des USP als SSP, merkt sich den alten SSP und restauriert ihn wieder beim Wechsel in den User-Mode. Hat man mehrere Programmteile, womöglich auf verschiedenen Unterprogramm-Ebenen, die hin- und herschalten, muß man sich jederzeit sicher sein, in welchem Modus der Prozessor sich gerade befindet, da Super den Modus ja immer wechselt. fdinzu kommt noch das oben erwähnte Problem des zerstörten USP.

All diese Schwierigkeiten werden durch die Funktionen supon() und supoff() (Abb. 5) beseitigt. Supon() schaltet jederzeit in den Supervisor-Mode und supoff() kehrt wieder in der User-Mode zurück. Sie sorgen dafür, daß ein Modus-Wechsel nur stattfindet, wenn er nötig ist und garantieren jederzeit richtige Stack-Zeiger.

Die Funktionen dürfen jedoch nicht von zu verschiedenen Zeiten gestarteten, getrennten Programmteilen (z. B. GEM-event-Routinen, Multi-Tasking) gemeinsam benutzt werden, da sie sonst nicht mehr wissen, welcher Modus gerade aktiv ist.

Aus mir nicht bekannten Gründen darf GEM, zumindest das AES, nicht im Supervisor-Mode aufgerufen werden, sonst bombt es. Bei BIOS und GEMDOS gibt es jedoch keine Schwierigkeiten.

    /* Routinen zur Benutzung der GEMOOS-Funktion Super */
    /* entwickelt mit MEGAMAX C */

    static long ssp = 0L; 
    extern supon(), supoff();

    asm
    {
    supon:
        tst.l   ssp(A4)
        bne.s   supon1          ;-> schon im Supervisor-Mode
        clr.l    -(A7)
        move.w  #0x20,-(A7)     ;Super-Funktion
        trap    #1              ;mit Übernahme des USP als SSP
        addq.l  #6,A7
        move.l  D0,ssp(A4)      ;jetzt im Supervisor-Mode
    supon1: 
        rts
    ;
    supoff:
        move.l  ssp(A4),D0
        beq.s   supoff1
        move.l  D0,-(A7)        ;alten SSP restaurieren
        move.w  #0x20,-(A7)     ;Super-Funktion
        move.l  A7,USP          ;Korrektur USP
        trap    #1
        addq.l  #6,A7
        clr.l   ssp(A4)         ;jetzt im User-Mode
    supoff1: 
        rts
    }

Abb.5 - Anwendung der Super-Funktion

Funktion $30 Sversion

int Sversion()

Diese Funktion ist nur der Vollständigkeit halber aufgeführt. Sie liefert die Versionsnummer von GEMDOS zurück. Diese Versionsnummer ist unabhängig von der TOS-Version, die im TOS-Header vermerkt ist (Adresse $FC0002). Die Versionsnummer ist $1300L für altes und neues TOS.

GEMDOS-Funktions-Dispatcher

Hier wird das vom TRAP1-Handler aufgerufene ’dos_entry’ beschrieben. ’dos_entry’ ist ein Muster-Beispiel für schlechten C-Programmier-Stil: Die Funktion ist viel zu lang, unübersichtlich und tief geschachtelt, vereint sehr unterschiedliche Funktionen in einer, enthält goto-Statements usw. Daher ist es auch kein Wunder, daß sie Fehler und überflüssige Teile enthält.

GEMDOS hat in seinen globalen, initialisierten Daten eine Tabelle (’dos_fx’, s. Abb. 6), in der für jede Funktionsnummer von 0 bis $57 folgende Struktur existiert:

typedef struct
{       long(*df_adr)();    /* 0: Zeiger auf GEMDOS-Funktion */
        int df_type:        /* 4: Funktions-Typ */
} DOS__F;

’df_adr’ ist die Adresse der GEMDOS-Funktion bzw. ’ill_func’, falls die Funktionsnummer nicht definiert ist. ’ill_func’ gibt die Fehlermeldung EINVFN (-32L, „ungültige Funktionsnummer“) zurück und wird auch angesprungen, wenn die Funktionsnummer größer als $57 ist.

’df_type’ ist ein Flag und gibt an, wie die Funktion von ’dos_entry’ im weiteren behandelt werden muß:

0 Parameter 0-2 Words
1 Parameter 3-4 Words
2 Parameter 5-6 Words
3 Parameter 7 Words

Im wesentlichen gibt das Flag also an, wie lang die Parameterliste des Funktionsaufrufs ist.

In ’dos_entry’ werden auch Teile der I/O-Umleitung durchgeführt, mit der ich mich noch in einer späteren Folge ausführlich beschäftigen werde.

Einige Funktionen, die durch I/O-Umleitung beeinflußt werden, sind durch ein gesetztes Bit 7 gekennzeichnet. Dies ist recht wahllos und nutzlos, da die meisten umleitbaren Funktionen sowieso durch direkte Abfrage der Funktionsnummern aussortiert werden. Bei den zeichen-orientierten Funktionen haben die niederwertigen Bits des Flags eine andere Bedeutung: Hier geben Sie das Standard-Handle (0-3) an, das dem jeweiligen Device zugeordnet ist. In ’dos_entry’ findet nun ein Teil der I/O-Umleitung statt, da die in der Tabelle angegebenen eigentlichen Funktionen nur I/O-Funktionen für die primär vorgesehenen I/O-Geräte beinhalten. D. h., ’dos__entry’ enthält die Umleitung der zeichenorientierten Funktionen (Cconout etc.) auf Dateien und der Datei-Funktionen (Fwrite etc.) auf Devices. Die Umleitung „Device nach Device“ geschieht dagegen in den zeichen-orientierten Funktionen selbst.

Dabei werden z. B. bei der Umleitung „Daten nach Device“ direkt die zeichenorientierten I/O-Funktionen von GEMDOS aufgerufen. Anschließend wird ’dos_entry’ mit Weitergabe des Rückgabewertes beendet.

Die Funktionen, die nicht von der I/O-Umleitung erfaßt wurden, werden nun aufgerufen. Da GEMDOS jetzt auf seinem eigenen Stack arbeitet, müssen die Parameter vom Stack des aufrufenden Programms zum GEMDOS-Stack kopiert werden. Dabei wird stets die, durch das Flag bestimmte, Maximalzahl von Word-Parametern übergeben, das sind also i. a. zu viele. Der Rückgabewert der Funktion wird direkt an den TRAP1-Handler (im Register D0) zurückgegeben.

Diskettenfehler-Behandlung

Zur Behandlung von Diskettenfehlern wird ziemlich am Anfang von ’dos_entry’ die C-Standardfunktion ’setjmp’ verwendet. Sie sorgt dafür, daß wenn auf einer sehr tiefen Ebene des GEMDOS beim Diskettenzugriff über das BIOS Fehler festgestellt wurden, mit ’longjmp’ direkt nach ’dos_entry’ zurückgesprungen wird, unter überspringen aller dazwischen liegenden Ebenen. Zu beachten ist, daß GEMDOS die Fehlermeldung erst erhält, wenn das BIOS alle eigenen Möglichkeiten der Fehlerbehandlung ausgeschöpft hat (mehrere Versuche, Dialog mit dem Anwender über Critical Error Handler-Alert-Boxen).

Bei allen Fehlern außer -14L (Diskettenwechsel) werden alle noch von GEMDOS in den Listen ’bufl’ gepufferten Sektoren des Laufwerks, bei dem der Fehler auftrat, für ungültig erklärt (die Pufferung wird noch in der Folge „FAT- und Dateiverwaltung“ behandelt). Das hat also zur Folge, daß beim nächsten Diskzugriff die Sektoren erneut geladen werden müssen. Die GEMDOS-Funktion wird durch Rückgabe der BIOS-Fehlernummer beendet.

Tritt ein Diskettenwechsel auf, so sorgt GEMDOS dafür, daß alle dynamischen Datenstrukturen, die für die Verwaltung des Laufwerks, seiner Directories und seiner offenen Dateien existieren, freigegeben werden, damit der, durch diesen gebrauchten internen GEMDOS-Speicher, wieder zur Verfügung steht. Wenn das Laufwerk (i. a. mit einer anderen Diskette) noch ansprechbar ist, wird die gesamte GEMDOS-Funktion noch einmal aufgerufen, nicht etwa nur die BIOS-Funktion, bei der der Fehler aufgetreten ist. Die Anzahl der Versuche, eine Funktion auszuführen, wird in ’err_ent’ zwar mitgezählt, aber nirgendwo mehr abgefragt, was soll’s dann überhaupt?

Ist das Laufwerk nicht mehr verfügbar, so wird es als unbekannt für GEMDOS deklariert, und die Funktion wird durch Rückgabe der BIOS-Fehlernummer beendet. Eine genauere Beschreibung erfolgt in der Folge „Disk-und Directory-Verwaltung“, nachdem die beteiligten Datenstrukturen erläutert wurden.

Globale Variablen

Die von den beschriebenen GEMDOS-Teilen verwendeten globalen Variablen sind in Abb. 6 zusammengefaßt. Die Adressen sind abhängig von der TOS-Version und sollten daher nicht in eigenen Programmen verwendet werden. Die erste Adresse bezieht sich auf das alte TOS, die zweite auf das Blitter-TOS. Bei den initialisierten Variablen gelten die Adressen für altes RAM-TOS, altes ROM-TOS und Blitter-TOS.

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

00f098      fc920a      fc94c2      48 e7 03 0c 28 6f 00 14
00f110      fc9282      fc953a      4e 71

Abb.7 - TOS-Patch für Dlskettenwechsel-Fehler

TOS-Patches

Die Fehler der Super-Funktion sollten aus Kompatibilitätsgründen nicht korrigiert werden. Der größte Teil der (bekannten) Fehler liegt in ’dos_entry’ und betrifft die I/O-Umleitung. Da einige Fehler mit anderen in den eigentlichen I/O-Routinen in Wechselwirkung stehen, werden sie geschlossen in der Folge „I/O-Umleitung“ besprochen.

Bei der Behandlung des Diskettenwechsels gibt es jedoch einen merkwürdigen Fehler. In einer Funktion, die alle von offenen Dateien benötigten internen Datenstrukturen freigibt, ist ein Parameter offensichtlich als C-REGISTER-Variable deklariert worden. Der Compiler hat jedoch anscheinend „vergessen“, den Befehl einzufügen, der den Parameter in das Register überträgt, so daß das Register keinen definierten Inhalt hat. Dies führt glücklicherweise „nur“ dazu, daß besagte Datenstrukturen nicht freigegeben werden. Die Konsequenzen daraus kann ich zur Zeit noch nicht absehen, aber all zu schlimm wird es wohl nicht sein, da ich keine tiefgreifenden Veränderungen an GEMDOS seit Beseitigung des Fehlers vor einigen Monaten feststellen konnte. Vielleicht werden nur Datei-Handles „verbraucht“. Der Patch zur Beseitigung dieses Fehlers ist in Abb. 7 angegeben.

Nächstes Mal geht es um die Fehlermeldung von GEMDOS und BIOS, den Critical Error Händler und um die GEM-AES-Funktion form_alert.

long pcsav;     /* $0e50/$0eb0: Zwischenspeicher für PC
            bei BIOS-Aufruf (TRAP13) */ 
int srsav;      /* $0e58/$0eb8: Zwischenspeicher für SR */ 
long otimer;    /* $1672/$16d2: etv_timer-Vektor vor GEMDOS-Initialisierung */ 
long otrap2;    /* $1676/$16d6: trap2-Vektor vor 6EMD0S-Init. */ 
long sjmp1[3];  /* $5752/$7ef4: Puffer für setjmp/longjmp */ 
long sjmp2[3];  /* $4dbe/$755c: Puffer für setjmp/longjmp */ 
long dsk_err;   /* $4e12/$75b4: BIOS-Disk-Error für GEMDOS-Fehlerbehandlung */ 
int err_drv;    /* $602a/$87cc: Drive des BIOS-Disk-Error */ 
int err_cnt;    /* $4158/$68fa: s. Diskettenfehler-Behandlung */ 
long err_stack = 0x166eL; /* bzw. $16ce */
    /* $17ae2/$fdlbf2/$fd301e: Adresse Stack für TRAP2-Error */ 
DOS_F dos.fx[]  /* $17b98/$fd1ca8/$fd30d4: s. GEMDOS-Funktions-Dispatcher */
={{ p_term, 0 },
{ c_conln, 0x80 },
{ c_conout, 0x81 },
{ c_auxin, 8x82 },
{ c_auxout, 0x82 },
{ c_prnout, 0x83 },
{ c_rawio, 0 },
{ c.rawcin, 0x80 },
{ c_necin, 0x80 },
{ c.conws, 0x81 },
{ c_conrs, 0x80 },
{ c_conis, 0x80 },
{ ill_func, 0 },
{ ill_func, 0 },
{ d_setdru, 0 },
{ ill_func, 0 },
{ c_conos, 0x81 },
{ c_prnos, 0x83 },
{ c.auxis, 8x82 }, 
{ c_auxos, 0x82 }, 
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ d_getdru, 0 },
{ f_setdta, 1 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 8 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 }.
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ t_getdate, 0 },
{ t_setdate, 0 },
{ t_gettime, 0 },
{ t_settime, 0 },
{ ill_func, 0 },
{ f_getdta, 0 },
{ s_version, 8 },
{ p_termres, 1 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ d_free, 1 },
{ ill_func, 8 },
{ ill_func, 0 },
{ d_create, i },
{ d_delete, 1 },
{ d_setpath, 1 }, 
{ f_create, 1 },
{ f_open, 1 },
{ f_close, 0 },
{ f_read, 0x82 }, 
{ f_Hrite, 0x82 }, 
{ f_delete, 1 },
{ f_seek, 0x81 }, 
{ f_attrib, 1 }.
{ ill_func, 0 },
{ f_dup, 0 },
{ f_force, 0 },
{ d_getpath, 1 }, 
{ m_malloc, 1 },
{ m_free, 1 },
{ m_shrink, 2 },
{ p_exec, 3 },
{ p_term, 0 },
{ ill_func, 0 },
{ f_sfirst, 1 },
{ f_snext, 0 },
{ ill_func, 0 },
{ ill_func, 0 },
{ ill_func, 8 }.
{ ill_func, 0 },
{ i1l_func, 0 },
{ ill_func, 0 },
{ f_rename, 2 },
{ f_datime, 1 }};

Alex Esser
Aus: ST-Computer 12 / 1987, Seite 108

Links

Copyright-Bestimmungen: siehe Über diese Seite