zurück : weiter : inhalt =
 
 

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 Lage
timem = linktime - 8
die 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.
 

Unbezwingbare Hindernisse

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.
 

Filesystem

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.
 

I.P.C.

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.
 

Processortyp

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)+,SP

Wä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.

Kanaltyp

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.
 

RAM/ROM-Test

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

 
 
 
 


 
oben : zurück : weiter : inhalt 

(count)