← ST-Computer 12 / 1987

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

Grundlagen

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