Archiv verlassen und diese Seite im Standarddesign anzeigen : Interruptbearbeitung (Reaktionstest)
Hallo Leute...
Ich muss in der FH eine Praktikumsaufgabe machen , blos ich hab gar kein plan wie :( könnt ihr mir vielleicht paar tips geben... hier die aufgabenstellung.
Es ist eine Interruptroutine zu realisieren, die auf den Tastaturinterrupt des PC's
reagiert (Interrupt Nr.9). Außerdem soll eine Interruptroutine zur Bearbeitung des
Timer-Interrupts (Interrupt Nr.8) vorgesehen werden.
Die Timer-Interruptroutine wird verwendet, um laufend eine Byte-Variable
hochzuzählen, die als „Zufallswert“ für den Rest der Aufgabe fungiert. Das
Hauptprogramm soll zunächst einen Begrüßungstext ausgeben – etwa in der Form:
Willkommen zum Reaktionstest der Gruppe Meier-Müller !
Bitte beobachten Sie die nächste Zeile. Wenn dort das Zeichen „X“ erscheint,
dann drücken Sie bitte so schnell wie möglich eine beliebige Taste.
Danach wartet das Hauptprogramm über eine Zählschleife für etwa 2 Sekunden.
Danach gibt es in schneller Folge eine zufällige Anzahl von „O“-Zeichen aus (siehe
obige „Zufallsvariable“). Danach wechselt es das auszugebende Zeichen zu „X“. Die
Ausgabe wird sofort gestoppt, wenn die Tastatur betätigt wird. Es entsteht also stets
ein Muster der Art:
OOOOOOOOOOXXXXXXXXX
Das „Messergebnis“ (Anzahl der „X“-Zeichen) ist auszugeben. Falls die Tastatur VOR
dem Erscheinen der „X“-Zeichen betätigt wird, so ist eine Beschimpfung des
Probanden vorzunehmen.
Da der Tastaturinterrupt beim Drücken UND Loslassen einer Taste ausgelöst wird,
sollte er 2x ausgewertet werden. Danach sind die beiden Interrupt-Vektoren wieder auf
ihren Originalwert zurückzusetzen.
butterkeks
14.06.2005, 20:45
wo liegt das Problem genau? Am Umbiegen des Interrupts oder am Programm an sich?
Klasse Aufgabe, sollte man vielleicht in Assembler lösen, näher kommt man den Interupts nicht, und die marginale IO bekommt man auch in Assembler noch geregelt ...
---------- edit ----------
Ups, wie peinlich is ja das Assembler Forum :rolleyes: :rolleyes:
Sach mal wo es klemmt dann kann sicher jemand helfen.
Klasse Aufgabe, sollte man vielleicht in Assembler lösen, näher kommt man den Interupts nicht, und die marginale IO bekommt man auch in Assembler noch geregelt ...
---------- edit ----------
Ups, wie peinlich is ja das Assembler Forum :rolleyes: :rolleyes:
Sach mal wo es klemmt dann kann sicher jemand helfen.
Sorry aber ich bin ein absoluter anfänger in Assembler.. und die Vorlesung bei dem Prof hilf nicht viel und in seinen Scripten steht auch überhaupt wie ich mit den Interupts umgehen soll.. also sagen wir mal so ich hab gar kein plan womit ich hier anfangen soll :rolleyes::(:rolleyes:
Sorry aber ich bin ein absoluter anfänger in Assembler.. und die Vorlesung bei dem Prof hilf nicht viel und in seinen Scripten steht auch überhaupt wie ich mit den Interupts umgehen soll.. also sagen wir mal so ich hab gar kein plan womit ich hier anfangen soll :rolleyes::(:rolleyes:
Hmm, soviel ich noch von dem gabzen Kram weiß löst man per INT einen Interupt aus z.B. INT21, dieser dient zum Aufruf der DOS-Funktionen und über diese kann man so ziemlich alle IO Probleme lösen. Ansatz kann es sein sich ein Assemblerbuch zu besorgen und sich mal die DOS-Funktionen des INT21 rein zu ziehen, da müßte was passendes dabei sein.
BTW Über die Bedeutung der Register und des Akkumulators bist Du dir doch im klaren oder bist Du blutiger Anfänger???
Grüße
Cisab
BTW Über die Bedeutung der Register und des Akkumulators bist Du dir doch im klaren oder bist Du blutiger Anfänger???
Grüße
Cisab
Ein buch hatte ich mir auch schon besorgt aber anscheinend das falsche, da nx wichtiges drin stnad, also über die Register bin ich schon im klaren nicht 100% aber geht schon.. :)
Soll das unter Windoof oder Dooos laufen.??
Soll das unter Windoof oder Dooos laufen.??
DOOOOOOOOS
Der Int 21h von DOS hat unterfunktionen mit denen man eigene Int einhängen kann.
Ich muss die Liste erst suchen
@Andi_s:
Ich antwort einfach mal.
Erstens: Besorg Dir unbedingt mal "Ralph Brown's Interrupt
List", wenn Dich das Thema (DOS und Assembler) interessieren
sollte. Und selbst für Coden unter Windows findet man hier
so manchen nützlichen Interrupt.
Zweitens:
Zur Lösung Deines Problems.
Bei DOS-Ints (Hauptsächlich wird INT 21h benutzt) werden
sogenannte Funktionsnummern im Register AH übergeben
(Höheres Byte von AX).
Die gesuchten Funktionsnummern sind 02h, 25h und 35h.
(Bitte das h (hexadezimal) nicht vergessen - häufiger
Anfängerfehler!)
1.) Ausgabe eines Zeichens.
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ein Zeichen wird ausgegeben mit Funktion Nr. 02h. Das
Zeichen muß dazu nach DL geladen werden. AL enthält nach
Ausführung das Zeichen (außer bei 9, da enthälts 32, weil
es "Tabulator" durch n Haufen Leerzeichen simuliert.
Ich sage das nur dazu, damit Du weißt, daß der Inhalt des
Registers AL (also niederes Byte von AX) von dem Int
"zerstört" wird.
Was ist also zu tun?
mov AH,02h ;Funktionscode 02h
mov DL,79 ;ASCII-Code von "O"
int 21h ;INT aufrufen
Der ASCII-Code für "X" ist 88.
Anmerkung: MAnche Assembler supporten auch Dinge wie
mov DL,"X"
so daß man also direkt den ASCII-Code übergeben kann.
Ergebnis bleibt aber dasselbe.
2. Interrupt-Vektoren setzen und holen.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Find's natürlich schon etwas komisch, daß man nen
Assembler-Neuling gleich mit Interrupt-Bearbeitung erschlägt -
aber ist ja nun nicht zu ändern.
So schwer isses auch nicht, man muß nur 'n paar Dinge
beachten.
Ein Interrupt ist ein Stück Programm, das das laufende
Programm einfach so unterbrechen kann. Alle
Registerinhalte, sowie auch die Stelle, wo sich der
Computer gerade bei der Programmausführung befindet (sind
ja auch bloß Register) wird "gerettet" (in den Stack -
auch Stapelspeicher genannt) und der Computer (also die
CPU) geht an die Stelle, wohin der Interrupt-Vektor zeigt.
Vektor? Ja, schönes Wort für ne eigentlich einfache Sache.
Ein Interrupt-Vektor ist nichts anderes als einfach eine
Speicherstelle. Und zwar genau die, wo das Stück Programm
steht, was durch diesen Interrupt ausgeführt werden soll.
D.h. diese Zahl "zeigt" auf die Stelle. Deswegen nennt man
sowas auch Zeiger. Oder eben Vektor.
Wenn das kleine Programm-Stück ausgeführt wurde, geht es
wieder zurück zum Hauptprogramm und macht dort weiter, als
wenn nichts gewesen wäre. Damit das funktioniert, darf es
nicht mit RET (also normalem Return-Befehl) enden, sondern
mit IRET (also "Interrupt Return"). Dieser Befehl sorgt
dafür, daß das Programm alle Register-Inhalte wieder vom
Stack herunterholt und genauso auch die Stelle, wo es im
Hauptprogramm gewesen ist. Dahin geht der Computer (also
die CPU) wieder zurück und das Programm wird ganz normal
weiter ausgeführt.
Achja: Das Sichern der restlichen Register (außer Adresse
und Flags) muß man selbst machen.
Wenn man einen Interrupt setzen will, den man nachher
wieder auf "normal" setzen will, muß man sich den Vektor,
auf den er vorher gezeigt hat, irgendwo "merken" - am
besten in irgendeiner Speicherstelle, die man definiert.
In Registern ist es zu unsicher, denn diese können sich ja
jederzeit ändern. Merken muß man sich den Vektor deswegen,
weil man ja nachher wieder den Interrupt auf den alten
Wert zurücksetzen will. Und noch aus einem anderen Grund -
aber dazu komme ich gleich.
Wo stehen eigentlich diese Vektoren?
Im Real-Mode (also dem normalen "16-Bit" Modus, in dem
sich DOS ja befindet) liegen die Interrupt-Vektoren
einfach ganz vorn im Speicher. Jeder Vektor ist dabei 4
Bytes groß. Weil es 256 verschiedene Interrupts (0 bis 255,
bzw 00h bis 0FFh) gibt, ist diese Tabelle also 1024 Bytes
groß. Die Position eines Interrupt-Vektors ist also
Interrupt-Nummer * 4:
(Weil im 16-Bit-Mode der Speicher in Segmente aufgeteilt.
schreibe ich das mal so. Das vordere ist das Segment, in
dem Fall also immer 0000h)
0000h:0000h Interrupt-Vektor 0
0000h:0004h Interrupt-Vektor 1
0000h:0008h Interrupt-Vektor 2
0000h:000Ch Interrupt-Vektor 3
::::::::::::::::::::::::::::::
0000h:03FCh Interrupt-Vektor 255
Das ist nicht so wichtig - aber vielleicht mal ganz
interessant. Man KÖNNTE nämlich den Interrupt-Vektor
einfach ändern, indem man ihn dort ausliest und den neuen
da reinschreibt. Vorher muß man mit CLI alle Interrupts
kurz sperren und danach mit STI alle Interrupts wieder
erlauben. Damit nicht, während man gerade einen Wert halb
ändert, genau dieser Interrupt auftritt...
Aber EGAL. Merk' Dir das am besten nicht - denn die
SICHERE Methode ist die, es über DOS-Interrupt 21h zu machen.
Das ist nämlich die standardisierte Methode - und so wird es,
wenn man Glück hat, auch evtl. unter anderen Betriebssystemen
noch laufen.
Interrupt-Vektor holen funktioniert so:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(für Interrupt 8 zum Beispiel)
mov AH,35h ;Funktionscode 35h
mov AL,08h ;Wir wollen Interrupt-Vektor Nr. 8
int 21h ;Und INT aufrufen
Danach steht die Adresse (also der Vektor) in ES (Segment)
und BX (Offset) und man kann sie irgendwohin speichern.
Achja: mov AH,35h und mov AL,08h kann man natürlich
zusammenfassen zu mov AX,3508h - aber ich geh mal davon
aus, daß Du das weißt. Diente jetzt nur der
Übersichtlichkeit.
SETZEN kann man einen Vektor, indem man die gewünschte
Adresse, getrennt in Segment und Offset, nach DS:DX lädt
und Funktionscode 25h statt 35h benutzt (in AH, wie immer).
Nach AL wird wieder die INT-Nummer geladen, für den der
Vektor gesetzt werden soll.
Woher kriegt man nun aber die gewünschte Adresse? Naja.
Die Adresse ist eben da, wo das Unterprogramm steht, das
beim Interrupt aufgerufen werden soll.
Ich demonstrier mal am besten kurz, was man machen muß:
@Timerinterrupt: ;Label, um die Adresse zu kriegen
::::::
Hier steht dann das Programm, was bei Interrupt ausgeführt
werden soll.
::::::
; Hier wird der Interrupt-Vektor gesetzt.
Da hier DS benutzt werden muß, man aber DS oft nicht
"verlieren" will (weil's ja schließlich auf das Standard-
Datensegment zeigt), empfiehlt sich eine "Umrahmung" des
ganzen mit push DS / pop DS
push DS ; DS retten
mov AH,25h ; Funktionscode 25h
mov AL,08h ; Interrupt-Nr 8 zu ändern
lds DX,dword ptr[@Timerinterrupt] ;Adresse nach DS:DX laden
int 21h ; Und wieder INT aufrufen
pop DS ; DS zurückholen
Das Interrupt-Programm
~~~~~~~~~~~~~~~~~~~~~~
Jetzt haben wir die Aufrufe, fehlt noch die Bearbeitung.
Es gibt zwei Möglichkeiten: Entweder wir "hängen uns rein"
oder wir "ersetzen den Interrupt".
Mit "Reinhängen" bezeichnen Coder es, wenn das, was der
Int vorher gemacht hat, weiterhin ausgeführt wird, nur daß
zusätzlich das ausgeführt wird, was man selber will.
Wenn wir "ersetzen", dann führen wir NUR unser Programm
aus und NICHT mehr das, was der Interrupt vorher gemacht
hat.
Der Unterschied ist eigentlich nur die Art, wie man das
Unterprogramm beendet, was das macht, was im Interrupt
gemacht werden soll.
Reinhängen geht so:
~~~~~~~~~~~~~~~~~~~
Man führt die ganze Interrupt-Routine aus, und danach macht
man einen far jump (also einen Sprung zu einer absoluten
Adresse) zu dem alten Interrupt-Vektor, den man vorher
gerettet hat. (Der sollte natürlich dann gültig sein - aber
bei Keyboard- und Timer-Interrupt ist er das auch.)
Also zu der Adresse, die man vorher gerettet hat.
Gibt jetzt mehrere Möglichkeiten, wie man sowas macht.
Man könnte z.B. die Adresse gleich beim "Retten" hinter den
jmp far xxxx:xxxx Befehl schreiben beim Retten - also
selbstmodifizierenden Code fabrizieren. Dies gilt aber bei
manchen Leuten als unfein.
Man könnte auch die gerettete Adresse nehmen, auf den Stack
werfen (zuerst das Segment, dann den Offset der Adresse!)
und anschließend ein RETF (also ein far-Return) veranstalten -
macht im Prinzip auch nix anderes...
Oder man springt indirekt zu der Stelle, auf die ne andere
Stelle zeigt (nämlich die, wo man die Adresse hingelegt hat).
Jedenfalls, man beendet nicht mit IRET, sondern man springt
dahin, wo der Int normalerweise sowieso gelandet wäre.
Das bedeutet: Zuerst wird das eigene Programm, dann das
eigentliche Interrupt-Programm ausgeführt.
Ersetzen geht so:
~~~~~~~~~~~~~~~~~
Am Ende geht man einfach mit IRET raus.
D.h. man setzt an das Ende der Interrupt-Behandlungsroutine
einfach IRET.
Wenn es allerdings ein durch IRQ ausgelöster Interrupt ist,
(und das sind Timer- und Tastatur-Interrupt zweifellos), muß
man auf Port 20h noch 20h ausgeben, bevor man IRET macht.
mov AL,20h ; Code 20h = Ints wieder erlauben
out 20h,AL ; An Port 20h (PIC) ausgeben
iret ; Interrupt-Programm Ende
Dies dient dazu, um dem PIC (Programmable Interrupt Controller)
zu sagen, daß der Interrupt "erkannt" und ausgeführt wurde
und er nun wieder neue Interrupts erzeugen darf. Das macht
er sonst nämlich so lange nicht, bis man ihn mit eben
obigem Befehl wieder "resettet".
Phuh...
Achja, noch was: Beim Reinhängen muß am Anfang der Routine
pusha stehen und kurz vor dem Sprung popa.
Die zwei Befehle retten, bzw. holen alle Register vom
Stack. Das ist wichtig - weil man die ja im Interrupt
wahrscheinlich ändert.
Beim Ersetzen muß popa dann direkt vor dem IRET stehen.
(weil mov AL,20h ja nochmal AL "versaut"). pusha bleibt
am Anfang.
Reinhängen und Ersetzen haben beide Vor- und Nachteile.
Ich beziehe mich jetzt mal auf die Aufgabenstellung:
Beim Timer könnte man reinhängen, bei Tastatur ersetzen.
Grund: Beim Timer will man ja, daß trotzdem der normale
DOS-Timer weiterläuft, der die Uhr/Kalender am laufen hält.
Beim Keyboard-Interrupt wird ja bei jedem Tastendruck
ein Zeichen im Keyboard-Buffer erzeugt. Wenn man sich
nur "reinhängt", wird es trotzdem erzeugt, liegt also
nutzlos im Tastaturpuffer rum. Wenn der voll wird, piepst
der Rechner entnervend (Standard ist dort Platz für 15
Zeichen).
D.h. in dem Fall müßte man den Puffer von Hand löschen -
durch Auslesen des Zeichens (z.B. mit INT 16h oder INT 21h)
oder durch Setzen von Puffer-"Kopf" und -"Schwanz" auf
denselben Wert (ist ein Ringpuffer).
Feststellen, ob irgendeine Taste gedrückt wurde, kann
man im Tastatur-Interrupt, indem man Port 60h ausliest.
Steht dort ein Wert mit gelöschtem oberen Bit, (also <128)
wurde eine Taste gedrückt, ist das Bit gesetzt, wurde sie
losgelassen. Es gibt dann noch so andere Funktionscodes,
wie PAUSE und die "Cursorblock" Tasten ("graue Tasten"
genannt) abgefragt werden usw - aber das führt jetzt zu
weit - ich erklär das gern auch nochmal, bei Interesse.
D.h. die Tastatur-Routine muß nur Port 60 auslesen und
prüfen, ob das oberste Bit gesetzt ist. Achja: Meines
Wissens ist 0 ebenfalls keine Taste - wird aber wohl imo
auch nie erzeugt.
Tastatur-Routine könnt also so aussehen:
pusha ;alle Register retten
in AL,60h ;Port 60 lesen
and AL,AL ;AND mit sich selbst - setzt "Sign" Flag, wenn >=128
js @Weiter ;Taste losgelassen - also nichts tun
:::::::::::::
Hier das tun, was immer man tun will, wenn einer ne Taste drückt.
:::::::::::::
@Weiter: ;hierhin wird gesprungen, wenn nur losgelassen
mov AL,20h ;Code 20h für "reset PIC"
out 20h,AL ;in den PIC (Port 20h) schreiben
popa ;alle Register wiederholen
iret ;und Int beenden
Beim Timer sollte man beachten:
Der Timer wird standardmäßig nur ca. 18,2 mal pro Sekunde
aufgerufen. Dies muß man bei der Berechnung einer zu
timenden Zeitspanne beachten. Der genaue Wert für die
Aufruf-Frequenz ist:
1193180 / 65536 = 18.20648193359375 Hz
Man kann die Ticker-Frequenz ändern - dafür gibts aber nicht
auf allen Betriebssystemen Funktionsgarantie. Windows NT
und Windows 2000 mögen das irgendwie nicht richtig und man
muß ein wenig tricksen...
Das sollte man nur wissen, wenn man damit die Zeit messen will.
(Anmerkung: Die Zeit wird in der 4-Byte-Speicherstelle
0040h:006Ch hochgezählt und um Mitternacht genullt -
bei 1573039 -> 0)
Und man sollte es noch aus nem anderen Grund wissen: Das,
was man innerhalb des Timer-Interrupts macht, sollte dann
natürlich nicht länger als ca. 1/18tel Sekunde dauern -
klar. (Aber wenn man in Assembler codet, braucht man sich
darum wohl keine Gedanken zu machen, weil ne 18tel Sekunde
da ne tierisch lange Zeitspanne ist...)
Wie setzt man den Interrupt wieder zurück?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Eigentlich genauso, wie man ihn setzt. Nur daß man jetzt
als Adresse in DS:BX die Adresse benutzen muß, die man sich
vorher "gemerkt" hat - also die Adresse, auf die der
Vektor vorher gezeigt hat.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Entschuldigung, daß ich soviel geschrieben hab.
Ich weiß, ich hab das wieder teilweise für Doofe erklärt.
Aber Du hast ja gesagt, bist Neuling - und da wußte ich halt
nicht, was ich voraussetzen kann und was nicht und dachte,
bevor ich hier n Haufen Kram erzähl, mit dem Du nix anfangen
kannst...
Interrupt-Programmierung ist auch nicht gerade das
einfachste Feld, was Du Dir ausgesucht hast, um Assembler-
Programmierung zu lernen.
Falls es Unklarheiten gibt, frag ruhig - ich erklär gern
auch einzelne Sachen ausführlicher. Wenn garnix anderes
hilft, bau ich Dir zur Not auch n kleines Beispielprogramm,
was das erforderliche macht. (Nur zeigt die Erfahrung,
daß man mehr lernt, wenn man's selber macht - auch auf die
Gefahr hin, daß man erstmal auf die Nase fällt. Grad
Assembler programmiert man ja "ohne Netz und doppelten
Boden" - oder anders ausgedrückt: Wenn man Mist gemacht
hat (und kein Tippfehler oder so), stürzt beim Ausprobieren
in der Regel der Task oder der Rechner ab (je nachdem).
Daher beim Quelltext tippen und Ausprobieren immer an die
drei wichtigsten Regeln des Programmierers denken:
Sichern, Sichern, Sichern.
(Im Klartext: Erst speichern - dann testen.)
Danke... für die antwort hab zwar noch nicht ganz durchgelesen.. aber.. hab heute leider keine zeit mehr.. aber morgen .. werde ich alles durchlesen und versuchen das zu coden... den deine beschreibung schein richtig gut zu sein vieeeeeeeeeeeelen dank :rolleyes::rolleyes::rolleyes::rolleyes:
@Xpyder (http://member.php?u=4010)
Vielen Dank, mit deiner Hilfe.. und bischen hilfe vom Prof ist das Programm sogut wie fertig, sind nur noch kleinigkeiten die zu machen sind, aber jetzt hab ich assemblerm mehr verstanden als in einem Semester :)))
Wenn ich noch fragen hab melde ich mich :)
vBulletin® v3.8.6, Copyright ©2000-2012, Jelsoft Enterprises Ltd.