| HOME | NEWS | AWARDS | ABOUT ME | TEXTE | REFERATE | PROJEKTE |
| MUSIK
| CHAT
| SPECIAL | LINKS |
Wer unter DOS schon einmal Dateien ausgedruckt hat, wird sicherlich schon die Meldung „Residenter Teil von PRINT geladen.“ gelesen haben. Was hat es damit auf sich?
Microsoft-DOS ist ein Single-Task System, d.h. es kann immer nur ein Programm aktiv sein. Da es jedoch manchmal nützlich ist, auf bestimmte Programme bzw. Tools ständig zuzugreifen zu können, wurden TSR-Programme in die Welt gesetzt. TSR steht für „Terminate and stay resident“, was soviel heißt wie: „Programm beenden und im Speicher behalten“. Ein solches Programm kann nun jederzeit wieder aus dem Speicher aufgerufen werden, z.B. über eine bestimmte Tastenkombination.
TSR-Programme gehören zu den nützlichsten, aber auch kompliziertesten Vertretern von Hilfsprogrammen, da hier ständig darauf geachtet werden muß, daß man nicht in Konflikt mit dem gerade aktuellen Programm kommt, oder während kritischer Situationen (Diskettenzugriff) auf DOS-Funktionen zugreift. Zudem ist immer im Auge zu behalten, ob das Tool nun eingreifen soll bzw. darf.
Man unterscheidet deshalb bei TSR-Programmen solche, die ständig im Hintergrund sind, wie z.B. DOS-TOOL und solche, die nur reagieren, nachdem ein sogenannter „Hotkey“ gedrückt wurde. Um ersteres zu erreichen werden oftmals Interrupts, wie der Timer verbogen, so das eine ständige Kontrolle des Systems möglich ist.
Allgemein ist bei der Programmierung von TSR´s äußerste Vorsicht geboten, da es auch leicht zu Speicherkonflikten kommen kann.
Wenn man sich ein Speicherresidentes Programm bildlich vorstellen möchte, kann man es als eine Schale um das eigentlich auszuführende Programm sehen, was jegliche Aktion des Hauptprogramms beobachtet und gegebenenfalls darauf reagiert.
Leider ist es Umfang dieser Projektarbeit nicht möglich, näher auf die Programmierung solcher Programme einzugehen. Dennoch sollte das Listing am Ende ein wenig Aufschluß über grundlegende Programmiertechniken geben, und wer noch mehr Informationen braucht, dem können wir das Buch „Effektives Programmieren mit Turbo Pascal“ aus dem Markt&Technik Verlag empfehlen.
Interrupts sind nicht nur zur Steuerung der Hardware unverzichtbar, sondern stellen das zentrale Medium zwischen Software und den Funktionen des BIOS und DOS dar. Inwiefern diese Aussage gerechtfertigt ist und wie das Interrupt-Konzept funktioniert, soll dieser Abschnitt unserer Projektarbeit näherbringen.
Wie der Name schon nahelegt, handelt es sich bei Interrupts um Unterbrechungen. Ausgelöst durch ein Programm oder ein Gerät wird dabei die CPU in ihrer Arbeit unterbrochen und gezwungen einen sog. Interrupt-Handler aufzurufen.
Ein simples Beispiel soll das Problem veranschaulichen: Man stelle sich vor, die CPU sei gerade mit der Ausführung eines Schreibprogramms beschäftigt. Währenddessen registriert die Netzwerk-Hardware angekommene Daten. Um Datenverluste mangels Speichermöglichkeiten seitens der Netzwerkkarte zu vermeiden, muß das Schreibprogramm unterbrochen werden. Dazu meldet die Karte über ihre Interrupt-Leitung eine Unterbrechung an, die den Prozessor zwingt die Arbeit zu unterbrechen um die angekommenen Daten dem Empfänger-Programm zuzustellen. Anschließend kann ursprüngliche Arbeit wieder aufgenommen werden.
Bei Interrupts unterscheidet man jene, die durch die Software ausgelöst werden, um eine Funktion des BIOS, DOS oder ähnlichem (Speicherverwaltung) auszuführen (Software- Interrupts), von denen, die durch externe Hardware hervorgerufen werden (Hardware-Interrupts).
Wenn die CPU von externer Hardware unterbrochen wird, wird ihr das über Interrupt-Leitungen signalisiert. Um welchen Interrupt-Request (IRQ) und damit um welches Gerät es sich handelt muß der CPU natürlich mitgeteilt werden. Diese Aufgabe fällt den Interrupt-Controller zu.
Ein vereinfachtes Schema könnte so aussehen:
Interruptanfrage Hardware-Komponente -----------------> Interrupt-Controller -----> CPU
Dem Interrupt-Controller kommt somit für die Steuerung externer Geräte
(z.B. Tastatur, Festplatte, Schnittstellen) eine immense Bedeutung zu. Vereinfacht
gesagt sorgt der Interrupt-Controller bei Hardware-Interrupt-Anfragen für
Ordnung, denn als Koppelglied zwischen Hardware und CPU sorgt er für Entlastung.
Ein Beispiel: Damit die Tastatur permanent auf Eingaben des Benutzers reagieren kann, müsste sie vom Prozessor auch permanent abgefragt werden, was viel CPU-Zeit in Anspruch nehmen und eine sofortige Reaktion unmöglich machen würde. Sinnvollerweise meldet sich in der Realität das Gerät, bei relevanten Ereignissen, über den Interrupt- Controller bei der CPU, die daraufhin einen sog. Interrupt-Handler aufruft.
Jede Hardware-Komponente besitzt eine eigene Interrupt-Leitung, über
die Interrupt- Anfragen an die CPU weitergegeben werden können. An den
Interrupt-Controller des PC`s können bis zu 8 Geräte angeschlossen,
da er 8 Interrupt-Leitungen besitzt (IRQ0 - IRQ7). Da alle Geräte gleichzeitig
Interrupts anmelden können, gibt es bei den Interrupt-Leitungen Prioritäten
(IRQ0 hat die höchste, IRQ7 die niedrigste), nach denen die Interrupt-Anfragen
weitergeleitet werden. Beim AT können durch 2 Interrupt-Controller bis
zu 16 Interrupt-Leitungen abgefragt werden.
Der Interrupt-Controller kommt nur bei Hardware-Interrupts zum Einsatz; bei
Software-Interrupts legt das jeweilige Programm die Interrupt-Nummer fest.
Interrupt-Leitung |
Hardware-Komponente |
IRQ 0 |
Timer |
IRQ 1 |
Tastatur |
IRQ 2 |
2. Interrupt-Controller |
IRQ 3 |
ser. Schnittstelle 2 |
IRQ 4 |
ser. Schnittstelle 1 |
IRQ 5 |
Festplatte |
IRQ 6 |
Diskette |
IRQ 7 |
Drucker |
Tab. 1: Die Besetzung der Interrupt-Leitungen
Wie schon mehrfach erwähnt wird von der CPU beim Auslösen eines Interrupts ein sog. Interrupt-Handler aufgerufen. Logisch erscheint, daß jeder Interrupt seinen eigenen Handler besitzt, denn jedem Interrupt kommt schließlich eine andere Aufgabe zu. Jeder Interrupt- Handler hat natürlich auch eine Adresse im Speicher, denn die CPU muß schließlich wissen, wo der Interrupt-Vektor geholt werden muß, um die Interrupt- Routine auszuführen, auf die der Vektor zeigt.
Für die 256 Interrupts, die der PC kennt, gibt es die sog. Interrupt-Vektor-Tabelle, die demzufolge 256 Einträge enthält. Da jeder Eintrag einen Zeiger darstellt, der Segment- und Offsetadresse des jeweiligen Interrupt-Handlers angibt, belegt er 2 aufeinanderfolgende Words. Folglich umfaßt die Interrupt-Vektor-Tabelle 1024 Byte.
Diese Tabelle stellt das Herzstück des Betriebssystems da, da jeder Interrupt-Aufruf über diese Tabelle erfolgen muß. Beim Systemstart wird sie vom BIOS an der untersten Speicheradresse ($0000:$0000) angelegt.
Unser Schema aus 1.2.2 läßt sich nun konkretisieren:
Interruptanfrage IVT Hardware------------> Interrupt-Controller --> CPU -----> Interrupt Handler Interruptanfrage | Software --------------------------------------------
Mit dem gegebenen Einblick in das Interrupt-Konzept des PC`s erklären wir im folgenden die Funktion von Interrupts, d.h. insbesondere ihrer Handler, bei der Erstellung von TSR-Programmen. Wie anfangs erwähnt, stellen Interrupt u.a. das zentrale Medium zwischen Software und den Funktionen des BIOS und DOS dar. Das heißt jedes Programm und dessen Funktionsweise beruht auf dem Auslösen von Interrupts um Funktionen des BIOS und DOS zu nutzen.
Auch TSR-Programme nutzen das Auslösen von Interrupts; mit einen entscheidenden Unterschied. Da es im Hintergrund arbeitet und dem Benutzer Aufgaben abnimmt, ohne das es notwendig ist, daß gerade ausgeführte Programm zu beenden, muß es je nach Funktion natürlich Mechanismen geben, auf die das TSR-Programm mit seinen Aufgaben reagiert. Das sind vom System ausgelöste Interrupts, die vom TSR-Programm abgefangen werden, und dessen Interrupt-Handler durch einen eigenen Handler (Interrupt-Service-Routine) ersetzt bzw. erweitert werden können. Dabei enthält ein TSR-Programm, je nach Komplexität, eine, in den meisten Fällen aber mehrere Interrupt-Service-Rountinen für die verschiedenen Interrupts.
Die Interrupt-Service-Rountine ist eine normale Prozedur, die neben anderen
Prozeduren in einen Programm untergebracht ist, jedoch selbstständig arbeitet
und deshalb nie als Prozedur aufgerufen wird. Sie reagiert nur auf das Auslösen
eines best. Interrupts (Mechanismus) und wird dann vom System anstelle des Original-Interrupt-Handlers
aufgerufen und ausgeführt. Damit das passiert, sind jedoch einige Schritte
notwendig, die wir anhand unseres Programms DOS-TOOL erläutern wollen:
Um die Aufgabe, die das Programm erfüllen soll zu realisieren, ist es notwendig
auf 3 best. Interrupts zu reagieren. Das sind der Timer-Interrupt $08, der vom
System 18,2 mal in der Sekunde ausgelöst wird, der Tastatur-Interrupt $09,
der von der Tastatur mit jedem Tastendruck ausgelöst wird und der Interrupt
$28, den DOS beim Warten auf Zeicheneingaben zyklisch aufruft. Auf die Funktionen
der Interrupts und die Funktionsweise unserer Interrupt-Service- Rountinen wird
im Kapitel 2 näher eingegangen.
Um zu gewährleisten das aber gerade unsere Interrupt-Service-Routinen,
und somit unsere gewünschten Funktionen, beim Auslösen dieses Interrupts
ausgeführt werden, ist es notwendig die Interrupts auf unsere ISR zu verbiegen.
Dazu ist es wichtig den Zeiger in der Interrupt-Vektor-Tabelle, der auf den
best. alten Interrupt-Handler zeigt, auf unsere neue Interrupt- Service-Rountine
für einen best. Interrupt zeigen zu lassen. Der Interrupt wird also beim
nächsten Aufruf gezwungen, unsere ISR auszuführen.
Auf unser Programm zurückgeführt bedeutet das, das wir die Zeiger
für die Interrupts $08, $09 und $28 einfach auf die Adressen unserer eigenen
Routinen zeigen lassen. Vorher muß man sich den Zeiger auf den Original-Handler
jedoch merken. Auch zweierlei Gründen: In der eigenen Interrupt-Service-Rountine
muß der alte Interrupt-Handler aufgerufen werden, denn die eigenen Interrupt-Service-Routinen
können nicht die Aufgaben, die die Original-Handler wahrnehmen übernehmen
(es sei denn man will einen Treiber schreiben). Außerdem muß gewährleistet
werden, das unser TSR-Programm auch dann reagiert, wenn es mit anderen TSR-Programmen
„zusammenspielt“, Das Problem besteht hier darin, das ein anderes TSR-Programm
denselben Interrupt schon auf seine eigene ISR umgebogen haben kann, und unser
Programm beim Verzicht auf den Aufruf des alten Handlers die Funktion des anderen
TSR-Programms stark beeinträchtigt (oft unbrauchbar macht).
Das Sichern des Origninal-Interrupt-Vektors (und somit der Adresse seines Handlers)
erfolgt über die Pascal-Funktion GETINTVEC, das Umbiegen eines Interrupts-Vektors
auf den eigenen Handler (die ISR-Prozedur) läßt sich über SETINTVEC
realisieren.
Bsp.: GETINTVEC($08,intvec8){sichert die Adresse des Timer-Interrupt Handlers im pointer „intvec8“}
SETINTVEC($08,@int8){setzt den Timer-Interrupt-Vektor auf die eigene ISR namens „int8“}
Auch in diesem Abschnitt möchten wir lediglich auf eine kurze, für DOS-TOOL relevante Erklärung zurückgreifen.
Programme haben im Normalfall meist globale Variablen und Konstanten. Damit
diese Platz im Speicher finden, reserviert man ihnen Speicherplatz - maximal
64kByte. Die hier im Datensegment gespeicherten Variablen sind in ihrer Größe
fest definiert und können während der Laufzeit nicht mehr diesbezüglich
verändert werden.
Biegt man zum Beispiel einen Interrupt unter Turbo-Pascal auf eine eigene Routine
um, so wird mit Aufruf der Routine auch das programmeigene Datensegment aktiviert.
Im Gegensatz zum Datensegment steht der Stack. Er übernimmt die Funktionen
des Datensegmentes für die lokalen Variablen, die nur zeitweise, sprich
temporär, gebraucht werden. Zudem speichert er beim Aufruf einer Prozedur
oder Funktion immer die Rücksprungadresse zum Hauptprogramm bzw. der aufrufenden
Prozedur. Ebenso wie das Datensegment ist der Stack auf 64kByte begrenzt. Seine
Adresse läßt sich mitttels der Turbo-Variablen „sseg“ und „sptr“
abfragen.
Dies ist für TSR-Programme sehr hilfreich, diese Werte zu kennen, da beim
Aufrufen zwar das Datensegment, nicht aber der Stack automatisch umgeschalten
wird. Dies muß manuell und in Assembler geschehen. Ohne das Umschalten
kommt es bei mehr Variablen leicht zum Programmabsturz.
Der Heapbereich kommt bei DOS-TOOL nicht zum Einsatz und wird deshalb nicht noch einmal gesondert hier besprochen.
Vielleicht haben Sie es selber schon einmal ausprobiert: Während des
Abarbeitens der Konfigurationsdateien haben Sie ein paar Tasten gedrückt
und haben genau diese Zeichenfolge, nachdem das Prompt erschienen ist, auf ihrem
Bildschirm.
Um sich solche, während Aktivitäten von DOS, getätigten Eingaben
zu merken hat DOS einen kleinen Tastaturpuffer eingerichtet, der diese Daten
zwischenspeichert, bis der Computer zu deren Ausgabe bereit ist. Dieser Puffer
ist jedoch auf 16 Zeichen begrenzt, was seine Funktionsmöglichkeiten bei
der Programmierung stark einschränkt. Die Startadresse des Tastaturpuffers
ist in der Adresse $0040:$0080 abgelegt. Anfang und Ende der Zeichenkette im
Puffer sind in den Adressen $0040:$001A und $0040:$001C abgelegt. Die Zeichenkette
beginnt im Speicher bei $0040:Pufferstartadresse.
In DOS-TOOL wurde diese Programmiertechnik angewandt, um Befehle in der DOS-Ebene auszuführen.
Da das BIOS in der Programmierung von DOS-TOOL nur am Rand von Bedeutung war, soll hier nur kurz und knapp darauf eingegangen werden und bei Interesse auf die zahlreiche Fachliteratur verwiesen werden.
Das BIOS (hier im speziellen das VGA-BIOS) erledigt grundlegende Ein- und Ausgabefunktionen des Computers. Das VGA-BIOS wird über den Interrupt 10h angesprochen. Nachteil von BIOS-Aufrufen ist nicht so hohe Geschwindigkeit. Der große Vorteil liegt jedoch in der immensen Kompatiblität zu allen Grafikkarten. Im Programm DOS-TOOL wurde lediglich die Cursorposition über das BIOS abgefragt und gesetzt.
Weiterführende Informationen zum VGA-BIOS sind z.B. im Buch „VGA und Super- VGA-Programmierung“, aus dem Addison-Wesley Verlag, zu finden.
In diesem Abschnitt soll kurz auf den Bildspeicher im Textmodus eingegangen werden. Der Bildspeicher im Textmodus umfaßt ein 64kByte großes Datensegment, wobei jeweils 32kByte für Monochromkarten und 32 für VGA/CGA-Karten sind. DOS-TOOL funktioniert nur mit letzterem Kartentyp!
Die Startadresse für den VGA-Textmodus-Bildspeicher liegt bei $B800:$0000. Die Zeichen werden nun jeweils immer mit ihrem Attribut fortlaufend im Speicher abgelegt. Dabei ist es wichtig zu wissen, daß die Zeichencodes an den geraden Adressen und die Attribute an den ungeraden Adressen liegen. Theoretisch verfügt man im Textmodus durch die vorhandenen 32kByte über 8 Bildseiten. Die Adresse eines Zeichens ergibt sich im 80x25 Zeichenmodus wiefolgt:
$B800:Zeile*160+Zeichen*2
Das entsprechende Attribut befindet sich ein Byte weiter.
Der Zugriff auf den Bildspeicher in Turbo-Pascal kann man über „mem“ realisieren.
MEM[adresse]:=wert bzw. wert:=MEM[adresse].
| HOME | NEWS | AWARDS | ABOUT ME | TEXTE | REFERATE | PROJEKTE |
| MUSIK
| CHAT
| SPECIAL | LINKS |