Artikel pedia
| Home | Kontakt | Artikel einreichen | Oberseite 50 artikel | Oberseite 50 autors
 
 


Artikel kategorien
Letztes fugte hinzu
    Ms-access

   Instrumentation + schnittstellen

   Pc tuning - volle kraft voraus für ihr system

   Informatorische grundlagen

   Javascript

   Interne sortieralgorithmen - der kern der sache

   Plotter und sonstige drucker

   Frage 20 (rössl priska)

   Internet - programmierung

   Monitore

   Semesterarbeit und spezialgebiet für informatik

   Erörterungs zum thema

   Inhaltsverzeichnis

   Einführung in die entwicklung ganzheitlicher informationssysteme:

   Titel dokument
alle kategorien

  I

              Fachbereichsarbeit aus Informatik             Thema: Die Bedeutung des Protected Mode und seine Funktionsweise, sowie die Programmierung einer Bibliothek zur Nutzung desselben in C++           Betreuer: Mag. Norbert Mühlegger   Von: Peter Feigl          Inhaltsverzeichnis INHALTSVERZEICHNIS 1.1–2 I. VORWORT 1.1–3 II. DER REAL MODE 1.

1–4 1. Einführung in die Intel Architecture 1.1–4 1.1. 8086/8088 1.1–4 1.

2. 80286 1.2–4 1.3. i386 1.3–5 1.

4. i486 1.4–5 1.5. Pentium 1.5–5 2.

Der allgemeine Aufbau des i386 1.5–6 2.1. Der Real Mode 2.1–6 2.2.

Logische Speicheradressierung und logischer Speicherzugriff des i386 2.2–13 2.3. Adressierungsarten 2.3–15 2.4.

Interrupts und Exceptions im Real Mode 2.4–16 III. DER PROTECTED MODE 2.4–20 3. Der Protected Mode des i386 2.4–20 3.

1. Segmentselektoren, Segmentdeskriptoren und Privilegierungsstufen 3.1–20 3.2. Globale und lokale Deskriptortabelle 3.2–23 3.

3. Umschalten in den Protected Mode 3.3–24 3.4. Speicheradressierung im Protected Mode 3.4–24 3.

5. Segment- und Zugriffstypen 3.5–25 3.6. Die Interrupt-Deskriptortabelle 3.6–30 3.

7. Multitasking, TSS und das Task-Gate 3.7–31 3.8. Schutz des I/O-Adreßraumes 3.8–33 3.

9. Exceptions im Protected Mode 3.9–35 4. Zusammenfassung der Schutzmechanismen des Protected Mode 3.9–37 IV. ZUSAMMENFASSUNG 3.

9–38 5. Glossar 3.9–39 5.1. Allgemein 5.1–39 5.

2. Programmiertechnisch 5.2–40 V. ANHANG 5.2–41 6. Quellenverzeichnis 5.

2–41 7. Arbeitsprotokol 5.2–41 8. pmlib.cpp 5.2–41 VI.

INDEX 5.2–46 9. Endnoten 5.2–48   Vorwort       Schon seit einigen Jahren habe ich mich zunehmend mehr mit der systemnahen Programmierung und den Prozessoren der Intel Architecture beschäftigt. Da diese relativ einfach zugänglich und weit verbreitet sind, dienen sie als guter Einstiegspunkt in die hardwarenahe Programmierung. Mit dieser Arbeit verfolge ich das Ziel, die Erfahrungen, die ich gemacht habe, sowie die Erkenntnis, die man beim Arbeiten an einem Projekt wie diesem erhält, zusammenzuschreiben und anderen zugänglich zu machen.

Die Komplexität der Materie bedingt, daß einige Vorkenntnisse nötig sind, um alles zu verstehen. So ist die Kenntnis der Programmiersprache C++ unerläßlich, um das Programm zu verstehen, Vertrautheit mit Assembler ist nicht nötig, aber ganz praktisch. Ich werde versuchen, alles so einfach wie möglich darzustellen. Die Abbildungen sind hoffentlich eine Hilfe hierbei. Für die Programme verwende ich die Standard-ANSI-C++ Syntax.   Das Ziel des theoretischen Teiles der Arbeit ist es vorwiegend, die theoretische Kenntnis der Mechanismen des Protected Mode zu vermitteln, was zuerst eine eingehende Beschäftigung mit dem Real Mode bedingt.

Mit der Kenntnis der in diesem Teil vermittelten Methoden ist es möglich, diese aktiv für eigene Programme zu nutzen. Was jedoch einfacher ist, als alles selbst auszuprogrammieren, ist wohl die Verwendung der im praktischen Teil vorgestellten, von mir geschriebenen Funktionen. Diese Erleichtern die Nutzung der Protected-Mode-typischen Features. Ich stelle mir jedoch nicht die Aufgabe, ein neues Betriebssystem zu programmieren. Das wäre auch nicht im Rahmen einer Fachbereichsarbeit möglich, mein Ziel ist lediglich eine Funktionsbibliothek, die man eventuell später im Rahmen einer Universitätsarbeit erweitern könnte. Ich setze einige Kenntnis im Fachgebiet Computer voraus, da eine Diskussion einer derartig schwierigen Thematik ohne diese fruchtlos wäre.

Doch werde ich mich bemühen, jegliche Unklarheit zu beseitigen und werde versuchen, im Glossar alle wichtigen Wörter noch einmal zu sammeln. Der Real Mode   Einführung in die Intel Architecture     Die Entwicklung der heutigen Intel Architecture kann über den 8085 und den 8080 zum 4004 (dem ersten Mikroprozessor, den Intel entwickelte) zurückverfolgt werden. Der erste Prozessor der Intel Architecture ist jedoch der 8086, dem schnell der für billige Systeme preisgünstigere 8088 folgte.   8086/8088   Der 8086 besitzt 16-Bit Register und einen 16-Bit breiten externen Bus, mit 20 Adress-Bits kann er einen Speicher von 1 mByte ansprechen. Der 8088 unterscheidet sich nur durch den 8-Bit breiten externen Bus vom 8086. Diese Prozessoren führten die Intel-Architecture-typische Segmentierung des Real Mode ein.


16-Bit Register dienen als Zeiger auf Adressen in Segmenten von bis zu 64kByte Größe. Die vier Segment-Register beinhalten die (effektive) Segment-Adresse der aktiven Segment (20 Bit). Bis zu 256 kByte können adressiert werden, ohne zwischen Segmenten umzuschalten, und insgesamt steht ein Adressraum von 1 mByte zur Verfügung.     80286   Der 80286 brachte den Protected Mode in die Intel Architecture. Dieser neue Modus nutzt den Inhalt der Segment-Register als Selektoren oder Zeiger in Descriptor-Tables. Die Deskriptoren stellen 24-Bit Basisadressen zur Verfügung, und damit einen Adressraum von 16 mByte.

Außerdem werden Virtual Memory Management, Segment Swapping und verschiedene Schutzmechanismen unterstützt. Diese Schutzmechanismen beinhalten Segment-Limit-checking, nur lesbare/nur ausführbare Segmente und bis zu vier Privilegstufen, um das Betriebssystem vor Anwenderprogrammen zu schützen. Dazu kommt noch die Hardware-Unterstützung des Task-Switching und die Local-Descriptor-Tables, die es dem Betriebssystem erlauben, die Anwenderprogramme voreinander zu schützen und voneinander zu trennen.     i386   Mit dem i386 kamen 32-Bit-Register in die Intel Architecture, sowohl als Operanden für Berechnungen, als auch als Adressen. Die untere Hälfte der 32-Bit-Register behielt ihre Eigenschaften als 16-Bit-Register der beiden früheren Generationen um komplette Abwärtskompatibilität zu gewährleisten. Ein neuer Modus, der Virtual-8086-Mode, wurde eingeführt, um größere Effizienz beim Abarbeiten von Programmen, die für den 8086/8088 geschrieben worden waren, auf dem 32-Bit System zu erlangen.

