Anhang: Beispiele und aktuelle Ergänzungen
* SQ: Atari-QL, SMSQ, SMSQ/E
Insbes. die SMSQ-Varianten weisen derart viele und versionsweise unterschiedliche Fehler auf, oder "Verbesserungen", jedenfalls aber Abweichungen von den hier betrachteten Referenz-Systemen, man benenne das nach Belieben, daß es hier nur in einigen Fällen möglich war, brauchbare Hinweise zu geben. Der Leser wird darum gebeten, eigene Kontrollen vorzunehmen, und ggf. nach Konsultation der Original-Dokumente tiefergehende Auskunft beim Verursacher zu reklamieren.
Die sporadisch in den Text aufgenommenen Hinweise auf die SQ-Eigenheiten sind nur Angelpunkte, die bei Bedarf überprüft werden sollten. Vor allem, da kaum Details bekanntgemacht werden, war das Wenige allein schon der Sicherheit wegen äußerst kritisch einzuordnen.
Dem ließe sich leicht abhelfen: Durch ordentliche Dokumentation...Wer diesbezüglich Erhellendes beizutragen vermag und geneigt ist, das allein im Interesse der QDOS-"Gemeinde" öffentlich zu machen, ohne jede Aussicht auf direkten finanziellen Gewinn, wird händeringend eben darum gebeten.
* Programm(ier)beispiele
* Befristet intallierte entfernbare System-Erweiterungen
Ein Beispiel soll beschreiben, wie Systemerweiterungen in Gestalt von Device-Handlern und Interrupt-Aufrufen an einen Job gebunden werden können, wodurch sie dann ohne weitere Vorkehrungen selbstätig mit dessen Aufgabe in geordneter Form aus dem System verschwinden.Es soll dazu ein kleiner Timer eingerichtet werden, der nur als kontinuierlich weitergezählter Langwortinhalt einer Speicherstelle existiert. Deren besondere Lage erlaubt äußerst schnelle Ausführung und entsprechend geringe Zeitbelastung des Systems durch diesen Aufruf.
16 Bytes werden für Zähler und Systemliste reserviert:
timem dc.l 0,0 hierher zeigt später a3, CH.LXINT linktime dc.l 0,0 Positionen CH.LPOLL, CH.APOLL
Die Interrupt-Routine kann an beliebigem Ort stehen, wobei die Lagetimem = linktime - 8die besonders einfache Adressierung möglich macht:
timer addq.l #1,(a3) rts
Eine Freigabe-Routine ist bereitzustellen:
timeunl movem.l a0/a1,-(sp) lea linktime(pc),a0 moveq #29,d0 mt.rpoll trap #1 movem.l (sp)+,a0/a1 rts
Aktiviert wird die Interruptroutine derart:
timelnk lea linktime(pc),a0 lea timer(pc),a1 move.l a1,4(a0) moveq #28,d0 mt.lpoll trap #1 rts
Nun wird ein Teilprogramm eingerichtet, das die sichere Entfernung der betreffenden Handler- oder Interrupt-Routinen aus den Systemlisten vorbereitet. Dazu nutzt man die Vorgänge bei der automatischen Freigabe von Heap-Bereichen, die einem Job zugewiesen wurden. Die geschieht durch das QDOS immer dann, wenn ein Job aufgegeben wird. Es ist daher "nur" dafür zu sorgen, daß das entprechende Teilprogramm zuvor die zusätzlich erforderlichen Freigabeproceduren durchläuft. QDOS selbst entnimmt bei jeglicher Form eines Freigabe-Aufrufs die zugehörige Adresse dem Linkblock des betreffenden Handlers; bei Heap-Zuweisungen ebenso.Ein Heap-Bereich hat in den ersten 16 Bytes einen Header, wie z.B. auch die QDOS-Kanaltabellen. Dort steht auf Offset 4 die Linkadresse CH.LIO des Handlers. Mit Offset 12 dazu ist auf Position CH.ACLOS die Adresse der Freigabe-Routine verzeichnet. Hier besorgt sie die Rückgabe des reservierten Bereichs an den "common heap". Dieser Tabellenkopf ist nun anzupassen:
Man merkt sich die auf Position 4 (CH.DRIVR) vorgefundene Handleradresse. Dort trägt man nun eine Adresse ein, die um 12 niedriger ist als eine neue Tabellenposition (entspr. CH.LIO). An dieser Stelle wird dann die Adresse eines kurzen Umleitungsprogramms eingetragen, das ROM-tauglich seinerseits Platz im gerade reservierten Heap finden kann. Dieses kleine Hilfsprogramm sorgt für den Sprung z.B. in die oben angegebene Routine zur Entfernung des Timers aus der Interrupt-Liste und danach in die ursprügliche "close"- rsp. Freigabe-Routine, wie sie aus der in CH.DRIVR angegebenen Handleradresse und daraus über CH.LIO und CH.ACLOS ermittelt wurde. Das sieht etwa so aus:
Standard Heap-Header:
[ heap-len ][ handler ][ ownerjob ][ system ][ reservierung ]Die Basisadresse des nutzbaren Bereichs liefert mt.alchp in a0.
Es wird nun geändert:[ xx ][ adr1 ][ xx ][ xx ][ jsr timeunl jmp (12(hdl)) ][ adr2 ]wo (adr1) die adr2-12 enthält, die auf den jsr-Befehl zeigt.
linkdev moveq #-1,d2 "aktueller" job ist besitzer moveq #16,d1 für 2 abs.l sprungbefehle + adresse moveq #24,d0 mt.alchp trap #1 move.l d0,d3 ggf. bei speicherüberlauf bne.s jobremv ? den job ganz aufgeben move.l a0,a1 speicherbasis merken move.w jsrcode(pc),(a0)+ lea timeunl(pc),a2 ablage: jsr timeunlk move.l a2,(a0)+ move.w jmpcode(pc),(a0)+ move.l -12(a1),a2 ablage: jmp memclose move.l 12(a2),(a0)+ move.l a2,d3 prüfung (notwendig bei smsq!) bne.s nsmsq ? adresse vorhanden move.w $0c2,a2 sonst mm_rechp move.l a2,-4(a0) verwenden nsmsq move.l a1,(a0) neuer "close"-pointer zeigt move.l a1,-12(a1) nun auf mem+12 = a1+12, wo rts der hilfsaufruf verzeichnet ist jobleave moveq #0,d3 keinen fehlercode weitergeben jobremv moveq #-1,d1 selbst moveq #5,d0 mt.frjob trap #1 rts nur, wenn was schief ging .... jmpcode jmp $11223344 ... zum beispiel, wenn der code jsrcode jsr $11223344 für jmp... und jsr... unbekannt ist
Sind alle diese Vorbereitungen abgeschlossen, erfolgt der Aufruf: .
.... programm jsr linkdev(pc) jsr timelnk(pc) .... programm jmp jobleave(pc)
Der Job wird nun unter allen Umständen stets das System geordnet zurücklassen. Auch die Auflösung durch Befehle wie QR oder RJOB wirkt einwandfrei.
* Basic-Parameter
Namen:
Ergänzend ein Verfahren, mit dem der Name einer Variablen geholt werden kann. Vorausgesetzt wird die Registerbelegung wie für die Vectoren zum Holen der sonstigen Basic-Parameter. Zurückgegeben werden die Werte entsprechend dem Aufruf ca.gtstr. Einsprung "nam_vec" erlaubt schrittweise das Holen mehrerer Namen. Auf dieselbe Weise können die Aufrufadressen residenter Funktionen und Proceduren ermittelt werden. Einige dieser Routinen verlangen D7 := 0 (z.B. PRINT und BEEP).
Die reichlichen Sicherheitsabfragen müssen nicht immer erforderlich sein. Sie sind als Schutz bei verdorbenen Daten gedacht, und evtl. auch nützlich für den Fall mangelhafter Dokumentation des Systems. Namen werden in Zusammenhang mit einer möglichst sicheren Kontrolle gegen Fehler in der Länge auf 127 Bytes begrenzt.
* REIN: a3 a5 a6 Basic-Register der Namen und der Jobbasis
* RAUS: d0 Fehlercode d3 Anzahl (1/accu) a1 String-Basisadresse
* VERDIRBT: d1 d2 d3 d4 a0 a2 und paßt a3 bei Erfolg an
nam_ftch moveq #0,d3 erstmaligen aufruf vorbereiten nam_vec moveq #6,d0 err.nf vorbereiten moveq #15,d2 typenmaske and.b 0(a3,a6.l),d2 aus dem use-byte beq.s nam_fnd ? übergabe kann gelten subq.b #1,d2 beq.s get_nich ? ausdruck subq.b #8,d2 bgt.s get_nich ? verwendung rätselhaft nam_fnd move.l a3,a0 copie zum suchen move.w 2(a0,a6.l),d1 name-index not.l d1 gegen -1 absichern beq.s get_nich ? doch ein ausdruck not.l d1 wert wiederherstellen ext.l d1 vereinfacht die prüfung lsl.l #3,d1 des ermittelten ptr ** Rom-Versionen vor JS ohne Addition aus bv.ntbas von 24(a6) ** nam_tab add.l 24(a6),d1 position in der name-table move.l d1,a0 ror.b #1,d1 bei verdorbenen daten bcs.s get_nich ? adressenfehler vermeiden move.w 2(a0,a6.l),d1 ptr zum namen bmi.s get_nich ? weil es den namen nicht gibt move.w d1,a0 ptr sichern move.l 32(a6),d1 basis der name-list add.l a0,d1 ptr auf den namen moveq #0,d4 register freimachen move.b 0(a6,d1.l),d4 count.b holen ble.s get_nich ? zu lang oder leer move.l d4,d1 übertragen addq.l #3,d1 dazu count.w der ablage and.w #-2,d1 geradzahlig move.w d3,-(sp) accu kellern move.w $11a,a2 mit bv.chrix jsr (a2) basic-speicher reservieren move.w (sp)+,d3 accu zurückholen moveq #3,d1 erneute längenermittlung add.l d4,d1 geschieht schneller als die and.w #-2,d1 stackablage move.l $58(a6),a1 zum alten wert sub.l d1,a1 neue länge im übergaberegister move.l a1,$58(a6) auf bv.rip fixieren move.l a1,a2 arbeiscopie move.w d4,0(a2,a6.l) länge eintragen subq.w #1,d4 zähler add.l 32(a6),a0 bv.chrix kann ptr verändert haben nam_put move.b 1(a0,a6.l),2(a2,a6.l) addq.l #1,a2 byteweise addq.l #1,a0 dbra d4,nam_put alle zeichen übertragen addq.l #8,a3 parm-ptr justieren addq.w #1,d3 anzahl eins weiter moveq #-1,d0 und ohne fehler get_nich not.l d0 ggf. err.om wiederherstellen rts Damit kann ein universeller Aufruf, der statt eines String automatisch den Namen holt, wenn er als Variable nicht definiert ist, so aussehen: stg_parm moveq #0,d3 zähler vorbereiten stg_nxtn move.l 4(a3,a6.l),d2 werte-ptr not.l d2 beq.s stg_name ? vari ohne wert tst.w d3 wenn schon namen da sind bne.s inp_rdy ? besser nicht zum vector stg_name bsr.s nam_vec > den namen als string holen beq.s stg_nxtn ? weiter (ver)suchen tst.w d3 anzahl beq.s inp_tst ? weiter mit 'mi' bei fehler inp_rdy moveq #0,d0 fehlercode war schlußsignal bra.s inp_oke - zur auswertung inp_vec move.w $0116,a2 ca.gtstr als jsr (a2) > regulärer aufruf inp_tst tst.l d0 fehlercode prüfen bne.s inp_err ? es gab einen fehler inp_oke .... .... auswertung und inp_err .... .... fehlerbehandlung
* System-Erkennung
Es gibt etliche Emulatoren des QL in Rechnersystemen aller Art. Sinn solcher Programme ist es u.a. auch, den QL gerade so nachzubilden, daß die Tatsache der Emulation aus dem "Innern" nicht erkennbar wird. Andrerseits gibt es dort im allgemeinen wesentliche Lücken, aufgrund derer manches QL-Programm entweder nur in besonderer Variante oder garnicht einsetzbar ist. Solche Programme scheitern meist an einer dem Anschein nach wohl vorhandenen, nicht aber originalgetreu nachgebildeten Funktion des Rechners.
Jene zu überprüfen gibt es verschiedene Mittel, wie auch verschiedene Kriterien. Unabdingbare und entscheidende Voraussetzung ist allerdings, daß bei Gestaltung der Emulation die QDOS-Konventionen und Entsprechendes der diversen Systemerweiterungen (TK2, etc) strikt beachtet wurden.
Wer ein Nicht-Standard-System nachbildet, sollte insbes. die gerade zur Unterscheidung solcher Varianten eingerichteten Variablen nutzen und sie ggf. durch Anmeldung an geeigneter Stelle "offiziell" machen. Er vergibt sich damit nichts! - Hat allerdings, wie u.a. das Beispiel "SYSTEM" (J.Schiemann) zeigt, damit noch keinerlei Garantie dafür, daß seine Arbeit "höherenorts" auch respektiert würde.
Doch immerhin wäre damit eine hinreichende Information gegeben, gutwilligen Partnern den konstruktiven Umgang mit dem betr. Programm zu ermöglichen...Oder die Nachbildung ist hinreichend exakt. Daß, und mit welch vortrefflichem Ergebnis, auch das möglich ist, zeigt das Beispiel QLAY für IBM-Typ PCs.
Falls nichts von alldem gelingen will, könnte es u.U. womöglich vielleicht eventuell auch einmal angebracht sein, ganz vorsichtig die Einschätzung der eigenen Kenntnis einer etwas strengeren Revision zu unterziehen.
gibt es wohl auch. Der Amiga-QL ist ein Beispiel dafür. Dort muß der TAS-Befehl mühsam umgangen werden, weil die Hardware des Rechners das verlangt. Konsequenz wäre, wo immer möglich, in Programmen das TAS zu vermeiden.
Dieser Test ist einfach, denn jedes System, in dem die Trap FS.XINF (3/79) fehlerfrei ausgeführt werden kann, gilt als "level 2" Filesystem (15), wo die zusätzliche Funktionalität grundsätzlich gegeben ist, und Ergänzungen zur Ur-Version mittels der zurückgegebenen Daten überprüft werden können.
1. Direkt:
Vorhandensein des wirklichen I.P.C. kann mit dem dazu vorgesehenen Aufruf TEST.CMD der Trap MT.IPCOM (1/17) festgestellt werden: Nur, wenn der I.P.C. entweder physisch vorhanden und in Ordnung ist, oder die Emulation ihn ohne jede Ausnahme vollständig und korrekt nachbildet, wird das Parameterbyte der Definition gemäß unverändert zurückgegeben.
Einmalige Wiederholung der Abfrage mit einem anderen Parameterbyte genügt, um zufällige Ergebnisse auszuschließen.Ein Beispiel für einerseits QDOS-compatible Installation und andererseits Verwendung zur Ausgabe systemfremder Informationen gibt der UQLX-QL-Emulator: Dort dient dieser IPCOM-Befehl einer eigenen Kennung, indem er das Komplement des Parameterbyte zurückgibt. Er signalisiert damit zugleich, daß der IPC zwar "vorhanden", aber nicht vollständig funktionstüchtig ist.
tstcmd dc.w $0f01,$aaaa,$aaaa,$5a02 dc.w $0f01,$aaaa,$aaaa,$a502 ipctest lea tstcmd(pc),a3 IPC-steuerung bsr.s qlhw > bne.s ipt_ret ? addq.l #8,a3 qlhw moveq moveq
#0,d1 #17,d0
sicherheitshalber mt.ipcom
trap #1 move.b 6(a3),d6 gesendetes byte eor.b d1,d6 empfangenes or.b d0,d6 'eq' nur mit IPC ipt_ret rts
2. über Systemvariable
Liefert das Uhrenregister über mehrere Sekunden hinweg stets denselben Wert, ist sicher irgendeine Emulation in Betrieb, und der IPC ist nicht vorhanden oder äußerst mangelhaft nachgebildet.
Von Experimenten mit speziellen Processorbefehlen, die nur auf dem einen oder dem anderen Gerät ausführbar sind und sonst in eine Ausnahme-Trap führen, wird dringend abgeraten. Die Folgen sind oft unangenehmer, als beim Betrieb des betr. ungeeigneten Programms (Programme z.B., wo direkt codiert an den Cache-Zuständen herumgefummelt wird).
Es gibt sichere Alternativen! Als da sind:
1. Der MOVEM-Befehl arbeitet unter gewissen Umständen mit unterschiedlichem Ergebnis:
MOVEM.L SP,-(SP)
CMP.L (SP)+,SPWährend in allen 680xx-Processoren die Adressregister stets mit dem zum Aufrufzeitpunkt geltenden Wert in den Processor übernommen werden, legt ab 68020 dieser Befehl den um 4 verminderten Wert von SP ab, was aber einzig für A7 gilt! Durch Vergleich SP mit dem abgelegten Wert läßt sich daher zwischen diesen Processorgruppen unterscheiden: Die obige Sequenz liefert nur bei Processoren vor 68020 den Flag-Zustand 'eq'.
2. Der Cache-Test mit SMS.CACH (1/47) gibt zum einen Aufschluß über den Status der Processorcache, zum andern aber auch, ob überhaupt ein geeigneter Processor installiert ist. Bei 6800x oder 6801x kommt ERR.BP zurück, da es dort dergleichen nicht gibt. Auch die üblichen Emulatoren werden im allgemeinen die ERR.BP-Rückgabe auslösen.
Die Trap arbeitet beim Lesen ohne jede Auswirkung auf das Sytem, kann also sicher zum Test benutzt werden.3. Systemvariable
Die Posten SYS.PTYP ($A1) und SYS.MTYP ($A7) geben recht detaillierte Aukunft über Processor-, Bildschirm- und Geräte-Typ, sofern sie denn vom System bedient werden, was leider nicht in allen anwendbaren Fällen zutrifft. - Im wirklichen QL kann SYS.PTYP ohnedies nicht eingerichtet werden, da die ungeschickte Wahl der SMSQ-Adresse dort zur Kollision mit SV_TIMO führt...
Mehr dazu an entsprechender Stelle im Tabellenteil.Ungeeignete Vorgehensweise führt gerade hier in vielen existierenden Programmen leicht zu Fehlern.
So ist etwa von vornherein offensichtlich, daß insbes. die Größe einer cdt zur Unterscheidung kaum brauchbar ist. Gerade dieser Wert hat aus der Sicht einer Anwendung in weitestem Sinne nicht das geringste mit einem bestimmten Kanaltypen zu tun.
Wenn dennoch aus der Größe der CON-cdt auf Vorhandensein des PIF oder einer anderen CON-Erweiterung geschlossen wird, mag das angehen, und es ist wegen der u.U. gänzlich nutzlosen Prüfroutinen des PIF mitunter das einzige Mittel.
Handlervergleich mit Kanal 0 ist aber für CON-Kanäle, falls nicht gerade ein Irrsinniger ausgerechnet diesen QDOS-Kanal geschlossen oder abweichend definiert hat, die schellere und zuverlässigere Prüfung.Und ist zwar Lesen der Bildparameter mit z.B. SD.CHENQ (3/11) möglich, oder liefert es ERR.NC (-1), und der Handler derselbe wie der des Kanals 0, aber bei ausreichender Länge die cdt ohne Eintrag an der Stelle SD.LINEL+4, so hat man es mit einem SCR-Kanal zu tun.
Als weiteres Mittel, das insbes. in größeren oder über einen längeren Zeitraum zu betreibenden Programmen probat ist, bietet sich vorübergehendes Öffnen eines Prüfkanals der gewünschten Art an, Speichern dessen Handleradresse in den eigenen Variablen, und fortan Analyse der fraglichen Kanäle anhand einer so gewonnenen Sammlung.
Soll aber z.B. die exakte Länge 160 als Kriterium für einen Directory-Device-Kanal herhalten, so ist das völlig verfehlt (in der Originaldokumentation heißt es "mindestens 160 Bytes..."). Geeignete Programmierung vorausgesetzt, ist die dadurch gewonnene Auskunft zudem ganz und gar überflüssig. Wird sie aber aus welchem Grunde auch immer dennoch benötigt, liefern die Handler-Listen des Systems die gesuchten Angaben, ohne daß irgendwelche Daten "gewußt" werden müßten, und das in zuverlässig systemunabhängiger Form.
Gerade die Unterscheidung zwischen Kanälen des einfachen und des Directory-Typs ist aufgrund der vielen im System verfügbaren Angaben außerordentlich einfach, sicher und von der aktuellen Konstellation unabhängig durchführbar.
Das geht hin bis zur vollständigen Bestimmung ganzer Kanalnamen, die gerade für Directory-Devices mit Hilfe allein der verschiedenen Systemvariablen möglich ist.
Mehr auch hierzu an entsprechender Stelle im Tabellenteil.
Es ist gelegentlich erforderlich, zu wissen, ob das Betriebssystem im RAM liegt, oder im ROM rsp. einem hardwaremäßig schreibgeschützten Adressenbereich. Das läßt sich unabhängig vom betr. Gerät und dem eingesetzten Processor mit folgender Subroutine in Erfahrung bringen:
Der Aufruf liefert (ohne Eingangsparameter):'eq' / D0= 0 : System im ROM,
'mi' / D0=-1 : System durch Bus-Error "geschützt"
'gt' / D0= 1 : System im RAM
trap #0 (supv) move.l sp,d2 leerer stack moveq #0,d0 prüf-flag "System im ROM" move.w #8,a2 bus-error vector move.l (a2),-(sp) lesen geht immer pea (a2) vector move.l sp,d1 fehler-stack merken pea berr(pc) vector und zur prüfung move.l (sp),(a2) springt ggf. in exception <berr> cmp.l (sp)+,(a2)+ seq d0 ? addq.b #1,d0 0 ROM, 1 RAM move.l d1,sp bus-error-trap restaurieren move.l (sp)+,a2 ROM oder nicht ist move.l (sp)+,(a2) hier nun unbedeutend rtusr move.l d2,sp ursprünglicher stackptr and.w #$dfff,sr (user) tst.l d0 flags setzen rts berr moveq #-1,d0 bra.s rtusr