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 }};