Die 32-Bit-Address-Register wurden durch einen 32-Bit-Address-Bus ergänzt, der nunmehr einen Adressraum von 4-gByte zuließ, in dem jedes Segment eben diese Länge von 4 gByte erreichen kann. Die ursprünglichen Befehle wurden durch neue 32-Bit-Operanden verbessert und gänzlich neue Instruktionen wurden hinzugefügt. Der i386 enthält als erster mehrere parallele Stufen, um die Arbeitsgeschwindigkeit zu verbessern: Die Bus Interface Unit (greift auf Speicher und I/O zu) Die Code Prefetch Unit (bekommt den Objektcode von der BIU und leitet ihn in eine 16-byte Warteschlange weiter Die Instruction Decode Unit (dekodiert den Objektcode von der CPU in Mikrocode) Die Execution Unit (führt die mikrocodierten Instruktionen aus) Die Segment Unit (wandelt logische in lineare Adressen um und führt Schutzprüfungen durch) Die Paging Unit (wandelt lineare in physikalische Adressen um)     i486   Durch den i486 gelang es, die Parallelisierung noch weiterzutreiben: Die Instruction Decode Unit und die Execution Unit wurden in 5 Stufen zerlegt, die alle gleichzeitig arbeiten. So kann der i486 eine Instruktion pro CPU-Clock-Cycle durchführen. Der i486 beinhaltet außerdem als erster die Floating-Point-Unit auf dem gleichen IC wie die CPU, wodurch die Geschwindigkeit weiter gesteigert werden konnte.     Pentium   Der Pentium erhielt eine zweite Execution Pipeline, mit deren Hilfe er sogar zwei Instruktionen pro CPU-Clock-Cycle durchführen kann.

Die Register sind noch immer 32 Bit breit, doch interne Datenleitungen bestehen aus 128 oder 256 Bit. Die Effektivität des Virtual-8086-Mode wurde außerdem erhöht.   Schon der i386 besitzt die wichtigsten Merkmale der sogenannten Intel Architecture, deshalb ist er wohl der geeignete Einstiegspunkt.   Eine kurze Zusammenfassung der wesentlichen Eigenschaften dieses 32-Bit-Prozessors: 32-Bit Vielzweck- und Offsetregister 16-Byte-Prefetch-Queue Speicherverwaltungseinheit (Memory Management Unit) mit Segmentierungs- und Paging Unit (PU) 32-Bit Daten- und Adreßbus 4gByte physikalischer Adreßraum 64tByte virtueller Adreßraum 65536 8-, 16- oder 32-Bit Ports Implementierung von Real, Protected und Virtual 8086 Mode Der allgemeine Aufbau des i386   Der Real Mode   Zunächst möchte ich die Register des i386 vorstellen, die im Real Mode benutzt werden können. Natürlich hat der Prozessor viele interne Register zur Speicherung von Zwischenergebnissen usw., die jedoch dem Programmierer nicht zugänglich und daher nur von geringer Bedeutung sind.

  Die Vielzweckregister dienen als schnelle interne Datenspeicher der CPU. Die Execution Unit liest Werte aus einem oder mehreren von diesen Registern, führt sie der ALU zu, um Manipulationen vorzunehmen, und legt das Ergebnis schließlich wieder in einem oder mehreren Registern ab. Neben den Vielzweck- gibt es noch die Segmentregister zur Speicherverwaltung sowie diverse Steuerregister.   Der i386 liest über seinen 32-Bit-Datenbus Daten aus dem Speicher oder schreibt sie mit Hilfe des Datenbusses in den Speicher. Die betreffende Stelle im Speicher wird dabei durch eine 32-bit-Adresse festgelegt, die der Prozessor mit Hilfe der Addressing Unit berechnet und über den 32-Bit-Adreßbus an das Speichersubsystem übergibt. Die Adreßberechnung ist je nach Betriebsmodus des i386 – Real- ,Protected- oder Virtual8086-Mode – unterschiedlich aufwendig.

  Befehle und Daten befinden sich wie heute üblich im selben physikalischen Speicher, wobei aber dieser eine physikalische Speicher bei der Intel Architecture logisch in mehrere verschiedene Abschnitte, sogenannte Segmente, aufgeteilt ist und die Abschnitte Programmcode oder Daten enthalten.   Segmentierung im Real Mode   Bereits der 8086 teilte den zur Verfügung stehenden Speicher in die Segmente auf. Das machen auch die neueren Prozessoren, bis hin zum Pentium II MMX. Da der 8086 aber insgesamt nur 20 Adreßleitungen gegenüber den 32 des i386 aufweist, kann er maximal 220Byte=1mByte Speicher adressieren. Damit besteht sein physikalischer Adreßraum aus 1mByte Speicher. Jedes der Vielzweckregister im 16-Bit-Prozessor 8086 (zur damaligen Zeit der 8-Bit-Chips dieselbe Sensation wie später der Übergang von 16 auf 32 Bits mit dem i386) ist jedoch nur 16 Bits lang und kann maximal 216byte=64kByte adressieren.

Der 8086 unterteilt also den physikalischen Adreßraum in 64k-Segmente mit einer Größe von jeweils 64kByte. Innerhalb eines Segmentes wird die Stelle eines Bytes durch einen Offset angegeben. Offsets werden in den Vielzweckregistern gespeichert. Demgegenüber werden die Segmente über die Segmentregister CS bis GS angesprochen. Die CPU bildet für den Zugriff auf den Speicher sogenannte Segment-Offset-Paare: Das Segment eines bestimmten Speicherobjekts wird durch das Segmentregister, der Offset innerhalb des so festgelegten Segments dann noch durch das beteiligte Vielzweckregister angegeben. Die 16-Bit-Segmentregister können wie die 16-Bit-Offsetregister des 8086 64k=65536 Segmente adressieren, die jeweils 64kByte groß sind.

Der theoretisch mögliche Adreßraum umfaßt daher 64kBytex64kByte=4gByte. Das ist mit dem 20-Bit-Adreßbus des 8086 aber nicht zu realisieren, er ist nur in der Lage, 1mByte anzusprechen. Die Segmente werden daher in einem Abstand von 16 Byte verzahnt. Erhöht sich der Wert des Segmentregisters um eins, so verschiebt sich das Segment lediglich um 16 Bytes, nicht um ein ”ganzes” Segment mit 64kByte. Demgegenüber verschiebt eine Erhöhung des Offsetregisters um den Wert eins das Speicherobjekt nur um eine Stelle (d.h.

ein Byte). Änderungen der Segmentregister führen also zu einem wesentlich größeren (zum 16-fachen) Versatz als Änderungen der Offsetregister. Die eben beschriebene Festlegung von Segment und Offset ist charakteristisch für den Real Mode. Der 8086 kann nur in eben diesem Modus arbeiten, der i386 (sowie alle weiteren Prozessoren der IA) beginnt nach dem Einschalten im Real Mode, kann aber später in den Protected- oder Virtual-8086-Mode umgeschaltet werden.   Die Adresse eines Objekts wird im Real Mode also durch die einfache Formel:   10h * Segment + Offset     berechnet. Anders ausgedrückt bedeutet das eine Verschiebung des Segmentregisters um vier Bits nach links und eine Addition des Offsets.

Die quasi aus dem nichts auftretenden vier Bits bei dieser Segmentregisterverschiebung werden auf Null gesetzt. Die AU führt genau diesen Verschiebungs- und Additionsprozeß aus: Nach einer Verschiebung der Segmentadresse um vier Bits nach links summiert ein Addierer in der AU die verschobene Segmentadresse und den Offset, um die betreffende lineare Adresse zu bilden.   Segment und Offset werden meist hexadezimal in der Schreibweise Segment:Offset angegeben.   Beispiel: 1F36:0A5D steht für Segment 1F36, Offset 0A5D; Nach der oben angeführten Formel ergibt sich für die lineare Adresse: 1F36h * 10h + 0A5Dh = 7990d * 16d + 2653d = 130493d   Alternativ kann man auch mit einer Verschiebung des Segments um vier Bits (eine Hexadezimalstelle) arbeiten: 1F360h 00A5Dh 1FDBDh=130493d   Zu beachten ist, daß zwei verschiedene Segment-Offset-Paare im Real Mode durchaus dieselbe Speicherstelle bezeichnen können.   Beispiel: 1FB1:02AD => 1FB1h * 10h + 02ADh = 130493d (vgl. obiges Beispiel)   Wie bereits erwähnt, ist diese Art der Adreßberechnung charakteristisch für den Real Mode oder eben den 8086.

Im Protected Mode, der als wesentliche Neuerung mit dem 80286 eingeführt wurde, sind Segment und Offset vollständig entkoppelt. Die Segmentregister besitzen eine völlig andere Bedeutung, es findet keine Abbildung nach der oben angegebenen einfachen Formel statt. Dadurch ist beim 80286 ein logischer Adreßraum von maximal 1gByte und beim 80386 sogar 64tByte je Task (Programm) möglich. Dieser logische Adreßraum wird jedoch von der Segmentierungslogik beim 80286 auf einen physikalischen Adreßraum von maximal 16mByte entsprechend seinen 24 Adreßleitungen (224=16mByte) abgebildet. Beim i386 stehen bereits 4gByte (232=4gByte) zur Verfügung. Meist liegt der tatsächliche Speicherausbau des Computers jedoch weit unter diesem Wert.

  Der im i386 erstmals implementierte Virtual 8086 Mode stellt eine erhebliche Innovation im Hinblick auf die Betreibung im Protected Mode dar. Der i386 führt in diesem Modus die Adreßberechnung nach der oben beschriebenen Formel des Real Mode aus, wobei aber der tatsächliche Zugriff auf den Speicher und die Peripherie durch dieselben Mechanismen überwacht und gegen unerlaubte und fehlerhafte Versuche geschützt wird, wie sie für den Protected Mode charakteristisch sind.     Die Vielzweck- und Segmentregister   Die Vielzweckregister sind ab dem i386 32 Bit breit, können aber aus Kompatibilitätsgründen mit den 16-Bit Vorgängern auch als 16- oder 8-Bit-Register angesprochen werden. Der i386 weist sieben Vielzweckregister, EAX bis EBP, sechs Segmentregister, CS bis GS, einen Befehlszähler EIP, einen Stack-Zeiger ESP sowie das Flagregister EFlags auf. All diese sind 32 Bit, nur die Segmentregister 16 Bit breit. Durch die maximale Größe der Vielzweckregister von 32 Bit sind beim i386 Offsets mit einer Länge von 32 Bits möglich, solche also von 0 bis 4gByte-1.

Segmente können beim i386 demnach wesentlich größer sein, als beim 8086. Bei allen 32-Bit-Registern ist es möglich, auch nur die zwei niederwertigen Bytes anzusprechen. Sie werden als AX bis DI, IP, SP und Flag bezeichnet. Das niederwertige Wort der vier Vielzweckregister AX bis DX kann sogar noch weiter in zwei Registerbytes aufgeteilt werden, nämlich AH und AL, BH und BL, usw. Dadurch kann der i386 auch einzelne Datenbytes bearbeiten. Akkumulator EAXDer Akkumulator wird am häufigsten zum Speichern von Daten verwendet.

Die Sonderstellung des Akkumulators hat historische Gründe, da es in den älteren und einfacheren Mikro-prozessoren nur ein Register (eben den Akkumulator) gab, um beispielsweise Daten zu addieren. Heute ist von dieser Einschränkung übrig geblieben, daß viele Befehle nur für den Akkumulator geschwindigkeitsoptimiert sind und damit bei einer Referenz des Akkumulators schneller arbeiten. Auch sind manche Befehle bei Registerreferenzen nur für den Akkumulator gültig.   Beispiel: OUT 70h, ax ;über Port 70h wird der Wert des Akkumulators ax ausgegeben. MOV ax,2Dh ;der Akkumulator ax wird mit dem Wert 2Dh geladen.  Basisregister EBXDas Basisregister kann zur temporären Speicherung von Daten und als Zeiger auf die Basis von Datenobjekten (beispielsweise den Beginn eines Feldes) bei indirekter Adressierung verwendet werden.

  Beispiel: MOV ecx, [ebx] ;ecx wird mit dem Wert geladen, der an der Basisadresse ebx gespeichert ist.  Zählregister ECXDas Zählregister speichert üblicherweise die Zahl der Wiederholungen von Schleifen (LOOP), Zeichenkettenbefehlen (REP) oder Verschiebungen und Rotationen (SHL, ROL, etc.). Bei dieser Verwendung wird der Wert von ECX bei jedem Schleifendurchlauf um eins erniedrigt. ECX kann auch als gewöhnliches Vielzweckregister verwendet werden, um zum Beispiel Daten temporär abzulegen.   Beispiel: MOV ecx, 10h ;ecx mit 10h laden Marke: ;Label für Rücksprung OUT 70h, al ;al über Port 70h ausgeben LOOP Marke ;wiederholen, bis ecx null ist (=> 16 mal)  Datenregister EDXDas Datenregister wird am häufigsten zur temporären Speicherung von Daten verwendet.

Bei der Ein- und Ausgabe enthält EDX die I/O-Adresse des anzusprechenden Ports (zwischen 0 und 65536). Der Weg über das Register EDX ist dabei der einzige Weg, um Ports mit einer I/O-Adresse größer als 255 anzusprechen.   Beispiel: MUL ebx ;Multiplikation von ebx mit eax (implizit), Produkt in edx:eax OUT dx, ax ;Ausgabe von ax über Port dx  Basiszeiger EBPAuch wenn der Basiszeiger zur allgemeinen temporären Speicherung von Daten verwendet werden kann, so liegt seine Stärke dennoch in der Verwendung als Zeiger. In diesem Fall dient er meist als Zeiger auf die Basis eines Stack-Frames und wird zum Ansprechen der Argumente von Prozeduren verwendet.   Beispiel: Addition von 3 Parametern PUSH sum1 ;sum1 auf den Stack bringen PUSH sum2 ;sum2 auf den Stack bringen PUSH sum3 ;sum3 auf den Stack bringen CALL addition ;Aufruf der Funktion addition ..

......

. Addition PROC NEAR ;Near call mit vier Byte für alten eip als Rückkehradresse PUSH ebp ;ebp sichern MOV ebp, esp ;ebp mit esp (Top of Stack) laden MOV eax, [ebp+8] ;sum3 nach eax kopieren ADD eax, [ebp+12] ;sum2 zu eax addieren ADD eax, [ebp+16] ;sum1 zu eax addieren POP ebp ;alten ebp wiederherstellen RET ;Rücksprung, (sum1+sum2+sum3) in eax Addition ENDP  Source-Index ESIÄhnlich wie EBP kann der Source-Index ESI als allgemeiner temporärer Datenspeicher und als Zeiger benutzt werden. Meist wird ESI als Index eines Datenobjekts innerhalb eines Feldes oder einer ähnlichen Struktur verwendet, dessen Basis häufig durch das Basisregister EBX angegeben wird. Bei Zeichenkettenbefehlen zeigt ESI auf einzelne Bytes, Worte oder Doppelworte innerhalb der Source-Zeichenkette und wird bei wiederholter Ausführung des Befehls (durch REP) in Abhängigkeit vom Direction-Flag (DF) automatisch erhöht oder erniedrigt.   Beispiel: Ausgabe der Zeichenkette ”abcdefghij” unterstrichen mit MOVSW   String DB ‘a‘,1,‘b‘,1,‘c‘,1,‘d’,1,‘e‘,1,‘f‘,1,‘g‘,1,‘h‘,1,‘i‘,1,‘j‘,1 ;1 ist unterstrichen   MOV eax, @data ;Datensegment von string in eax laden MOV ds, eax ;ds auf Datensegment von string einstellen MOV eax, 0B800h ;Segment des Video-Ram nach eax laden MOV es, eax ;Videosegment nach es kopieren CLD ;von a nach j (aufsteigend) vorgehen MOV ecx, 5 ;5 Worte á 4 Bytes (4 Zeichen + 4 Attribute) übertragen MOV esi, OFFSET string ;Adresse von string nach esi laden MOV edi, 00h ;Offset 0 im Video-RAM REP MOVSW ;fünfmal ein Wort übertragen   Durch den Befehl REP MOVSW werden edi und esi nach jedem übertragenen Wort um zwei Byte erhöht und zeigen damit auf das nächste zu übertragende Wort  Destination-Index EDIDer Destination-Index EDI stellt das Pendant zum Source-Index ESI dar und kann als allgemeiner temporärer Datenspeicher und als Zeiger benutzt werden.   Beispiel: siehe Source-Index  Codesegment CSDas Codesegment CS enthält die Befehle und Daten, die unmittelbar (immediate) adressiert werden.

Die Befehle des Segments werden durch den Befehlszeiger EIP (Extended Instruction Pointer) adressiert. Das Codesegment wird bei einem Far-Call und einem INT automatisch verändert. Im Protected Mode prüft der i386 bei einer Änderung des Inhalts eines Segmentregisters automatisch, ob der aufrufende Task auch eine Zugangsberechtigung für das neue Segment hat.  Datensegment DSDas Datensegment DS enthält Daten, die dem gerade laufenden Programm zugeordnet sind. Viele Befehle verwenden das Datensegment implizit, um Daten im Speicher zu adressieren. Erst eine Überschreibung mit einem anderen Segmentregister hebt diese automatische Segmentadressierung auf.

 Stacksegment SSDas Stacksegment SS enthält Daten, auf die mittels Stack-Befehlen wie PUSH, POP, PUSHA, POPA etc. zugegriffen wird. Diese Befehle benützen den Wert von SS automatisch, um Daten im Speicher (auf dem Stack) abzulegen oder aus diesem zu lesen. Auch die sogenannten lokalen Variablen oder lokalen Daten von Prozeduren werden normalerweise auf dem Stack abgelegt. Sie werden dann nach einer Rückkehr von der Prozedur von einer anderen Prozedur wieder überschrieben. Beim Abspeichern von Daten auf dem Stack wird der zugehörige Stapelzeiger ESP (Stack Pointer) automatisch entsprechend dem Umfang der abgelegten Daten vermindert.

Damit wächst der Stack von höheren zu niedrigeren Speicheradressen.  Extrasegmente ES, FS, GSDiese Segmentadressen stehen für Zeichenkettenbefehle zur Verfügung. Außerdem können ES, FS und GS dazu benützt werden, das Standarddatensegment DS zu überschreiben, um die Daten einer Speicherzelle anzusprechen, die nicht in DS liegt, ohne den Wert in DS zu verändern.   Die sechs Segmentregister sind in allen Betriebsmodi des i386 für die Organisation und Adressierung des Speichers von erheblicher Bedeutung.   Die Flags   Bedingten Sprüngen geht nahezu immer ein logischer Vergleich zweier Größen (eine Prüfung einer Bedingung) voraus, wie schon das Wort bedingt impliziert. Von großer Bedeutung in diesem Zusammenhang ist das Flag-Register, da bestimmte Flags in Abhängigkeit vom Ergebnis des Vergleichs gesetzt (Flag gleich 1) oder gelöscht (Flag gleich 0) werden.

Auch setzen und löschen manche Befehle bestimmte Flags. Bei den 16-Bit Prozessoren waren nur die unteren 16 der folgenden 32 Bit vorhanden, deswegen wird dieses Register auch EFlags genannt.    Im folgenden eine Beschreibung der einzelnen Flags des i386. [Da bestimmte Flags in dieser Arbeit keine Bedeutung haben, werde ich diese (IP, VIP, VIF, AC,VM) nicht erklären]  Carry (Übertrag; ab 8086)Carry wird gesetzt, wenn ein Vorgang einen Übertrag für den Zieloperanden erzeugt. Dies ist beispielsweise der Fall, wenn bei einer 32-Bit Addition die Summe zweier 32-Bit-Zahlen ein Ergebnis größer als 4gByte-1 erzeugt. Carry kann durch den Befehl STC (Set Carry) gesetzt, durch CLC (Clear Carry) gelöscht und durch CMC (Complement Carry) komplementiert werden.

 Parity (Parität; ab 8086)Parity wird gesetzt, falls das Ergebnis der Operation eine gerade Zahl von gesetzten Bits aufweist. Parity wird vom Prozessor gesetzt.  Auxiliary Carry (zusätzlicher Übertrag; ab 8086)Das Flag wird für Arithmetik mit BCD-Zahlen verwendet und wird gesetzt, wenn eine Operation einen Übertrag oder ein Borrow für die unteren vier Bits (BCD-Zahlen belegen nur die unteren vier Bits eines Byte) eines Operanden erzeugt.  Zero (Null; ab 8086)Zero wird vom Prozessor gesetzt, falls das Ergebnis einer Operation Null ergibt. Ein Beispiel liefert die Subtraktion gleich großer Zahlen oder das bitweise logische AND eines Wertes mit Null.  Sign (Vorzeichen; ab 8086)Sign ist gleich dem höchstwertigen Bit des Operationsergebnisses (0=positiv, 1=negativ) und hat damit nur einen Sinn für vorzeichenbehaftete Zahlen.

Ist die Differenz zweier Zahlen negativ, so wird Sign vom Prozessor auf eins gesetzt. Damit können beispielsweise zwei Zahlen miteinander verglichen werden.  Trap (Einzelschritt; ab 8086)Ist Trap gesetzt, so erzeugt der Prozessor nach jedem Schritt einen Interrupt 1. Trap gehört also zur Klasse der Exceptions. Viele Debug-Programme setzen Trap und fangen den Interrupt ab, um ein Programm schrittweise auszuführen.  Interrupt Enable (Interrupt erlauben; ab 8086)Ist Interrupt Enable gesetzt, so akzeptiert der Prozessor Hardware-Interrupts.

Dieses Flag kann explizit mit CLI gelöscht und mit STI gesetzt werden. Interrupts müssen bei Anwendungen gesperrt werden, die keine Unterbrechung erlauben. Eine zu lange Sperrung kann jedoch zu Problemen bei Echtzeitanwendungen führen. Normalerweise sollte nur das Betriebssystem dieses Flag verändern.  Direction (Richtung; ab 8086)Direction bestimmt die Richtung von String-Operationen (z.B.

MOVS). Ist Direction gesetzt, so werden die Zeichenketten von hoher zu niedriger Adresse bearbeitet, ansonsten von niedriger zu hoher Adresse. Das Direction-Flag kann mit STD gesetzt und mit CLD gelöscht werden.  Overflow (Überlauf; ab 8086)Overflow wird vom Prozessor gesetzt, falls das Ergebnis einer Operation für den Zieloperanden zu groß oder zu klein ist. Beispielsweise kann die Addition zweier 16-Bit-Zahlen zu einem Wert führen, der nicht mehr in ein 16-Bit-Register paßt.   Die bisher beschriebenen Register waren bereits auf dem 8086 vorhanden, die folgenden sind mit dem 80286 dazugekommen, um den Protected Mode zu unterstützen und einen Schutz für den I/O-Adreßbereich zu implementieren.

 I/O-Protection-Level (I/O-Schutzebene; ab 80286)Dieses 2-Bit Flag gibt im Protected Mode die minimal benötigte Schutzebene für Ein- und Ausgabeoperationen für den I/O-Adreßraum an und wird vom Betriebssystem verwaltet. Im Real Mode hat das Flag keine Bedeutung.  Nested Task (verschachtelter Task; ab 80286)Nested Task dient im Protected Mode zur Überwachung der Verkettung unterbrochener und aufgerufener Tasks und wird vom Betriebssystem verwaltet. Ein gesetztes NT-Flag zeigt an, daß mindestens ein Task-Switch aufgetreten ist und sich im Speicher ein inaktives Task-State-Segment befindet.   Beim i386 sind im Flag-Register noch zwei Einträge hinzugekommen, die einerseits die neu implementierte Debug-Unterstützung, andererseits den innovativen Virtual 8086 Mode betreffen (die jedoch beide für diese Arbeit nicht von all zu großer Bedeutung sind. Ich will sie nur der Vollständigkeit halber erwähnen).

 Resume (Wiederanlauf; ab i386)Das Resume-Flag steuert den Wiederanlauf eines Tasks nach einer Breakpoint-Unterbrechung über die Debug-Register des i386. Ist Resume gesetzt, so werden die Breakpoints temporär deaktiviert. Das bedeutet, daß die Programmausführung an der Unterbrechungsstelle wiederaufgenommen werden kann, ohne daß eine erneute Debug-Exception auftritt.  Virtual 8086 Mode (virtueller 8086-Modus; ab i386) Um den i386 in den Virtual 8086 Mode umzuschalten muß das Betriebssystem das VM-Flag setzen. Das ist nur im Protected Mode und hier nur über ein Gate möglich. Ist das VM-Flag gesetzt, so arbeitet der Prozessor im Virtual 8086 Mode.

Er führt dann die vom 8086 her bekannte einfache Real-Mode-Adreßberechnung aus und kann dadurch Real-Mode-Anwendungen ausführen. Das alles geschieht aber im Gegensatz zum Real Mode in einer geschützten Umgebung. Ist das VM-Flag dagegen gelöscht, so arbeitet der i386 im gewöhnlichen Protected Mode. Im Real Mode hat das Flag keine Bedeutung.   Nahezu alle Befehle zu bedingten Sprüngen testen die Werte des Flags, das das Ergebnis des vorherigen Vergleichs widerspiegelt. Das macht die besondere Bedeutung des Flag-Registers aus.

  Beispiel: Ist der Wert des eax-Registers gleich 5 so soll zu Marke1 verzweigt werden: CMP eax, 5 ;Register eax mit dem Wert 5 vergleichen JE Marke1 ;Sprung zu Marke1, wenn der letzte Vergleich Gleichheit ergeben hat   Der i386 führt den Vergleich CMP eax, 5 aus, indem er den Wert 5 von eax subtrahiert und die Flags entsprechend setzt. In unserem Beispiel wird daher 5 vom Wert des Registers eax subtrahiert. Ist eax gleich 5, so wird das Zero-Flag gesetzt. Ist eax größer 5, so setzt der Prozessor die Flags Sign und Zero auf 0. Ist eax kleiner als 5, so wird Zero auf 0 und Sign auf 1 gesetzt.   Manche Befehle (wie zum Beispiel JBE = Jump if Below or Equal) testen mehrere Flags (hier: Sign und Zero) um zu ermitteln, ob die Sprungbedingung erfüllt ist oder nicht.

    Steuer- und Speicherverwaltungsregister   Der i386 besitzt vier eigentliche Steuerregister sowie vier Speicherverwaltungsregister für den Protected Mode. Die Steuer-Register sind jeweils 32 Bit breit.   Das bereits im 80286 implementierte Maschinenstatuswort (MSW) für die Unterstützung des 16-Bit Protected Mode beim 80286 ist im niederwertigen Wort des Steuerregisters CR0 aufgegangen. Die Bedeutung der Bits TS, EM, MP und PE ist daher dieselbe wie beim 80286. Aus Kompatibilitätsgründen kann das niederwertige MSW-Wort des CR0-Registers weiterhin über die 80286-Befehle LMSW (Load MSW) und SMSW (Store MSW) angesprochen werden.  PE (Protection Enable, Protected Mode aktivieren; 80286)Wenn dieses Bit gesetzt wird, schaltet der i386 in den Protected Mode um.

Beim 80286 war eine Rückkehr zum Real Mode nur über einen Prozessor-Reset oder einen Dreifach-Fehler (Triple Fault) möglich; beim i386 kann das explizit durch einen Befehl vollzogen werden, der das PE-Bit löscht. Das darf aber nur ein Task mit der Privilegstufe 0 (das Betriebssystem), sonst wird eine Exception ausgelöst.  MP (Monitor Coprocessor, Coprozessor überwachen; 80286)Wenn das MP-Bit gesetzt ist, dann löst der WAIT-Befehl die Exception ”kein Coprozessor vorhanden” aus, die zu einem Interrupt 7 führt.  EM (Emulate Coprocessor, Coprozessoremulation; 80286)Ein gesetztes EM-Bit teilt dem i386 mit, daß alle Coprozessorfunktionen des i387 durch Software emuliert werden müssen, das heißt keine Datenübertragung zwischen der CPU und dem Coprozessor ausgeführt wird. Statt dessen führt jeder ESC-Befehl für den Coprozessor zu einer Exception 7, deren Handler dann den entsprechenden Befehl mit der Ganzzahlarithmetik des i386 ausführt.  TS (Task Switched, Taskwechsel; 80286)Ist dieses Bit gesetzt, so ist ein Task-Switch aufgetreten; der i386 ist also im Protected Mode mindestens einmal auf ein Task-Gate gestoßen.

Das TS-Bit ist wichtig, um zu ermitteln, ob der Coprozessor möglicherweise noch einen Befehl für den alten Task oder schon einen für den neu aktivierten berechnet. Da manche aufwendigen i387-Befehle bis zu 1000 Taktzyklen benötigen, ist es zumindest denkbar, daß die i386 CPU zu einem neuen Task umschaltet, diesen bedient und, bevor der Coprozessor seinen Befehl abgearbeitet hat, zum alten Task zurückschaltet.   Die vier beschriebenen Bits des CR0-Registers sind bereits im 80286-MSW implementiert. Beim i386 sind zwei Bits hinzugekommen, die zur Aktivierung oder Deaktivierung der Paging-Unit und zur Festlegung des Coprozessors als i387 oder 80287 dienen.  ET (Extension Type; i386)Mit dem ET-Bit wird festgelegt, ob der installierte Coprozessor ein i387 (ET=1) oder ein 80287 (ET=0) ist. Der i386 kann mit beiden zusammenarbeiten, weil die Schnittstelle zwischen beiden und das Protokoll für den Datenaustausch identisch sind.

Dagegen ist die Breite des Datenbusses für den 80287 mit 16 Bits geringer. Dementsprechend mehr besondere I/O-Zyklen müssen der i386 und 80287 ausführen, um Codes und Daten auszutauschen.  PG (Paging; i386)Mit dem PG-Bit wird die Paging Unit (PU) in der Speicherverwaltungseinheit des i386 aktiviert (PG=1) oder deaktiviert (PG=0). Bei deaktivierter PU führt der i386 keine Adreßtransformation aus, die lineare Adresse nach Addition von Offset und Segmentbasis stellt automatisch die physikalische Adresse des Speicherobjekts dar, so wie der i386 über seine Adreßleitungen den Speicher physikalisch adressiert. Bei aktiver Paging Unit führt der i386 dagegen zusätzlich zur (schon aufwendig genug erscheinenden) Segmentierung noch eine weitere Adreßtransformation aus, um aus der linearen eine physikalische Adresse zu bilden. Paging kann nur im Protected Mode benutzt werden (Ich werde jedoch im Rahmen dieser Arbeit nicht näher darauf eingehen).

  Wie bereits erwähnt, stehen für den Zugriff auf das MSW zwei besondere Befehle zur Verfügung, nämlich LMSW und SMSW. Wenn man aber auf das PG- oder das ET-Bit zugreifen möchte, so muß das Steuerregister CR0 mit einem MOV-Befehl angesprochen werden.   Beispiel: Der i386 soll durch Setzen des PE-Bits in CR0 in den Protected Mode versetzt werden: Möglichkeit: MOV CR0, 0001h ;PE-Bit durch MOV-Befehl mit 32-Operanden setzen Möglichkeit: LMSW 01h ;PE-Bit durch LMSW-Befehl mit 16-Bit Operanden setzen     Logische Speicheradressierung und logischer Speicherzugriff des i386   Dieses Kapitel behandelt die logische Adressierung des Speichers. Dazu ist eine eingehende Untersuchung der segmentierten Speicherorganisation und der beteiligten Register notwendig. Außerdem unterscheidet sich die Speicheradressierung je nach dem aktiven Betriebsmodus (Real Mode, Protected Mode, Virtual 8086 Mode).   Codesegment und Befehlszähler   Zur Programmausführung holt der Prozessor Befehle aus dem Speicher (Befehls-Prefetching) und führt diese dann aus.

Grundlage für dieses automatische Lesen bilden Codesegment und Befehlszeiger. Das Codesegment gibt dabei das Segment an, aus dem der nächste Befehl gelesen werden soll. Der Befehlszeiger ist der Offset des nächsten zu lesenden Befehls. Das Paar Codesegment:Befehlszeiger bildet somit die Adresse des nächsten auszuführenden Befehls im Speicher. Der Prozessor kann damit diesen Befehl einlesen und ausführen. Am Code des Befehls erkennt der Prozessor, wie viele Bytes er einlesen muß, damit sich der vollständige Befehl im Prozessor befindet.

Befehle für den 80x86 sind zwischen einem und 15 Bytes lang.   Ist der Befehl ausgeführt worden, so wird der Befehlszeiger um die Zahl der Bytes inkrementiert, die der gerade ausgeführte Befehl aufwies. Bei einem kurzen 2-Byte-Befehl wird der Befehlszähler also um zwei erhöht, das Codesegment:Befehlszeiger-Paar verweist dann auf den nächsten auszuführenden Befehl. Dieser wird in gleicher Weise eingelesen und ausgeführt. Anschließend inkrementiert der Prozessor den Befehlszeiger erneut. Dieses Einlesen und Inkrementieren führt die CPU dabei völlig selbständig aus, es ist kein Eingriff eines Steuerprogrammes oder gar des Benutzers notwendig.

Einmal ”angestoßen”, fährt die CPU damit ständig fort, Befehle einzulesen und auszuführen.   Dieser gleichmäßige Befehlsstrom kann jedoch durch bedingte und unbedingte Sprünge und Verzweigungen unterbrochen und an anderer Stelle wieder fortgesetzt werden. Hierzu muß nur der Wert des Befehlszeigers und gegebenenfalls des Codesegments verändert werden. Bei einem Near-Call oder –Jump bleibt das Codesegment unverändert, es wird nur der Wert des EIP neu geladen. Demgegenüber wird bei einem Far-Call oder –Jump auch der Wert des Codesegments verändert. Der Prozessor fährt an einer anderen Stelle des im Speicher befindlichen Programmes fort.

Sprünge – allgemeiner auch Verzweigungen oder Branches genannt – sind für den logischen Ablauf von Programmen sehr wichtig, weil ein Computer häufig in Abhängigkeit von bestimmten Bedingungen verschiedene Dinge ausführen soll.   Beispiel: Der Wert des Codesegments lautet 24D5, der Wert des Befehlszählers 0108. Der nächste Befehl befindet sich damit bei der Adresse 24D5:0108. Der Code an dieser Adresse lautet 8CC0. Die Steuereinheit CU dekodiert diesen Code und ermittelt den Befehl MOV eax, es Es soll also der Wert des Extrasegments es in das 32-Bit-Akkumulatorregister eax übertragen werden. Nach der Ausführung des Befehls wird der Wert des Befehlszählers um zwei erhöht, da <MOV eax, es> ein Zwei-Byte-Befehl war.

Der Wert von EIP lautet somit 10a0, der Wert des Codesegments bleibt unverändert.         Stacksegment und Stackzeiger   Eine besondere Bedeutung besitzt das Stacksegment SS sowie der zugehörige Stack-Pointer oder Stapelzeiger ESP. Jedes Programm besitzt normalerweise ein eigenes Stacksegment, auf dem mit PUSH der Wert eines Registers oder eines Speicherwortes abgelegt werden kann. Mit PUSHF können die Flags und ab dem 80186 mit PUSHA alle Vielzweckregister auf dem Stack gespeichert werden. Umgekehrt werden mit POP, POPF bzw. POPA (ab 80186) die entsprechenden Daten vom Stack wieder abgenommen.

Dabei wächst der Stack nach unten, d.h. zu kleineren Werten des Stapelzeigers ESP. Werden Daten auf dem Stack abgelegt, so vermindert sich der Wert von ESP um vier, weil immer ein ganzes Doppelwort auf den Stack geschoben wird. Wird der i386 im 16-Bit-Modus betrieben, so werden stets nur zwei Byte auf dem Stack abgelegt und der Wert von SP mit jedem PUSH nur um zwei verringert. Das gilt natürlich auch für die 16-Bit-Vorgänger 8086 und 80286.

Ist der Stack leer, dann nimmt der Stapelzeiger ESP seinen größten Wert an. Nach dem Ablegen desWortes zeigt der Stack-Pointer auf das zuletzt abgelegte Wort auf dem Stack. Ein Befehl PUSH dekrementiert also zuerst den Wert des Stapelzeigers ESP, und anschließend wird der Register- oder Speicherwert auf dem Stack abgelegt. Durch das Wachsen des Stack nach unten kann ein Stapelüberlauf auf einfache Weise erkannt werden: Nimmt ESP den Wert 0 an, so ist der Stack erschöpft und bei entsprechend programmierten Anwen-dungsprogrammen, die den Stack laufend überprüfen, erscheint die Mitteilung Stapelüberlauf oder Stack-Overflow. Programmierer sehen jedoch meist einen ausreichend großen Stack (ausreichend großen Anfangswert für ESP) vor, so daß ein solcher Stack-Overflow nur bei einem Programmfehler oder einer fehlerhaften Programmbedienung auftreten sollte. Nachteilig ist, daß im Real Mode das Anwendungsprogramm vor jedem PUSH explizit prüfen muß, ob noch ausreichend Kapazität auf dem Stack frei ist.

Im Protected Mode wird die Prüfung, ob ein Stacküberlauf vorliegt, von der Hardware des Prozessors erledigt. Damit ist eine sehr schnelle Überprüfung möglich, ohne daß zusätzliche Software-Routinen notwendig sind.   Das Datensegment DS und die Adressierung   Neben dem Code- und Stacksegment hat auch das Datensegmentregister DS eine besondere Bedeutung. Es ist immer dann wichtig, wenn ein Befehl Daten aus dem Speicher liest oder in ihm abspeichert, d.h. wenn Speicheroperanden betroffen sind.

Der Offset des Speicheroperanden wird üblicherweise in einem der Vielzweckregister bereitgehalten, und das Paar DS:Offset verweist auf den anzusprechenden Wert. Das Datensegmentregister DS wird standardmäßig als das einem Offset zugeordnete Segmentregister verwendet. Wenn dagegen ein Wert in einem anderen Segment geschrieben oder gelesen werden soll, muß das Segmentregister DS mit dem Wert eines neuen Segments geladen oder ein Segmentpräfix benutzt werden, das das Segment DS durch eines der Extrasegmente ES bis GS ersetzt. Die Daten des Codesegments sollten dabei nur ausführbar und höchstens noch lesbar, nicht aber überschreibbar sein. Ein Überschreiben von Code führt notwendigerweise zum Absturz eines Programmes. Nur-ausführbare Daten sind nicht in Vielzweck- oder Segmentregister einlesbar.

Ein Programm kann sie also nicht im Sinne von Daten verwenden, die verarbeitet werden. Die Verwendung verschiedener Segmente für Code, Stack und Daten gestattet eine Trennung der verschiedenen Abschnitte eines Programmes. Im Protected Mode wird davon intensiv Gebrauch gemacht, um ein versehentliches Überschreiben von Code durch einen Programmfehler (eine häufige Ursache hängender Programme) zu vermeiden. Selbstverständlich können alle Segmentregister denselben Wert aufweisen. In diesem Fall findet keine Trennung von Code, Stack und Daten statt. Die .

COM-Programme unter MS-DOS sind in dieser Weise strukturiert. .COM-Programme sind Relikte aus den Zeiten von CP/M, einem Betriebssystem für einfachere 8-Bit-Prozessoren. Sie unterstützen keinen in Segmente aufgeteilten Speicher. Damit ist der Adreßraum für Code und Daten auf zusammen 64kByte (ein Segment) begrenzt.   Adressierungsarten     Programmierung auf Prozessorebene: Mnemonics und der Assembler   Befehle, wie MOV, CALL, JNZ.

.. werden als mnemonische Codes oder Mnemonics bezeichnet. Sie dienen nur dazu, dem Programmierer eine Gedächtnisstütze zu liefern, da die Mnemonics die Operation des entsprechenden Befehls in verkürzter Form angeben. Ein Assembler versteht diesen mnemonischen Code und führt eine entsprechende Codierung in einen Maschinenbefehl aus. Maschinenbefehle sind – wie könnte es anders sein – eine Folge von Nullen und Einsen mit einem oder mehreren Byte Länge.

Wenn nun ein Programm mit TYPE ausgegeben wird, so werden diese Codes als ASCII-Zeichen interpretiert und anscheinend völlig wirres Zeug ausgegeben.   Adressierungsarten   Soll ein Register (hier beispielsweise der Akkumulator eax) über MOV eax,<wert> mit einem Wert geladen werden, dann stehen drei Möglichkeiten zur Verfügung:   Unmittelbarer Operand (Immediate): MOV eax,6a02h Das Akkumulatorregister eax wird mit dem Wert 6a02h geladen. Dieser Wert wird zur Programmierzeit fest vorgegeben und ist Bestandteil des Programmcodes, d.h. er erscheint als Bestandteil des Befehlsstromes, der aus dem Speicher geladen wird. Das zugeordnete Segment ist also das Codesegment CS und nicht das Datensegment DS oder ein Extrasegment.

  Registeroperand: MOV eax, ebx Das Register eax wird mit dem Wert im Register ebx geladen. Auch im obigen Beispiel war eax ein Registeroperand (nämlich Ziel- oder Destination-Operand). Hier ist nun auch der Quellen- oder Source-Operand ein Register (nämlich ebx).   Speicheroperand: MOV eax, mem32 Anstelle von mem32 muß in diesem Fall die effektive Adresse des symbolischen Operanden stehen, der in den Akkumulator übertragen werden soll. Ist die effektive Adresse fest, d.h.

bereits zur Assemblierzeit bekannt, wird sie bereits vom Assembler berechnet. mem32 ist in diesem Fall ein direkter Speicheroperand. Weist die effektive Adresse einen veränderlichen Anteil (meist ein Register) auf, so berechnet die CPU zur Laufzeit die effektive Adresse. In diesem Fall stellt mem32 einen indirekten Speicheroperanden dar.   Als effektive Adresse bezeichnet man den Offset des Operanden innerhalb des ausgewählten Segments (hier: DS). Die effektive Adresse setzt sich aus bis zu vier Elementen zusammen.

  Displacement: MOV eax, array[0] Im assemblierten Programm steht anstelle der symbolischen Adresse array[0] eine Zahl. Befindet sich array[0] beispielsweise an der Stelle 0f2h, so lautet der Befehl in diesem Fall MOV eax,[0f2h]. Diese Art der Adressierung darf nicht mit einem unmittelbaren Operanden verwechselt werden: Bei einem Displacement wird der Wert an der angegebenen Adresse und nicht der Wert selbst angesprochen. In diesem Beispiel also der Wert am Offset 0f2h im Segment DS und nicht der Wert 0f2h selbst.   Basisregister: MOV eax, [ebx] Der Befehl lädt den Operanden im Segment DS bei dem Offset, den der Wert im Register ebx angibt, in den Akkumulator. Weist ebx beispielsweise den Wert 0f2h auf, so ist dieser Befehl äquivalent zu MOV eax,[0f2h].

Der Unterschied besteht nur darin, daß der Wert in ebx zur Laufzeit dynamisch verändert werden kann, array[0] jedoch einen während des Programmablaufs stets festen und konstanten Wert darstellt.   Indexregister: MOV eax,[esi] In dieser Grundform ist die Verwendung eines Indexregisters identisch mit der Benutzung des Basisregisters. Zusätzlich besteht aber noch die Möglichkeit, einen Skalierungsfaktor anzugeben. Als Indexregister sind esi und edi gültig.   Skalierungsfaktor: MOV eax, [esi*4] Um die effektive Adresse zu berechnen, wird beim angegebenen Beispiel der Wert des Indexregisters mit 4 multipliziert. Hierdurch können Felder angesprochen werden, deren Inhalt vier Byte lang ist.

Für den Skalierungsfaktor können die Faktoren 1, 2, 4 oder 8 verwendet werden, die Skalierung (Multiplikation) selbst erfolgt ohne Zeitverlust.   Displacement, Basisregister, Indexregister und Skalierungsfaktor können in beliebiger Kombination angewandt werden. Damit können sehr ausgeklügelte und mehrdimensionale Felder definiert werden.   Beispiel: Gegeben sei ein Feld mit 10 Elementen, das 10 verschiedene Körper bezeichnet. Jedes Element besitzt den Aufbau Höhe:Breite:Tiefe:Querschnitt. Die Teilelemente Höhe, etc.

umfassen jeweils 1 Byte. Das Feld beginnt bei 0c224h. Durch die folgenden Zeilen kann die Tiefe der Elemente in den Akkumulator eax geladen werden. MOV ebx, 0c224h ;Laden der Basisadresse in ebx MOV esi, <nr> ;Laden der Elementnummer in esi MOV eax, [ebx+esi*4+2] ;Tiefe (Displacement 2 gegenüber dem Beginn des Elements) des Elements <nr> ;(Elementgröße = 4 Byte, daher Skalierung 4) des Feldes (beginnend bei der Basisadresse in ;ebx) in den Akkumulator eax laden.   Interrupts und Exceptions im Real Mode   Daß der Prozessor unentwegt Befehle ausführt ist wohl unschwer zu erkennen. Auch, wenn er scheinbar darauf wartet, daß ein Befehl (z.

B. dir) eingegeben wird, bedeutet das nicht, daß er wirklich angehalten wird. Vielmehr läuft im Hintergrund eine Routine ab, die überprüft, ob bereits Zeichen eingegeben worden sind. Nur bei Computern mit Power-Management wird die CPU wirklich stillgelegt, um beim ersten Tastendruck sofort wieder aktiviert zu werden.   Um den Prozessor bei der kontinuierlichen Abarbeitung dieser Befehle gezielt zu unterbrechen, wird ein sogenannter Interrupt ausgelöst. Beispielsweise wird ein periodischer Interrupt, der Timer-Interrupt, dazu benutzt, regelmäßig das Hintergrundprogramm PRINT für eine kurze Zeit zu aktivieren.

Für den i386 stehen insgesamt 256 verschiedene Interrupts (0 bis 255) zur Verfügung. INTEL hat die ersten 32 Interrupts für eine Verwendung durch den Prozessor reserviert, was IBM allerdings nicht daran gehindert hat, alle Hardware-Interrupts und die Interrupts des PC-BIOS in genau diesen Bereich zu legen. Zwingende Gründe dafür kennt wohl kein Mensch.   In Abhängigkeit von der Quelle einer Unterbrechung (dt. für Interrupt) unterscheidet man drei Kategorien von Interrupts:   Software-Interrupts Hardware-Interrupts Exceptions (Ausnahmen)     Software-Interrupts   Ein Software-Interrupt wird gezielt durch einen INT-Befehl ausgelöst, z.B.

ruft der Befehl INT 10h den Interrupt mit der Nummer 10h auf.   Im Real-Mode-Adreßraum des i386 sind die ersten 1024(1k) Byte für die Interrupt-Vector-Table reserviert. Diese Tabelle weist für jeden der 256 möglichen Interrupts einen sogenannten Interrupt-Vektor auf. Beim 8086 war die Lage dieser Tabelle fest von der Hardware vorgegeben. Der i386 verwaltet sie selbst im Real Mode etwas flexibler. Von allen Speicherverwaltungsregistern besitzt das Interrupt-Descriptor-Table-Register IDTR bereits im Real Mode eine Bedeutung.

Es speichert nämlich die Basisadresse und das Limit der Real-Mode-Deskriptortabelle. Nach einem Prozessor-Reset wird das IDTR standardmäßig mit den Werten 0h für die Basis, 03ffh für das Limit geladen. Das entspricht genau einer 1-kByte-Tabelle im Segment 0000h, Offset 0000h. Durch die beiden i386-Befehle LIDT (Load IDT) und SIDT (Store IDT) können die beiden Werte aber verändert und die Tabelle dadurch im Real-Mode-Adreßraum verschoben und deren Größe verändert werden. Es ist aber darauf zu achten, daß die Tabelle auch alle Vektoren für die möglicherweise auftretenden Interrupts aufnehmen kann. Sonst ist eine Exception 8 die Folge.

    Jeder Interrupt-Vektor ist vier Bytes lang und gibt im INTEL-Format die Adresse Segment:Offset des Interrupt-Handlers an, der den Interrupt bedient. Da ein Interrupt meist eine bestimmte Ursache hat, wie die Anforderung einer Betriebssystemfunktion, der Empfang eines Zeichens über die serielle Schnittstelle etc., behandelt der Interrupt-Handler diese Ursache in geeigneter Weise. Er führt also beispielsweise die Betriebssystemfunktion aus oder nimmt das empfangene Zeichen entgegen. Durch Ersetzen des Handlers kann einem Interrupt auf einfache Weise eine andere Funktion zugewiesen werden. Die Zuordnung von Interrupt und Interrupt-Vektor verläuft auf einer 1:1 Basis, d.

h. dem Interrupt 0 wird der Interrupt-Vektor 0 (an der Adresse 0000:0000), dem Interrupt 1 der Vektor 1 (an der Adresse 0000:0004) usw. zugeordnet. Damit muß nur die Nummer des Interrupts mit 4 multipliziert werden, um den Offset des Interrupt-Vektors im Segment 0000h zu erhalten. Der Prozessor tut genau dies. Ein Überschreiben der Interrupt-Vector-Table mit ungültigen Werten hat katastrophale Folgen.

Beim nächsten Interrupt stürzt der Rechner ab.   Beim Aufruf eines Interrupts läuft nun folgende Prozedur ab, die der i386 automatisch und ohne weiteres Eingreifen eines Programmes ausführt:   Der i386 schiebt die EFlags, CS und EIP - in dieser Reihenfolge – auf den Stack (im 16-Bit Modus natürlich nur Flags, CS und IP),   das Interrupt- und das Trap-Flag werden gelöscht,   der i386 adressiert den Interrupt-Vektor in der Interrupt-Vector-Table entsprechend der Nummer des Interrupts und lädt EIP (oder IP im 16-Bit Modus) sowie CS aus der Tabelle.   Beispiel: INT 10h Der Prozessor schiebt die aktuellen Flags, CS und IP auf den Stack, löscht das Interrupt- und Trap-Flag und liest den Interrupt-Vektor an der Adresse 0000:0040h. Die zwei Byte bei 0000:0040 werden in IP, die beiden Byte bei 0000:0042 in CS geladen.   Für alle Interruptbefehle ist nur eine unmittelbare Adressierung möglich, d.h.

die Nummer des Interrupt-Vektors ist Teil des Befehlsstromes. Damit kann die Nummer des Interrupts nicht in einem Register oder Speicheroperanden bereitgehalten werden. Software-Interrupts treten synchron zur Befehlsausführung auf, d.h. jedesmal, wenn das Programm den Punkt mit dem INT-Befehl erreicht, wird ein Interrupt ausgelöst. Das unterscheidet sie wesentlich von den Hardware-Interrupts und Exceptions.

  Hardware-Interrupts   Wie schon der Name sagt, werden diese Unterbrechungen durch einen Hardware-Baustein (beim Timer-Interrupt z.B. durch den Timer-Baustein) oder ein Peripheriegerät wie beispielsweise die Festplatte ausgelöst. Man unterscheidet zwei grundsätzlich verschiedene Arten von Hardware-Interrupts: den nicht-maskierbaren Interrupt NMI sowie die (maskierbaren) Interruptanforderungen IRQ (Interrupt Request). Für die Bedienung eines solchen IRQ spielt der Interruptcontroller 8259 eine große Rolle. Er verwaltet mehrere Interrupt-Anforderungen und gibt sie geordnet nach ihrer Priorität gezielt an den Prozessor weiter.

  Löst der Computer einen NMI aus, so arbeitet der i386 den gerade ausgeführten Befehl ab und führt unmittelbar anschließend in gleicher Weise wie oben einen Interrupt 2 aus. Beim PC wird ein NMI ausgelöst, wenn beim Lesen von Daten aus dem Speicher ein Paritätsfehler oder ein anderes ernstes Hardware-Problem wie z.B. eine fehlerhafte Busarbitrierung, auftritt. Der Computer meldet sich bei einem Paritätsfehler mit der folgenden Nachricht:   Paritätsfehler bei xxxx:xxxx   xxxx:xxxx gibt das Byte mit dem Paritätsfehler an. Die Besonderheit des NMI ist, daß er (wie bereits der Name sagt) nicht unterdrückt werden kann.

Ein NMI drängelt sich immer nach vorne. Da er normalerweise nur bei einer wirklich schwerwiegenden Fehlfunktion der Hardware ausgelöst wird, ist dies aber verständlich und auch richtig: Ein PC mit unzuverlässigem Speicherinhalt muß am Datenselbstmord gehindert werden.   Demgegenüber können die Interrupt-Anforderungen IRQ maskiert werden, indem man mit CLI (Clear Interrupt Flag) das Interrupt Flag IE löscht. Alle Interrupt-Anforderungen über den Anschluß INTR des i386 werden dann ignoriert. Erst durch den umgekehrten Befehl STI (Set Interrupt Flag) werden solche Interrupts wieder zugelassen. Zu beachten ist, daß der Befehl INT xx implizit ein CLI ausführt.

Nach einem INT müssen also Interrupt-Anforderungen mit einem STI explizit wieder zugelassen werden, sonst wird der Computer taub. IRQs werden üblicherweise durch ein Peripheriegerät ausgelöst, beispielsweise die serielle Schnittstelle oder den Drucker.   Hardware-Interrupts (NMI und IRQ) sind im Gegensatz zu Software-Interrupts völlig asynchron. Es tritt ja z.B. nicht immer an der gleichen Programmstelle ein Speicherparitätsfehler auf.

Abgesehen davon benötigt die Festplatte in Abhängigkeit von der Ausgangsstellung der Festplattenköpfe eine unterschiedliche Zeitspanne, bis die Daten zum Programm übertragen werden können. Dies macht die Erfassung von Programmfehlern sehr schwierig, wenn sie nur im Zusammenhang mit Hardware-Interrupts auftreten.     Exceptions   Neben den beiden obigen bildet der Prozessor selbst eine Quelle für Interrupts. Solche vom Prozessor erzeugten Ausnahmen nennt man Exceptions. Die Auswirkungen einer Exception entsprechen dabei einem Software-Interrupt, d.h.

es wird ein Interrupt aufgerufen, dessen Nummer in diesem Fall vom Prozessor selbst angegeben wird. Ursache für eine Exception ist im allgemeinen eine prozessorinterne Fehlerbedingung, die den Eingriff von System-Software erfordert und vom i386 nicht mehr alleine behandelt werden kann.  Exceptions werden in drei Klassen eingeteilt: Faults, Traps und Aborts. Im folgenden kurz die Kennzeichen der drei Klassen: Fault: Ein Fault löst eine Exception aus, bevor der Befehl vollständig abgearbeitet wird. Der gesicherte EIP-Wert zeigt damit auf den Befehl, der die Exception ausgelöst hat. Durch laden des gesicherten EIP-Wertes kann der i386 den Befehl nochmals ausführen, hoffentlich ohne erneut eine Exception auszulösen.

Ein Beispiel für einen Fault wäre die Exception “Segment nicht vorhanden”. Der i386 hat dann die Möglichkeit, das Segment in den Speicher zu laden und einen erneuten Zugriffsversuch zu wagen.   Trap: Ein Trap löst dagegen eine Exception aus, nachdem der Befehl ausgeführt worden ist. Der gesicherte EIP-Wert zeigt also auf den Befehl unmittelbar nach dem, der die Exception ausgelöst hat. Der Befehl wird also nicht nochmals ausgeführt. Traps sind günstig, wenn der Befehl zwar korrekt ausgeführt worden ist, aber das Programm trotzdem unterbrochen werden soll.

Das gilt z.B. für die Haltepunkte eines Debuggers; ein nochmaliges Ausführen des entsprechenden Befehles würde zu einem fehlerhaften Befehlsfluß führen.   Abort: Ein Abort liefert im Gegensatz zu Faults und Traps nicht immer die Adresse des Fehlers. Dadurch kann nach einem Abort die Programmausführung manchmal nicht wieder aufgenommen werden. Aborts werden nur dazu benutzt, äußerst schwerwiegende Fehlfunktionen anzuzeigen, wie z.

B. Hardware-Ausfälle oder ungültige Systemtabellen.   Ein großer Teil der Exceptions ist für den Betrieb des i386 im Protected Mode vorgesehen. Im Real Mode sind nur folgende von Bedeutung: Division durch 0 (Exception 0): Ist bei einem Befehl DIV oder IDIV der Divisor gleich Null, dann ist das Ergebnis der Operation nicht einmal mathematisch definiert, geschweige denn im i386. Die ALU würde für die Berechnung eines solchen Quotienten unendlich lange brauchen. Ist eine Division nicht nach einer bestimmten Zahl von Taktzyklen beendet, ermittelt die Steuereinheit eine Division durch Null und löst eine Exception 0 aus.

Einzelschritt (Exception 1): Ist das Trap-Flag gesetzt worden, so löst der i386 nach jedem einzelnen ausgeführten Befehl eine Exception 1 aus. Da das Trap-Flag beim Aufruf eines Interrupts automatisch gelöscht wird, kann der Prozessor die Interrupt-Routine ohne Unterbrechung durchführen. Häufig setzen Debugger das Trap-Flag und fangen den Interrupt ab, um ein Programm schrittweise auszuführen. Breakpoint (Exception 3): Unter bestimmten Umständen (in den Debug-Registern festlegbar) löst der i386 bei Schreib- oder Leseversuchen auf bestimmte Speicherstellen eine Exception 3 aus. Überlauferfassung mit INTO (Exception 4): Ist das Overflow-Flag gesetzt und wird der Befehl INTO ausgeführt, so erzeugt der Prozessor eine Exception 4. BOUND (Exception 5): Liegt der mit dem Befehl BOUND geprüfte Index in ein Feld außerhalb der Grenzen des Feldes, so erzeugt der i386 eine Exception 5.

Ungültiger Opcode (Exception 6): Stößt die Befehlseinheit auf einen Opcode, dem kein Befehl zugeordnet ist, so löst der Prozessor eine Exception 6 aus. Die Ursache dafür ist meist ein fehlerhafter Assembler oder Compiler, oder ein Programmfehler, der zu einem Sprung an eine Stelle führt, an der eigentlich kein Opcode, sondern z.B. ein Datenbyte steht. Kein Coprozessor vorhanden (Exception 7): Erfaßt die Befehlseinheit einen Opcode, der einen Befehl für den Coprozessor angibt, und ist gar kein Coprozessor installiert, so löst der i386 eine Exception 7 aus. IDT-Limit zu klein (Exception 8): Ist das Limit der IDT zu klein, um den Vektor des ausgelösten Interrupts aufzunehmen, dann führt das zu einer Exception 8.

Das ist meist der Fall, wenn ein Programm die IDT in unkorrekter Weise verändert hat. Stack-Exception (Exception 12): Der Stapelzeiger ESP liefert einen größeren Wert als 0ffffh für den Stack-Zugriff. Das kann vorkommen, weil ESP ein 32-Bit-Register darstellt und sein Wert nicht auf unter 1mByte beschränkt ist. Eine Exception 12 ist die Folge, um die Erzeugung von linearen Adressen über 10ffefh im Real Mode zu unterbinden. Allgemeiner Protection-Fehler (Exception 13): Ein 32-Bit-Offset liefert eine größere Adresse als 0ffffh für einen Speicherzugriff. Wie für den Stapelzeiger wird dadurch die Erzeugung von linearen Adressen größer als 10ffefh im Real Mode unterbunden.

Coprozessor-Fehler (Exception 16): Im Coprozessor ist ein Fehler aufgetreten. Der Protected Mode       Der Protected Mode des i386   Der Protected Virtual Address Mode oder kurz Protected Mode wurde, beginnend mit dem 80286, implementiert, um (wie der Name schon sagt) die verschiedenen Tasks unter einem Multitasking-Betriebssystem zu schützen. Zu diesem Zweck prüft die i386-Hardware den Zugriff auf insgesamt vier Schutzebenen. Damit sind Daten und Code geschützt und ein kompletter Systemabsturz ist normalerweise – außer bei unsauberer Programmierung - nicht möglich.   Die Zugriffsprüfungen im Protected Mode dienen vor allem zur Hardwareunterstützung eines Multitasking-Betriebssystems; typische Vertreter sind Linux, Windows NT oder UNIX. Unter einem Multitasking-Betriebssystem laufen mehrere Programme (Tasks) scheinbar parallel ab.

Doch genau genommen werden die einzelnen Tasks vom Betriebssystem in kurzen Zeitabständen aktiviert, laufen eine kurze Zeit lang ab und werden dann vom Betriebssystem wieder unterbrochen, damit der nächste Task aktiviert werden kann. Im Gegensatz zu TSR(Terminate and Stay Residental)-Programmen unter MS-DOS werden die Programme also gezielt vom Betriebssystem aufgerufen. TSR-Programme hingegen aktivieren sich selbst, indem sie beispielsweise den periodischen Timer-Interrupt abfangen. In einem Multitasking-Betriebssystem wird also häufig in kurzer Zeit zwischen den einzelne

Suchen artikel im kategorien
Schlüsselwort
  
Kategorien
  
  
   Zusammenfassung Der Vorleser

   sachtextanalyse

   interpretation zwist

   Fabel interpretation

   literarische charakteristik

   interpretation bender heimkehr

   felix lateinbuch

   interpretation der taucher von schiller

   textbeschreibung

   charakterisierung eduard selicke
Anmerkungen:

* Name:

* Email:

URL:


* Diskussion: (NO HTML)




| impressum | datenschutz

© Copyright Artikelpedia.com