Die Bedeutung des Protected Mode und seine Funktio

 

Inhaltsverzeichnis




I. 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, dass 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.

II. Der Real Mode[1]


1. Einf├╝hrung in die Intel Architecture[2]



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.




1.1. 8086/8088[3]


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.


1.2. 80286[4]


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.


1.3. i386[5]





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)


1.4. i486[6]


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.


1.5. Pentium[7]


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

2. Der allgemeine Aufbau des i386


2.1. 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.

2.1.1. 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, dass 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.


2.1.2. Die Vielzweck - und Segmentregister[8]





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 EAX
Der 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, dass 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 EBX
Das 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 ECX
Das 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 EDX
Das 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 EBP
Auch 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 EDI
Der 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 CS
Das 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 DS
Das 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 SS
Das 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, GS
Diese 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.

2.1.3. Die Flags[9]





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, dass 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, dass die Programmausf├╝hrung an der Unterbrechungsstelle wiederaufgenommen werden kann, ohne dass eine erneute Debug - Exception auftritt.

Virtual 8086 Mode (virtueller 8086 - Modus; ab i386)
Um den i386 in den Virtual 8086 Mode umzuschalten muss 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.


2.1.4. Steuer - und Speicherverwaltungsregister[10]


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, dass 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, dass 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 muss 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


2.2. Logische Speicheradressierung und logischer Speicherzugriff des i386[11]


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).

2.2.1. Codesegment und Befehlsz├Ąhler[12]


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 muss, 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 muss 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 ein Zwei - Byte - Befehl war. Der Wert von EIP lautet somit 10a0, der Wert des Codesegments bleibt unver├Ąndert.




2.2.2. Stacksegment und Stackzeiger[13]


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 dass ein solcher Stack - Overflow nur bei einem Programmfehler oder einer fehlerhaften Programmbedienung auftreten sollte. Nachteilig ist, dass im Real Mode das Anwendungsprogramm vor jedem PUSH explizit pr├╝fen muss, 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 dass zus├Ątzliche Software - Routinen notwendig sind.

2.2.3. 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, muss 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.

2.3. Adressierungsarten



2.3.1. 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.

2.3.2. Adressierungsarten[14]


Soll ein Register (hier beispielsweise der Akkumulator eax) ├╝ber MOV eax, 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 muss 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, dass 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, ;Laden der Elementnummer in esi
MOV eax, [ebx+esi*4+2] ;Tiefe (Displacement 2 gegen├╝ber dem Beginn des Elements) des Elements ;(Elementgr├Â├če = 4 Byte, daher Skalierung 4) des Feldes (beginnend bei der Basisadresse in ;ebx) in den Akkumulator eax laden.

2.4. Interrupts und Exceptions im Real Mode[15]


Dass der Prozessor unentwegt Befehle ausf├╝hrt ist wohl unschwer zu erkennen. Auch, wenn er scheinbar darauf wartet, dass ein Befehl (z.B. dir) eingegeben wird, bedeutet das nicht, dass 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)


2.4.1. 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, dass 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 muss 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.

2.4.2. 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, dass 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 muss 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, dass 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.


2.4.3. 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.

III. Der Protected Mode[16]




3. 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 einzelnen Tasks umgeschaltet, d.h. es wird ein Task - Switch ausgef├╝hrt, so dass der Benutzer den Eindruck hat, die Programme w├╝rden parallel arbeiten. Durch die Hardware - Unterst├╝tzung des i386 im Protected Mode k├Ânnen diese Task - Switches schnell und effektiv ausgef├╝hrt werden.

Ein weiterer wesentlicher Unterschied des Protected Mode gegen├╝ber dem Real Mode ist die v├Âllig andersartige Berechnung der linearen Adresse eines Speicherobjekts.

3.1. Segmentselektoren, Segmentdeskriptoren und Privilegierungsstufen[17]


Wie schon beschrieben, multipliziert die Adressierungseinheit des i386 im Real Mode den Wert des Segmentregisters einfach mit 16, um die lineare Basisadresse des betreffenden Segments zu ermitteln. Die Adresse innerhalb eines solchen 64kByte gro├čen Segments wird dann durch den Offset in einem Vielzweckregister angegeben. Im Protected Mode werden die Werte der Segmentregister v├Âllig anders interpretiert, sie stellen keine Basisadressen, sondern sogenannte Selektoren dar.



Der Selektor ist wie im Real Mode 16 Bit lang und wird in einem 16 - Bit - Segmentregister gespeichert. Die beiden niederwertigsten Bits 0 und 1 geben die Selektor - oder geforderte Privilegierungsstufe (RPL: Requestor Privilege Level) an, ab der ein Programm auf das Segment zugreifen darf. Der Wert des Feldes RPL im Segmentselektor des Code - Segments CS wird als gegenw├Ąrtige Privilegierungsstufe (CPL: Current Privilege Level) bezeichnet, da dem gegenw├Ąrtig aktiven Programmcode diese Privilegierungsstufe zugewiesen ist. Das aktive Programm kann also auf Datensegmente zugreifen, deren Privilegierungsstufe gleich oder gr├Â├čer als CPL ist. Dabei kann der Wert von RPL gr├Â├čer als CPL sein, d.h. auf das vom Selektor festgelegte Segment wird mit einer geringeren Privilegierungsstufe zugegriffen. Der gr├Â├čere der beiden Werte CPL und RPL definiert die effektive Privilegierungsstufe (EPL) des Tasks. Der i386 kennt insgesamt vier Privilegierungsstufen (PL) 0 bis 3 - 0 kennzeichnet die h├Âchste, 3 die niedrigste Stufe. Ein gr├Â├čerer Wert gibt also eine niedrigere, ein kleinerer eine h├Âhere Privilegierungsstufe an. Ein RPL mit dem Wert 0 schr├Ąnkt also die Privilegierungsstufe eines Tasks nicht ein, wohingegen ein Selektor mit einem RPL von 3 unabh├Ąngig vom CPL nur Segmente ansprechen kann, die eine Privilegierungsstufe von 3 aufweisen.

Programme mit niedrigerer Privilegierungsstufe (h├Âherer CPL) d├╝rfen nur in Ausnahmef├Ąllen auf Segmente mit h├Âherer Stufe (kleinerer CPL) zugreifen. Hierzu dienen sogenannte Gates (Tore). Damit wird der Schutz innerhalb eines Tasks durch die verschiedenen Privilegierungsstufen vermittelt.

Die h├Âchste Privilegierungsstufe 0 besitzt ├╝blicherweise der kritische Teil oder Kernel des Betriebssystems. Dieser umfa├čt zumeist die Routinen zur Speicherverwaltung, zum Ausf├╝hren von Task - Switches, zu Behandlung kritischer Fehler, usw. Viele Befehle, die den Status des Prozessors oder Computers direkt betreffen, wie beispielsweise LGDT (Load Global Descriptor Table) oder LMSW (Load Machine Status Word) k├Ânnen im Protected Mode nur von einem Programm ausgef├╝hrt werden, das die Privilegierungsstufe 0 aufweist. Damit soll verhindert werden, dass Anwendungsprogramme durch einen Programmierfehler das Betriebssystem zerst├Âren oder Hacker unkontrolliert Zugriff auf Daten bekommen.

Betriebssysteme verwalten aber den Computer nicht nur, sondern stellen auch bestimmte Funktionen zur Datenverwaltung, Zeichenausgabe etc. zur Verf├╝gung. In einem PC unter DOS geschieht dies beispielsweise durch den Interrupt 21h, dem DOS - Funktionsverteiler. Solche Betriebssystemfunktionen laufen meist mit PL=1. Auch Einheiten - und Ger├Ątetreiber arbeiten h├Ąufig auf dieser Stufe. Weniger kritische Betriebssystemfunktionen, z.B. Unterst├╝tzungen f├╝r eine graphische Benutzeroberfl├Ąche (API), k├Ânnen dagegen Stufe 2 aufweisen.

Die niedrigste Privilegierungsstufe aller Tasks besitzen Anwendungsprogramme, da diese den Computer nur benutzen, aber nicht steuern oder kontrollieren sollen. Durch die niedrige Stufe (hohe PL) sind die Daten und Codes anderer Programme und des Betriebssystems sehr gut gegen Programmfehler gesch├╝tzt. Unter DOS f├╝hrt beispielsweise ein Programmierfehler, der versehentlich die Interrupt - Vektortabelle ├╝berschreibt, zu einem v├Âlligen Systemabsturz. Im Protected Mode reagiert das Betriebssystem auf einen solchen Vorgang unter Ausgabe einer Fehlermeldung nur mit dem Abbruch des fehlerhaften Programms, alle anderen Tasks laufen unbesch├Ądigt und unbeeinflu├čt weiter. Damit k├Ânnen Bugs, d.h. Programmfehler, besser entdeckt werden. Dies alles setzt nat├╝rlich voraus, dass das Betriebssystem fehlerfrei ist, was wegen der Komplexit├Ąt von Multitasking - Betriebssystemen leider keine Selbstverst├Ąndlichkeit darstellt.




Der i386 sieht f├╝r jede Privilegierungsstufe PL=0 bis PL=3 eines Tasks einen eigenen Stack vor. Beispielsweise w├Ąre bei der Ausf├╝hrung von Word ein Stack f├╝r das Programm Word (PL=3), einer f├╝r die Funktionen der Benutzeroberfl├Ąche (PL=2), ein weiterer f├╝r die Betriebssystemfunktionen zur Dateiverwaltung (PL=1) und ein letzter f├╝r den Kernel (PL=0) vorhanden.

F├╝r einen kontrollierten Zugriff von Programmen auf Daten und Code in Segmenten h├Âherer Privilegierungsstufe stehen Gates zur Verf├╝gung. Diese geben einen maximalen Schutz vor einem unberechtigten oder fehlerhaften Zugriff auf fremde Daten und Programme. Wenn z.B. ein Anwendungsprogramm Betriebssystemfunktionen in Anspruch nimmt, um Dateien zu er├Âffnen oder zu schlie├čen - es also auf fremde Funktionen zugreift - , garantieren die Gates, dass der Zugriff fehlerfrei l├Ąuft. W├╝rde das Anwendungsprogramm versuchen, die Funktionen mit einer falschen Einsprungadresse aufzurufen, so w├Ąre ein unvorhersehbares Verhalten des Computers wahrscheinlich. Die Gates definieren daher "Tore", durch die das Anwendungsprogramm Zugriff auf fremde Routinen hat.

Das Bit 2 im Segmentselektor gibt als sogenannter Tabellenindikator (TI) an, ob die globale (TI=0) oder lokale (TI=1) Deskriptortabelle f├╝r die Lokalisierung des Segments im Speicher benutzt werden soll. Diese beiden Tabellen sind wesentlich f├╝r die Segmentierung des Speichers im Protected Mode. Der i386 verwendet im Protected Mode die Segmentselektoren in den Segmentregistern n├Ąmlich als Index in die globale oder lokale Deskriptortabelle.

Die globale Deskriptortabelle ist eine Liste im Speicher, die in Form von Segmentdeskriptoren Gr├Â├če und Adresse von Segmenten im Speicher beschreibt. Jeder Deskriptor umfa├čt acht Byte. Diese Deskriptortabelle wird als global bezeichnet, weil sie Segmente beschreibt, die ├╝blicherweise allen Tasks zur Verf├╝gung stehen (wenn deren Privilegierungsstufe oder entsprechende Gates den Zugriff gestatten). Auch die lokale Deskriptortabelle ist eine Liste mit gleichartig aufgebauten Segmentdeskriptoren. Im Gegensatz zur globalen Deskriptortabelle steht die lokale aber normalerweise nur dem gerade aktiven Task zur Verf├╝gung, d.h. bei einem Task - Switch wird auch die lokale Deskriptortabelle gewechselt.

Die 32 Basisbits des Segmentdeskriptors geben die Startadresse des beschriebenen Segments im Speicher an. Diese 32 Bits der Basisadresse entsprechen den 32 Adre├čleitungen des i386. Damit k├Ânnen im Protected Mode 232Byte oder 4gByte adressiert werden. Der Basiseintrag eines Segmentdeskriptors gibt die Startadresse des betreffenden Segments in diesem doch recht gro├čen Adre├čraum an.

Im Real Mode ist jedes Segment 64kByte gro├č, selbst, wenn nur wenige Daten in einem Segment gespeichert werden. Durch die festgelegte Verzahnung der Segmente bleiben aber h├Âchstens 15 Byte unbenutzt, da die Segmente mit 16 Byte Abstand aufeinander folgen. Die v├Âllige Entkoppelung der Segmente im Protected Mode ├Ąndert das vollkommen, weil nun entsprechend dem Selektorwert im Segmentregister aufeinanderfolgende Segmente in keiner Weise auch physikalisch im Speicher aufeinanderfolgen m├╝ssen. Jeder Segmentdeskriptor weist daher einen Eintrag Limit auf, um die tats├Ąchliche Gr├Â├če des Segments in Byte zu definieren. Bei gel├Âschtem G - Bit (Page - Granularit├Ąt [=K├Ârnigkeit]) sind durch die 20 Bits des Limiteintrags im Protected Mode also Segmente mit einer maximalen Gr├Â├če von 220*1Byte (=1mByte) m├Âglich, bei gesetztem G - Bit hingegen Segmente mit einer Gr├Â├če von bis zu 220*4kByte (=4gByte).

Das tats├Ąchliche Segmentlimit (oder gleichbedeutend die Gr├Â├če) des vom Deskriptor beschriebenen Segments h├Ąngt also neben dem 20 - Bit - Limit noch vom Granularity - Bit G ab. Zu beachten ist, dass beim i386 eine Page 4kByte (=4,096 Byte) gro├č ist.

Beispiel

Limit=1000, G=0: Segmentlimit 1000 Byte
Limit=1000, G=1: Segmentlimit 1000*4kByte=4096000 Byte

Der volle Adressierungsbereich der 32 - Bit - Offset - Register kommt daher erst bei Page - K├Ârnigkeit zum Tragen. In diesem Fall ist die kleinste Zuweisungseinheit f├╝r ein Segment aber bereits 4kByte gro├č, d.h. der Speicher wird in Portionen zu 4kByte in Segmente eingeteilt. Das kommt nur f├╝r gr├Â├čere Systeme in Betracht, f├╝r PC - Anwender sind Segmente mit einer Gr├Â├če von 1mByte meist ausreichen, der Speicherplatz wird dann besser ausgenutzt. OS/2 und auch Windows NT verwenden aber ein sogenanntes flaches Speichermodell (flat memory), bei dem ein einziges 4gByte gro├čes Segment alle Tasks aufnimmt, d.h. die Segmentierung spielt hier fast keine Rolle mehr. Statt dessen wird eine Speicheraufteilung in Pages vorgenommen, f├╝r die weitere Schutzmechanismen existieren. [im Rahmen dieser Arbeit werden die Paging - Mechanismen jedoch nicht dargestellt]



Das Bit S im Segmentdeskriptor gibt an, um welchen Deskriptortyp es sich handelt. Ist S gleich 0, so beschreibt der Deskriptor ein Systemsegment, ansonsten ein Applikations - segment. Das Feld Type im Segmentdeskriptor gibt die Art und DPL (Descriptor PL) die Privilegierungsstufe (von 0 bis 3) des Segments an. Schlie├člich ist das Bit P (Present) ein Indikator daf├╝r, ob sich das Segment tats├Ąchlich im Speicher befindet (oder auf Festplatte ausgelagert ist etc.).

Das Bit AVL im Segmentdeskriptor kann der Anwender oder das Betriebssystem frei benutzen, der i386 verwendet es nicht, und Intel hat es auch nicht f├╝r k├╝nftige Verwendung reserviert.

Das Bit DB (Default/Big) gibt an, ob der i386 f├╝r das vom Deskriptor beschriebene Segment standardm├Ą├čig 16 - oder 32 - Bit - Operanden im Fall eines Datensegments bzw. 16 - oder 32 - Bit - Adressen f├╝r ein Codesegment verwenden soll. Ist DB=0, so benutzt der i386 16 - Bit - Operanden oder - Adressen. Dieses Bit dient vor allem der Emulation von 16 - Bit - Programmen, die f├╝r den 80286 geschrieben worden sind, auf einem i386.


3.2. Globale und lokale Deskriptortabelle[18]



F├╝r die Verwaltung der lokalen und globalen Deskriptortabelle sowie der weiter unten beschriebenen Interrupt - Deskriptortabelle und der Tasks implementiert der i386 f├╝nf Register. Das sind das Steuerregister CR0 und die vier Speicherverwaltungs - oder Taskregister (TR), sowie die Register f├╝r die lokale Deskriptortabelle (LDTR), die Interrupt - Deskriptortabelle (IDTR) und die globale Deskriptortabelle(GDTR).




Der Index, d. h. die h├Âherwertigen 13 Bits des Segmentselektors geben nun die Nummer des Segmentdeskriptors in der Deskriptortabelle an, der das zugeh├Ârige Segment beschreibt. Mit 13 Bits sind maximal 8192 verschiedene Indices m├Âglich, so dass die globale und lokale Deskriptortabelle jeweils maximal 8192 Eintr├Ąge zu 8 Byte oder insgesamt 64kByte umfassen k├Ânnen. Die Tabellen beschreiben damit jeweils bis zu 8192 verschiedene Segmente. Aufbau und Gr├Â├če der Segmentdeskriptoren f├╝r die lokale Deskriptortabelle (LDT) und die globale Deskriptortabelle (GDT) stimmen ├╝berein. Ob sich der Segmentselektor in einem Segmentregister auf die GDT oder die LDT bezieht, gibt ja der Tabellenindikator TI im Selektor an. M├Âchte der i386 auf einen Eintrag in der GDT oder LDT zugreifen, multipliziert er den Indexwert des Selektors mit 8 (Anzahl der Byte pro Deskriptor) und addiert das Ergebnis zur Basisadresse der entsprechenden Deskriptortabelle.
Das niederwertige Wort des Steuerregisters CR0 ist bereits beim 80286 als Maschinenstatuswort (MSW) vorhanden und kann beim i386 aus Kompatibilit├Ątsgr├╝nden auch so adressiert werden.

Von besonderer Bedeutung f├╝r den Protected Mode ist das Bit PE (Protection Enable). Wenn es auf 1 gesetzt wird, schaltet der i386 sofort in den Protected Mode um. Gel├Âscht wird es entweder explizit durch den Befehl MOV CR0,xxx....xxx0b, einen Prozessor - Reset oder einen Triple - Fault des i386.

Die Basisadresse der globalen und lokalen Deskriptortabelle ist im GDT - bzw. LDT - Register abgelegt. Diese Register werden von der Laderoutine des Betriebssystems mit den entsprechenden Werten geladen. Im Gegensatz zur LDT darf beim Aufbau der GDT der nullte Eintrag nicht benutzt werden. Ein Bezug auf den nullten Eintrag f├╝hrt sofort zu der Exception "allgemeiner Protection - Fehler". Dadurch wird verhindert, dass ein noch nicht initialisiertes GDTR benutzt wird.

Wesentlich an der GDT ist, dass die gesamte Segment - und damit Speicherverwaltung aus ihr aufgebaut wird. Im GDTR sind sowohl die Basisadresse als auch das Limit (die Gr├Â├če der GDT in Byte) der globalen Deskriptortabelle gespeichert - das GDTR weist somit auf die GDT im Speicher. Im Gegensatz dazu verwaltet der i386 die lokale Deskriptortabelle dynamisch, wodurch mehrere LDTs m├Âglich sind (dagegen ist nur eine einzige GDT vorhanden). F├╝r jede lokale Deskriptortabelle existiert ein Eintrag in der GDT, die LDTs werden dadurch ├Ąhnlich wie Segmente verwaltet. Das GDTR enth├Ąlt also einen Segmentdeskriptor, das LDTR aber einen Segmentselektor.

Das GDTR kann durch den Befehl LGDT mem64 mit dem Segmentdeskriptor mem64 geladen werden. Dieser Schritt ist notwendig, bevor die Betriebssystemlader den i386 in den Protected Mode umschalten. Ansonsten h├Ąngt die Speicherverwaltung in der Luft. Demgegen├╝ber wird das LDTR ├╝ber den Befehl LLDT reg16 oder LLDT mem16 mit einem Segmentselektor geladen. Dieser Selektor gibt den Eintrag in der globalen Deskriptortabelle an, der den Deskriptor f├╝r die lokale Deskriptortabelle enth├Ąlt. Die konsistente Verwaltung der Deskriptortabellen ist alleinige Aufgabe des Betriebssystems, der Anwendungsprogrammierer hat keinerlei Einflu├čm├Âglichkeit auf diesen Vorgang. Die Befehle zum Laden der Deskriptortabellenregister LDTR und GDTR m├╝ssen im Protected Mode von einem Task mit der Privilegierungsstufe 0 ausgef├╝hrt werden, ├╝blicherweise vom Kernel.

Das Betriebssystem liefert f├╝r einen Task sowohl die globale als auch eine lokale Deskriptortabelle. G├╝nstig ist es, von mehreren Tasks gemeinsam benutzte Segmente (wie beispielsweise Segmente mit Betriebssystemfunktionen) in der GDT und ausschlie├člich vom jeweiligen Task benutzte Segmente (Programmcode und - daten) in der LDT zu beschreiben. Mit diesem Verfahren k├Ânnen die verschiedenen Tasks voneinander isoliert werden, und es stehen f├╝r einen Task maximal zwei vollst├Ąndige Deskriptortabellen ├í 8192 Eintr├Ąge oder 16384 Segmente zur Verf├╝gung, wobei der nullte Eintrag in der GDT nicht benutzt wird. Jedes Segment kann bei gel├Âschtem G - Bit bis zu 1mByte, bei gesetztem G - Bit bis zu 4gByte umfassen. Damit erh├Ąlt man einen maximalen logischen oder virtuellen Adre├čraum von 16gByte bzw. 64tByte pro Task. Dieser Wert ist nat├╝rlich wesentlich gr├Â├čer als der physikalische Adre├čraum des i386 mit 4gByte.

Schon bei Page - Granularit├Ąt mit einem virtuellen Adre├čraum von vergleichsweise bescheidenen 16gByte k├Ânnen nicht mehr alle Segmente im Speicher vorhanden sein. Wird ein Segment beispielsweise vom Betriebssystem auf Festplatte ausgelagert, so setzt dieses das P - Bit im Segmentdeskriptor der entsprechenden Deskriptortabelle LDT oder GDT auf 0. M├Âchte der Prozessor auf dieses Segment zugreifen, so l├Âst die Hardware die Exception "Segment nicht vorhanden", entsprechend Interrupt 0bh, aus. Der aufgerufene Interrupt - Handler kann dann das gew├╝nschte Segment erneut in den Speicher laden, wobei er unter Umst├Ąnden ein anderes Segment auslagert. Der i386 unterst├╝tzt dieses Swapping, indem es die Exception ausl├Âst. Das eigentliche Laden oder Auslagern der Segmente muss aber das Betriebssystem erledigen.


3.3. Umschalten in den Protected Mode[19]


Soll der i386 in den Protected Mode umgeschaltet werden, muss das Betriebssystem oder das ROM - BIOS die erforderlichen Tabellen im Speicher aufbauen und wenigstens die beiden Register GDTR und IDTR mit geeigneten Werten initialisieren. Ist das geschehen, so setzt das System oder ROM - BIOS ├╝ber den Befehl MOV cr0, das Bit PE im Steuerregister CR0 auf 1. Der i386 arbeitet nun im Protected Mode. Beim PC kann der i386 auch ├╝ber die Funktion 89h des Interrupts INT 15h in den Protected Mode umgeschaltet werden. Aus Kompatibilit├Ątsgr├╝nden mit dem 80286 kann der i386 zudem ├╝ber den 80286 - Befehl LMSW in den Protected Mode versetzt werden.

Der i386 kann den Protected Mode auf einfache Weise wieder verlassen, indem ein Task mit PL=0 das PE - Bit durch den Befehl MOV cr0, wieder l├Âscht. Der i386 schaltet dann sofort in den Real Mode zur├╝ck. Beim 80286 ist das leider nicht m├Âglich. Einmal im Protected Mode, konnte beim 80286 das PE - Bit nicht wieder gel├Âscht werden. Der Grund daf├╝r war wohl, dass INTEL sich nicht vorstellen konnte, dass jemand (n├Ąmlich MS - DOS) den neuen Protected Mode einfach ignorieren k├Ânnte. Der Real Mode sollte beim 80286 nur dazu dienen, alle notwendigen Vorbereitungen zum Sprung in den Protected Mode zu treffen. Eine R├╝ckkehr in den Real Mode schien absurd (ist sie auch, wenn man z.B. den physikalischen Real - Mode - Adre├čraum von 1mByte mit dem des Protected Mode mit 4gByte vergleicht).Erst die Maxime der Kompatibilit├Ąt und die enorme Marktbedeutung von MS - DOS zwangen INTEL, eine einfache R├╝ckkehr in den Real Mode zu erm├Âglichen.


3.4. Speicheradressierung im Protected Mode


Im Real Mode war die Ermittlung einer linearen Speicheradresse ganz einfach: Der Wert des entsprechenden Segments wurde mit 16 multipliziert zum Offset addiert. Im Protected Mode ist das ganze erheblich umfangreicher. Es laufen folgende Schritte ab:
    Anhand des Segmentselektors im entsprechenden Segmentregister wird ermittelt, ob die globale oder die lokale Deskriptortabelle benutzt werden soll;

    Mit Hilfe des Speicherverwaltungsregisters GDTR oder LDTR wird die Basisadresse der globalen bzw. lokalen Deskriptortabelle ermittelt;

    Der Index des Segmentselektors wird mit 8 multipliziert, das Ergebnis zur Basisadresse der Deskriptortabelle addiert und es wird ermittelt, ob der so erhaltene Wert das Limit nicht ├╝bersteigt; tut er das, so l├Âst der Prozessor die Exception 0dh "allgemeiner Protection - Fehler" aus.

    Anhand des Segmentdeskriptors in der Deskriptortabelle werden Basisadresse und Limit des Segments ermittelt;

    Im Adre├čaddierer in der Adressierungseinheit werden diese Basisadresse und der Offset addiert und es wird ermittelt, ob der erhaltene Wert das Limit des betreffenden Segments nicht ├╝bersteigt; ist dies der Fall, so l├Âst der Prozessor die Exception 0dh "allgemeiner Protection - Fehler" aus.

    Die so ermittelte Adresse wird als lineare (ohne Paging als physikalische) Adresse ausgegeben.





3.5. Segment - und Zugriffstypen[20]


Das Bit S sowie die vier Typbits des Segmentdeskriptor geben Art und M├Âglichkeit f├╝r einen Zugriff auf das beschriebene Segment an. Ist S (Descriptor - Type) gleich 1, so handelt es sich um ein Applikationssegment, d.h. das Segment enth├Ąlt Programmcode oder Programmdaten. Zu diesen Programmen geh├Âren auch Systemroutinen, die zur Verwaltung des Computers bis auf Kernel - Ebene des Betriebssystems geh├Âren. Demgegen├╝ber beschreiben Systemsegmente und Gates (S=0) vordefinierte Datenstrukturen, die zur Steuerung und ├ťberwachung von mehreren parallel ablaufenden Tasks im Computer oder zum Aufruf von Prozeduren und zum Sprung zu Segmenten mit unterschiedlicher Privilegierungsstufe dienen.


3.5.1. Applikationssegmente


Ist S=1, handelt es sich also um ein Applikationssegment, so hat das Feld Typ im Segmentdeskriptor folgenden Aufbau:

In der umseitigen Tabelle finden sich alle m├Âglichen Kombinationen der Bits EXE, E/C, W/R und A sowie deren Bedeutung.

Legt man den Begriff Daten m├Âglichst weit aus, so besagt er soviel wie Information. In diesem Sinne sind auch Programme Daten, da sie (logischerweise) eine Menge von Information (eine Folge von Bits) darstellen (n├Ąmlich was der Computer machen soll). Dass uns diese Art der Information nicht immer unmittelbar einsichtig erscheint, ist dabei ohne Relevanz.

Im engeren Sinne trifft man aber doch eine Unterscheidung zwischen Daten und Programmen: Daten sind Information, die be - oder verarbeitet werden soll, Programme stellen die Werkzeuge dar, mit denen die Daten be - und verarbeitet werden. Wesentlich unterscheiden sich diese beiden Kategorien vor allem darin, dass die Datenmenge "Programm" w├Ąhrend ihrer Ausf├╝hrung im allgemeinen nicht ver├Ąndert wird, Daten im engeren Sinne aber gerade bearbeitet und dadurch ver├Ąndert werden. Diese Differenzierung des allgemeinen Begriffes "Daten" spiegelt sich auch bei den Segmentdeskriptoren wider: Es gibt ausf├╝hrbare, d.h. Programmsegmente, sowie nicht ausf├╝hrbare, d.h. Datensegmente. Markiert wird diese Unterscheidung durch das Bit EXE (Execute): ist EXE gleich 1, so handelt es sich um ein Programmsegment, ansonsten um ein Datensegment.




Datensegmente sind implizit stets als lesbar gekennzeichnet. Sie k├Ânnen jedoch erst dann beschrieben werden, wenn das Bit W (Write) gesetzt ist. Ein Schreibversuch auf ein Segment mit gel├Âschtem Write - Bit (W=0) l├Âst die Exception "allgemeiner Protection - Fehler" und damit den Interrupt 0dh aus. Datensegmente k├Ânnen sich darin unterscheiden, in welche Richtung sie jeweils "wachsen". Die Richtung wird dabei durch das Bit E (Expand - down) definiert. Es ver├Ąndert die Bedeutung des Limits: Bei einem sich nach oben erstreckenden Segment (E=0) muss der Offset eines Objekts stets kleiner oder darf h├Âchstens gleich dem Limit sein. Demgegen├╝ber muss bei einem sich nach unten erstreckenden Segment (E=1) der Offset immer einen gr├Â├čeren Wert als das Limit aufweisen. Bei gesetztem E - Bit w├Ąchst das Segment also nach unten, ansonsten nach oben. Ein Beispiel f├╝r ein nach unten wachsendes Segment w├Ąre das Stack - Segment, da der Stack bei einer Vergr├Â├čerung durch den dekrementierten Stack - Zeiger ESP nach unten w├Ąchst.

Greift der Prozessor auf Daten oder Code in einem Segment zu, so setzt er automatisch das Bit A (Accessed) im entsprechenden Deskriptor. Das Betriebssystem ist dadurch in der Lage festzustellen, welche Segmente h├Ąufig angesprochen werden. Das ist wichtig, wenn ein Segment auf Festplatte ausgelagert werden soll, um Platz f├╝r ein neues zu schaffen.

Programmsegmente sind im Gegensatz zu Datensegmenten implizit stets als nicht - beschreibbar gekennzeichnet, der Prozessor kann daher niemals einen Schreibzugriff auf ein solches Codesegment ausf├╝hren. Diese Vorkehrung dient zum Schutz gegen Programmfehler und ist Bestandteil der Schutzphilosophie des Protected Mode. Unter MS - DOS f├╝hren Bugs h├Ąufig zum ├ťberschreiben von Programmcode - der PC st├╝rzt ab. Das wird durch den impliziten Schreibschutz von Codesegmenten verhindert. Manchmal ist es aber dennoch notwendig, Code oder Immediate - Operanden (die ja Bestandteil des Befehlsstromes und dadurch des Codesegments sind) w├Ąhrend der Laufzeit zu ├╝berschreiben. Zu diesem Zweck sieht die Implementierung des Protected Mode vor, ein Code - und ein Datensegment zu ├╝berlagern, beispielsweise indem ein Segment als Codesegment gekennzeichnet wird, w├Ąhrend man ein zweites Segment mit gleicher Basisadresse und gleichem Limit als Datensegment definiert. In diesem Fall kann der i386 ├╝ber den Umweg eines sogenannten Alias mit Hilfe des Datensegments Code ├╝berschreiben. Das ist nat├╝rlich nicht ganz ungef├Ąhrlich, weil nun im Prinzip die gleichen, oben angef├╝hrten, Fehler auftreten k├Ânnen.

Ist das Bit R (Read) im Segmentdeskriptor eines Codesegments gesetzt (R=1), kann das Segment nicht nur ausgef├╝hrt, sondern auch ├╝ber einen MOV - Befehl direkt in ein Vielzweckregister ├╝bertragen werden. Das ist beispielsweise notwendig, wenn im Programmcode Daten eingebettet sind (also Immediate - Operanden oder Tabellen). Im ROM - BIOS finden sich h├Ąufig Tabellen f├╝r Festplattentypen, Basisadressen des Bildschirmspeichers etc. Wenn die Tabellen nur gelesen werden sollen, kann man sich ├╝ber das R - Bit ein aufwendiges und fehleranf├Ąlliges Alias sparen.

Um auch ein m├Âglichst effektives Auslagern von Codesegmenten auf einen Massenspeicher zu erm├Âglichen, weist der Segmentdeskriptor f├╝r ein Codesegment ebenfalls ein Accessed - Bit auf. Statt des Expand - Down - Bits (Bit E) der Datensegment - Deskriptoren besitzt ein Codesegment - Deskriptor das Bit C (Conforming). Programmsegmente, die als Conforming gekennzeichnet sind, k├Ânnen auch von weniger privilegierten Codesegmenten direkt angesprochen werden, ohne dass der Umweg ├╝ber ein Gate notwendig ist. Der Code des Segments wird dann mit der Privilegierungsstufe des aufrufenden Segments ausgef├╝hrt. Typischerweise befinden sich Systemfunktionen, die keine gesch├╝tzten Teile des Systems ben├╝tzen, in Conforming - Segmenten. Damit k├Ânnen Anwendungsprogramme auf weniger kritische Funktionen zugreifen, ohne den langwierigen Umweg ├╝ber ein Call - Gate machen zu m├╝ssen.

3.5.2. Systemsegmente





Der i386 kennt insgesamt drei grundlegende Systemsegmenttypen, n├Ąmlich Task - State - Segment (TSS), lokale Deskriptortabelle (LDT) und Gate. Sie dienen zur Speicherverwaltung (LDT) sowie zum Aufruf von Tasks und Prozeduren. Ein Segmentdeskriptor beschreibt ein Systemsegment, wenn das S - Bit gel├Âscht ist (S=0). Der i386 interpretiert dann den Wert in dem Feld Typ des Deskriptors entsprechend der folgenden Tabelle. Beispielsweise wird eine lokale Deskriptortabelle durch einen Systemsegment - Deskriptor beschrieben, der im Typfeld den Eintrag 2 (0010b) aufweist. Neben den Systemsegmenten existieren auch Gates.
Die Tabelle zeigt deutlich, dass sich die meisten Gates und die Task - State - Segmente f├╝r den 80286 und den i386 unterscheiden. Ursache ist die 32 - Bit - Architektur des i386, die im Gegensatz zum 80286 z.B. 32 - Bit - Offsets und 32 - Bit - Stackwerte erlaubt. Aus Kompatibilit├Ątsgr├╝nden kann der i386 die 80286 - Systemsegmente problemlos verarbeiten. Umgekehrt gilt das nat├╝rlich nicht. Ein TSS definiert den Zustand eines aktiven (busy) oder unterbrochenen (verf├╝gbaren) Tasks, es ist wesentlich f├╝r die Hardwareunterst├╝tzung von Multitasking - Betriebssystemen durch den i386. Zu beachten ist, dass TSS - und LDT - Deskriptoren nur in der GDT als Eintr├Ąge zul├Ąssig sind, in der LDT k├Ânnen sie nicht auftreten. [Im Rahmen dieser Arbeit werde ich nur die i386 - Versionen der verschiedenen Systemsegmenttypen erl├Ąutern, da nur diese aufgrund des Veraltens des 80286 noch von Bedeutung sind.]

Im Protected Mode verwendet der i386 sogenannte Gates f├╝r einen kontrollierten Zugriff von Tasks auf Daten und Code in Segmenten h├Âherer Privilegierungsstufe. Die Gates implementieren einen erheblichen Schutz gegen unberechtigte oder fehlerhafte Zugriffe auf fremde Daten und Programme. Nimmt ein Anwendungsprogramm Betriebssystemfunktionen in Anspruch, um z.B. Dateien zu ├Âffnen oder zu schlie├čen, dann garantieren die Gates, dass der Zugriff fehlerfrei erfolgt, oder eine Exception ausgel├Âst wird. Das Anwendungsprogramm kann sich nicht an den Schutzmechanismen vorbeimogeln, ohne dass das Betriebssystem sofort davon informiert wird. Im Real Mode ist DOS dagegen v├Âllig blind. Ein fehlerhafter Zugriff muss nicht sofort zu einer offensichtlichen St├Ârung des Computers f├╝hren, h├Ąufig f├Ąllt er erst auf, wenn das System bereits abgest├╝rzt ist. Das macht das Debuggen von Real - Mode - Programmen manchmal recht kompliziert. Versucht ein Anwendungsprogramm, die Funktion mit einer falschen Einsprungadresse aufzurufen, verh├Ąlt sich der Computer unvorhersehbar. Die Gates definieren im Protected Mode daher quasi "Tore", durch die das Anwendungsprogramm Zugriff auf fremde Routinen hat.


3.5.3. Steuerungs├╝bergabe und Call Gates[21]


Bei einem Near - Call wird die Steuerung an eine Prozedur oder einen Sprungpunkt ├╝bergeben, der bzw. die sich im gleichen Segment wie der entsprechende CALL - oder JMP - Befehl befindet. Ein solcher Transfer ver├Ąndert also nur den Wert des Befehlsz├Ąhlers EIP, und der i386 pr├╝ft lediglich, ob EIP das Limit des Segments ├╝bersteigt. Ist der Offset g├╝ltig, so wird der Aufruf bzw. Sprung ausgef├╝hrt, ansonsten l├Âst der i386 die Exception "allgemeiner Protection - Fehler" aus.

Tasks bestehen aber nur selten aus nur einem Codesegment. In der Regel sind mehrere Codesegmente vorhanden. Ein Zugriff auf ein anderes Codesegment innerhalb des Tasks findet beispielsweise bei einem Far - Call, einem Far - Jump oder einem Interrupt statt. In allen drei F├Ąllen wird ein neuer Selektor f├╝r das Codesegment geladen. Im Real Mode werden bei einem solchen Intersegment - Aufruf einfach der Befehlsz├Ąhler EIP und das Codesegment CS mit neuen Werten geladen, die den Einsprungpunkt der Routine angeben. Im Protected Mode ist dies etwas komplizierter, schlie├člich verlangt das Laden des Codesegments eine umfangreiche Pr├╝fprozedur.

F├╝r einen Far - Call oder Far - Jump stehen drei M├Âglichkeiten zur Verf├╝gung:
    Besitzt das Zielsegment dieselbe Privilegierungsstufe (PL) wie das Ausgangssegment, so kann der Far - Call durch das Laden des Zielsegmentselektors in das Codesegment CS direkt ausgef├╝hrt werden. Der i386 pr├╝ft in diesem Fall lediglich, ob der neue Wert des Befehlsz├Ąhlers EIP das Limit des Zielsegments nicht ├╝bersteigt und ob der Typ des Zielsegments (EXE=0 oder 1) mit dem Aufruf konsistent ist.

    Ist das Zielsegment als Conforming gekennzeichnet, und ist seine Privilegierungsstufe gr├Â├čer (der Wert von PL kleiner) als die des Ausgangssegments, so wird der Far - Aufruf in gleicher Weise wie oben ausgef├╝hrt. Der Code des Conforming - Segments wird dann allerdings mit einer Privilegierungsstufe CPL ausgef├╝hrt, die der weniger privilegierten Ebene des aufrufenden Programmes und nicht der h├Âher privilegierten Stufe des Conforming - Segments entspricht. Das verhindert, dass sich weniger privilegierte Programme ├╝ber die Hintert├╝r eines Conforming - Segments eine h├Âhere Privilegstufe beschaffen und dadurch Zugriff auf gesch├╝tzte Systembereiche erlangen.



Besitzt das Zielsegment eine andere Privilegstufe als das Ausgangssegment, und ist es nicht als Conforming gekennzeichnet, so bleibt nur der Weg ├╝ber ein Call - Gate.
In den ersten beiden F├Ąllen l├Ądt der i386 einfach den Zielselektor in das Register CS und den neuen Befehlsz├Ąhlerwert in EIP und f├Ąhrt dann mit der Programmausf├╝hrung fort. Das ist (mit Ausnahme der ├ťberpr├╝fung) einem Far - Aufruf oder Far - Sprung im Real Mode ├Ąhnlich. Im letzten Fall zeigt der neue Segmentselektor nicht auf das Zielsegment selbst, sondern auf ein sogenanntes Call - Gate.

Die Behandlung von Interrupts ist im allgemeinen eine ureigene und auch kritische Aufgabe des Betriebssystem - Kernels, weil Interrupts den Computer unmittelbar beeinflussen. Ein Interrupt - Aufruf f├╝hrt dadurch meist zu einer ├änderung der Privilegierungsstufe (z.B. wenn ein Anwenderprogramm (PL=3) durch einen Interrupt - Handler im Kernel (PL=0) unterbrochen wird). Der Interrupt muss daher ein Interrupt - oder Trap - Gate benutzen, um den Interrupt - Handler zu aktivieren. Die Bedeutung der Task - Gates wird weiter unten erl├Ąutert. Die Call - , Interrupt - und Trap - Gates bilden "Tore" f├╝r den Einsprung in eine Routine eines anderen Segments mit anderer Privilegierungsstufe. Gates werden durch ein Bit S=0 im Segmentdeskriptor und einen Wert des Typfeldes von 4 bis 7 und 12 bis 15 definiert. Sie sind also Teil der Systemsegmente.

Call - Gates werden nicht nur f├╝r Prozedur - Aufrufe ├╝ber einen Far - Call, sondern auch f├╝r alle unbedingten und bedingten Sprunganweisungen mit einem Far - Jump verwendet. Call - Gates d├╝rfen in der lokalen und globalen Deskriptortabelle, nicht aber in der Interrupt - Deskriptortabelle auftreten. Dort sind nur Interrupt - , Trap - und Task - Gates erlaubt.

Wie die Abbildung bereits zeigt, unterscheidet sich der Aufbau eines Gate - Deskriptors ganz erheblich von einem "normalen" Segmentdeskriptor: Es fehlt z.B. die Basisadresse des Segments. Statt dessen ist das 5 - Bit - Feld Param - Count vorgesehen, und die Bits 5 bis 7 im zweiten Deskriptordoppelwort sind auf 0 gesetzt. Au├čerdem ist das zweite Wort f├╝r einen Segmentselektor reserviert. Er definiert das Zielsegment f├╝r den Far - Aufruf oder Far - Sprung und gibt zusammen mit dem Offset im niederwertigen und h├Âchstwertigen Wort die Einsprungadresse an. Damit werden bei


einem Far - Call ├╝ber ein Call - Gate zwei Segmentdeskriptor - referenzen ausgef├╝hrt: die erste, um den Gate - Deskriptor zu ermitteln, und die zweite, um die Basisadresse des betreffenden Segments zu ermitteln. Das Gate enth├Ąlt ja wiederum nur einen Zielsegment - selektor, nicht dessen lineare Adresse. Die Adressierungseinheit des i386 addiert die Basisadresse des durch den Segmentselektor im Gate - Deskriptor festgelegten Zielsegments und den im Gate - Deskriptor angegebenen Offset. Der ermittelte Wert stellt die lineare Einsprungadresse


dar.
Der i386 erkennt am Eintrag im Typfeld, ob der Zielsegmentselektor f├╝r das CS - Register bei einem Far - Call oder Far - Sprung direkt ein Codesegment oder einen Gate - Deskriptor darstellt. Im ersten Fall pr├╝ft der Prozessor, ob der direkte Aufruf erlaubt ist (ob z.B. das Zielsegment als Conforming gekennzeichnet ist), und f├╝hrt ihn unabh├Ąngig davon auf oder erzeugt eine Exception. In letzterem Fall l├Ądt er dagegen zun├Ąchst den Segmentdeskriptor, der im Call - Gate angegeben ist.

Sinn und Zweck dieses Vorgehens liegen auf der Hand: Es ist ein exakt definierter Einsprungpunkt vorgegeben, so dass das aufrufende Programm nicht versehentlich einen falschen Einsprungpunkt vorgeben kann. Das ist besonders wichtig, wenn Funktionen des Betriebssystems aufgerufen werden: Ein falscher Einsprungpunkt in diesen Routinen f├╝hrt gew├Âhnlich zu einem totalen Systemabsturz, die Angabe des falschen Gates dagegen h├Âchstens zum Abbruch des Tasks und der Ausgabe einer Fehlermeldung.

Wie bereits erw├Ąhnt, besitzt jeder Task f├╝r jede der vier verschiedenen Privilegierungsstufen jeweils einen eigenen Stack. Zwischen diesen Stacks m├╝ssen nat├╝rlich h├Ąufig Daten ausgetauscht werden, damit die Routine einer anderen Stufe Zugriff auf die Daten des aufrufenden Programms hat. Um diesen Zugriff zu erm├Âglichen, tr├Ągt das System oder der Compiler/Assembler in das Feld Param - Count die Anzahl der zu kopierenden Doppelworte (├í vier Byte) ein. Der i386 ├╝bertr├Ągt dann diese Doppelworte bei einem Aufruf des Call - Gates automatisch vom Stack der aufrufenden zum Stack der aufgerufenen Prozedur. Mit f├╝nf Bits lassen sich so maximal 31 Doppelworte, d.h. 124 Byte ├╝bergeben. Ein Programmierer, der mehr Byte f├╝r die Parameter├╝bergabe ben├Âtigt, ist selbst schuld, doch bietet die ├ťbergabe eines Far - Zeigers auf eine Datenstruktur mit den gew├╝nschten Parametern einen einfachen und schnellen Ausweg.

Selbstverst├Ąndlich f├╝hrt der i386 auch bei einem Aufruf ├╝ber Gates eine Pr├╝fung der Zugriffsberechtigung aus. In diese Pr├╝fung gehen die folgenden Privilegierungsstufen ein:
    CPL; RPL des Segmentselektors f├╝r das Call - Gate; DPL des Gate - Deskriptors; DPL des Segmentdeskriptors f├╝r das Zielsegment des Aufrufs oder Sprungs.

Der DPL - Eintrag des Gate - Deskriptors legt fest, von welchen Privilegierungsstufen aus das Gate benutzt werden kann.

Gates werden z.B. benutzt, um die Steuerung an privilegierte Ebenen (z.B. das Betriebssystem) oder Code gleicher Stufe zu ├╝bergeben. F├╝r letzteren Fall ist zwar kein Gate n├Âtig, aber dieses Vorgehen ist auch m├Âglich (und sicherer). Wichtig ist, dass nur CALL - Befehle Gates dazu verwenden k├Ânnen, Routinen niedriger Privilegierungsstufe (mit gr├Â├čerer PL) aufzurufen. Sprung - Befehle k├Ânnen Call - Gates nur dazu benutzen, die Steuerung an ein Code - Segment gleicher Privilegierungsstufe oder an ein Conforming - Segment gleicher oder h├Âherer Stufe zu ├╝bergeben.

F├╝r einen Sprung - Befehl zu einem nicht als Conforming gekennzeichneten Segment m├╝ssen die beiden folgenden Bedingungen erf├╝llt sein:
    Die effektive Privilegierungsstufe EPL (gleich dem Maximum von CPL und RPL) muss kleiner oder gleich dem DPL des Gate - Deskriptors sein; Der DPL des Zielsegment - Deskriptors muss gleich dem CPL des aufrufenden Programmes sein.

F├╝r den CALL - Aufruf oder einen Sprung - Befehl zu einem Conforming - Segment m├╝ssen dagegen die folgenden zwei Bedingungen erf├╝llt sein:
    Die effektive Privilegierungsstufe EPL (gleich dem Maximum von CPL und RPL) muss kleiner oder gleich dem DPL des Gate - Deskriptors sein; Der DPL des Zielsegment - Deskriptors muss kleiner oder gleich dem CPL des aufrufenden Programmes sein.

Bei einem Aufruf einer Prozedur h├Âherer Privilegierungsstufe ├╝ber ein Call - Gate f├╝hrt der i386 noch folgende Vorg├Ąnge aus:
    Der CPL - Wert wird so ge├Ąndert, dass er die neue Privilegierungsstufe widerspiegelt; Der i386 ├╝bergibt die Steuerung an die aufgerufene Prozedur oder den angesprungenen Code; Statt des bisherigen Stack wird nun der Stack der neuen Privilegierungsstufe benutzt.


Die Stacks aller Privilegierungsstufen werden dabei durch das Task - State - Segment des jeweiligen Tasks definiert.

3.6. Die Interrupt - Deskriptortabelle[22]


Neben den Registern f├╝r die globale und lokale Deskriptortabelle sowie das Task - Register besitzt der 80286 noch ein neues Register f├╝r die Interrupt - Deskriptortabelle (IDT). Im Real Mode waren die 1024 (1k) niederwertigen Byte des Adre├čraums f├╝r die 256 Eintr├Ąge (entsprechend den 256 Interrupts des i386) der Interrupt - Vektortabelle reserviert. Jeder Eintrag enth├Ąlt im Format Segment:Offset die Einsprungadresse des zugeh├Ârigen Interrupt - Handlers.

Auch im Protected Mode stehen 256 Interrupts von 0 bis 255 zur Verf├╝gung. Die Interrupt - Handler werden jedoch nicht mehr ├╝ber ein Doppelwort mit dem Format Segment:Offset angesprochen, sondern ├╝ber Gates. Als Eintr├Ąge in der IDT sind nur Task - , Interrupt - und Trap - Gates zul├Ąssig. Damit ist jeder Eintrag statt vier nunmehr acht Bytes lang. Durch den Eintrag Limit im IDTR kann die Gr├Â├če der Interrupt - Deskriptortabelle jedoch den tats├Ąchlichen Erfordernissen angepa├čt werden. Ben├Âtigt ein System beispielsweise nur die Interrupts 0 bis 63, so gen├╝gt eine IDT mit 64 Eintr├Ągen zu acht Bytes, d.h. insgesamt 512 Bytes. Wird ein Interrupt ausgel├Âst, f├╝r den kein Eintrag in der IDT mehr existiert (im angef├╝hrten Fall z.B. ein INT 68), so tritt der i386 in den Shutdown - Modus ein. Da im IDTR neben dem Limit auch die Basisadresse der IDT angegeben wird, kann sich die Tabelle irgendwo im Speicher befinden.

Bevor der i386 in den Protected Mode umgeschalten wird, muss das im Real Mode laufende Initialisierungsprogramm neben der GDT auch die IDT aufbauen und deren Basisadresse und Limit in das IDTR laden. Geschieht das nicht, so h├Ąngt sich der Prozessor mit ziemlicher Sicherheit auf, bevor die IDT im Protected Mode erstellt werden kann, da jede Art von Exception oder Interrupt entweder ins Nirwana weist oder eine neue Fehler - Exception ausl├Âst, die nicht behandelt werden kann. Beim Einschalten oder einem Prozessor - Reset l├Ądt der i386 von sich aus das IDTR mit dem Wert 000000h f├╝r die Basisadresse und 03ffh f├╝r das Limit. Diese Werte sind konsistent mit dem reservierten Bereich f├╝r die Interrupt - Vektortabelle im Real Mode.




Die Interrupt - , Trap - und Task - Gates weisen denselben Aufbau wie das Call - Gate auf, nur besitzt der Eintrag DWord - Count keine Bedeutung. Die Interrupt - und Trap - Gates definieren in gleicher Weise wie das Call - Gate den Einsprungpunkt ├╝ber die Eintr├Ąge Offset und Segmentselektor. Der Segmentselektor weist wie bei einem Call - Gate auf den Segmentdeskriptor in der LDT oder GDT, der die Basisadresse des betreffenden Segments enth├Ąlt. Der Unterschied zwischen Interrupt - und Trap - Gate besteht darin, dass ein Interrupt - Aufruf ├╝ber ein Interrupt - Gate die Flags IE (Interrupt Enable) und T (Trap) l├Âscht, das Trap - Gate hingegen nicht.

Die Besonderheiten, die gelten, wenn der Prozessor bei einem Interrupt oder einem CALL - bzw. JMP - Befehl auf ein Task - Gate trifft, werden im folgenden Abschnitt erl├Ąutert.

3.7. Multitasking, TSS und das Task - Gate[23]


Die gesamten Protection - Funktionen des i386 dienen in erster Linie einem Ziel: Multitasking. Bei einem leistungsf├Ąhigen PC - System sollen mehrere Tasks mehr oder weniger parallel ablaufen. Tats├Ąchlich erreicht man mit einem Prozessor nur eine scheinbare Parallelit├Ąt, weil die einzelnen Tasks nur f├╝r kurze Zeit hintereinander ausgef├╝hrt werden, um dann unterbrochen und nach kurzer Zeit an der gleichen Stelle wieder gestartet zu werden. Um das zu erreichen, muss der Zustand eines Tasks zum Zeitpunkt der Unterbrechung vollst├Ąndig gesichert werden, weil der Task ja sonst nicht an derselben Stelle unter den Bedingungen, die zum Zeitpunkt der Unterbrechung herrschten, neu aufgenommen werden kann.

Ein ├Ąhnlicher Vorgang findet auch unter MS - DOS statt: Tritt ein Hardware - Interrupt wie beispielsweise der Timer - Interrupt auf, so werden alle Register auf den Stack gesichert, der Interrupt bedient und alle Register vom Stack wieder mit den alten Werten geladen. Wichtig ist, dass das Registerpaar CS:EIP gesichert wird, da es die Stelle im Programm


angibt, an der es unterbrochen worden ist.

Doch im Protected Mode ist es aufgrund der umfangreichen Schutzfunktionen des i386 nicht damit getan, einfach ein paar Register zu sichern. Hierzu dient vielmehr das noch nicht n├Ąher besprochene Systemsegment mit Namen Task - State - Segment oder kurz TSS. Wie der Name bereits ausdr├╝ckt, speichert es den Zustand eines Tasks vollst├Ąndig. Es stellt ein ganzes Segment dar, das ausschlie├člich zum Speichern des Task - Zustandes dient.

Im TSS sind neben den gew├Âhnlichen Offset - und Segment - Registern beispielsweise die Zeiger ESP und die Segmente SS f├╝r die Stacks der verschiedenen Pivilegierungsstufen, die f├╝r den Task benutzte lokale Deskriptortabelle und ein Eintrag enthalten, der auf das TSS des zuvor ausgef├╝hrten Tasks zeigt. Au├čerdem ist hier das CR3 - Register abgelegt, das die Basisadresse des Page - Directory f├╝r den beschriebenen Task angibt. [Ich erw├Ąhne das nur der Vollst├Ąndigkeit halber, in dieser Arbeit wird nicht n├Ąher auf die Paging - Mechanismen eingegangen.]

Der Eintrag I/O - Map - Basis gibt die Adresse einer I/O - Map an, die neben dem IOPL - Flag zum Schutz des I/O - Adre├čbereichs im Protected Mode dient. Das Feld Back - Link enth├Ąlt einen Segmentselektor, der auf das TSS des zuvor unterbrochenen Tasks weist. Der Eintrag ist aber nur dann g├╝ltig, wenn das Bit NT (Nested Task) im EFlags - Register gesetzt ist. Wenn das T - Bit (Trap) gesetzt ist, erzeugt der i386 bei einem Task - Switch (d.h. beim Laden des TSS) eine Debug - Exception 01h.

Weist der zugeh├Ârige TSS - Deskriptor in der LDT oder GDT im Typfeld den Wert 1 (80286 - kompatibles TSS) oder 9 (i386 - TSS) auf, so ist das durch den Deskriptor beschriebene TSS verf├╝gbar. Dies bedeutet, dass der von diesem TSS beschriebene Task gestartet werden kann. Ist im Typfeld hingegen ein Eintrag 3 (80286 - kompatibles TSS [busy]) oder 11 (i386 - TSS [busy]) vorhanden, so ist das TSS als aktiv (busy) gekennzeichnet. Der von einem solchen TSS beschriebene Task ist aktiv und muss nicht eigens aktiviert werden. Im ├╝brigen darf er nicht einmal aktiviert werden, weil das gespeicherte TSS noch die alten Werte enth├Ąlt. Tasks sind also im Gegensatz zu Prozeduren prinzipiell nicht reentrant. Erst wenn der gerade laufende (aktive) Task unterbrochen wird, um z.B. einen anderen Task zu aktivieren, sichert der i386 alle aktuellen Werte des aktiven Task im zugeh├Ârigen TSS und l├Ądt die Werte des zu startenden Task aus dessen TSS in die Segment - , Offset - und Steuerregister. Das geschieht v├Âllig automatisch und ohne einen weiteren Eingriff von Software. Woher "wei├č" der Prozessor aber, wann er einen Task und welchen neuen er aktivieren soll, d.h. was bildet den Trigger f├╝r einen Task - Switch. Der Schl├╝ssel liegt in den Task - Gates.

Der TSS - Segmentselektor im Task - Gate verweist auf den Segmentdeskriptor, der das TSS des neu zu aktivierenden Tasks definiert. Trifft der i386 bei einem CALL - Befehl, einem Sprungbefehl oder einem Interrupt auf ein solches Task - Gate, f├╝hrt er einen solchen Task - Switch aus, indem er den gegenw├Ąrtigen Zustand des aktiven Tasks im TSS abspeichert, das durch das Task - Register TR definiert ist und dem Typfeld des zugeh├Ârigen TSS - Deskriptors den Wert 1 (80286 - kompatibles TSS) oder 9 (i386 - TSS) zuweist. Damit ist das TSS als verf├╝gbares TSS gekennzeichnet. Anschlie├čend l├Ądt er den neuen TSS - Segmentselektor aus dem Task - Gate - Deskriptor in das TR und liest aus der LDT oder GDT Basisadresse, Limit und Zugriffsrechte des Task - Gate - Deskriptors. Um den Task - Switch zu vollenden, kennzeichnet der Prozessor nun den zugeh├Ârigen Deskriptor im Typfeld als busy, d.h. er schreibt den Wert 3 (80286 - kompatibles TSS [busy]) oder 11 (i386 - TSS [busy]) in dieses Feld. Zuletzt l├Ądt er die im neuen TSS abgelegten Werte f├╝r die Segmente und Offsets in die entsprechenden Register. Das Registerpaar CS:EIP zeigt nun auf den Befehl des neu aktivierten Tasks, bei dem dieser zuvor unterbrochen worden ist; seine Ausf├╝hrung wird also an der Unterbrechungsstelle erneut aufgenommen.

Erstmals aktivierte Tasks - also Tasks, die neu geladen werden - aktiviert der i386 in gleicher Weise. Nur zeigt das Registerpaar CS:EIP hier nicht auf den Befehl an der Unterbrechungsstelle, sondern den Startbefehl des Programmes.

Beispiel

Der aktive Task sei das Textverarbeitungsprogramm Word, das gerade damit besch├Ąftigt ist, einen Seitenumbruch durchzuf├╝hren. Nun tritt ein Timer - Interrupt auf. Im Interrupt - Handler trifft der i386 auf ein Task - Gate, das auf dBase zeigt. Damit suspendiert der Prozessor Word, indem er alle Register im zugeh├Ârigen TSS sichert. Anschlie├čend l├Ądt er alle notwendigen Daten aus dem TSS f├╝r dBase und startet diesen bereits fr├╝her unterbrochenen Task. Nach kurzer Zeit tritt erneut ein Timer - Interrupt auf, nur wird diesmal dBase angehalten und daf├╝r der C - Compiler aktiviert. Dieses Unterbrechen und Wiederaufnehmen von Tasks findet laufend statt.

Wird ein neues Programm gestartet, so stellt das Betriebssystem ein neues TSS f├╝r diesen Task zur Verf├╝gung. Ein Multitasking - Betriebssystem muss also sehr komplexe Operationen rasend schnell ausf├╝hren. Es wird jedoch vom i386 in sehr effektiver Weise unterst├╝tzt: Um einen Task - Switch auszuf├╝hren, muss das Betriebssystem "nur" ein Task - Gate, einen TSS - Deskriptor und ein TSS zur Verf├╝gung stellen. Das Sichern der alten Registerinhalte und das Laden der neuen Werte f├╝hrt der Prozessor selbst├Ąndig und automatisch aus. Es sind keine Software - Anweisungen des Betriebssystems notwendig, d.h. der i386 sichert bei einem Task - Switch die 104 Bytes des alten TSS und l├Ądt die 104 des neuen TSS v├Âllig selbst├Ąndig.

Zu betonen ist noch, dass es alleinige Aufgabe des Betriebssystems ist, den einzelnen Programmen einen entsprechend gro├čen Anteil an Prozessorzeit zuzuweisen. Die Steuerung der Task - Switches ist alleinige Aufgabe des Betriebssystem, die Programme selbst haben bei einem richtigen Multitasking - Betriebssystem keine Einflu├čm├Âglichkeit darauf.

Nun ein mittlerer Hammer: Das ehemals am meisten verbreitete und auch heute noch oft ben├╝tzte MS - DOS (genauso wie DR - DOS oder PC - DOS) verwendet von den oben beschriebenen Funktionen nicht eine einzige. Auch die Treiber SMARTDRV.SYS und RAMDRIVE.SYS erstellen nur eine GDT und eine IDT, um Bytegruppen zwischen dem unteren 1mByte des Speichers und dem Extended Memory zu verschieben. Task - Switches und die umfangreichen und sehr n├╝tzlichen Zugriffs├╝berpr├╝fungen werden in keinster Weise ausgen├╝tzt. Es gibt ja schlie├člich auch Leute, die stellen sich eine MIG in den Vorgarten......

Neben den bereits im Ansatz geschilderten Pr├╝fungen und Besonderheiten beim Aufruf von Prozeduren oder dem Umschalten zwischen verschiedenen Tasks muss ein Systemprogrammierer noch viele weitere Einschr├Ąnkungen und Vorsichtsma├čnahmen beachten. Erst dann ist es m├Âglich, ein voll funktionsf├Ąhiges Betriebssystem zu programmieren, das den i386 voll ausnutzt. In den n├Ąchsten beiden Abschnitten werden noch die Schutzvorkehrungen f├╝r den zweiten Adre├čraum des i386 erl├Ąutert, n├Ąmlich die Zugriffspr├╝fungen f├╝r den I/O - Adre├čraum.


3.8. Schutz des I/O - Adre├čraumes


├ťber die I/O - Ports werden im allgemeinen Register von Hardware - Komponenten des PC wie beispielsweise der Festplattencontroller oder die Steuerregister des DMA - Chips angesprochen. Da die Steuerung und ├ťberwachung eine origin├Ąre Aufgabe des Betriebssystems ist und hierzu meist Treiber mit PL=1 benutzt werden, f├Ąllt auch der I/O - Adre├čbereich unter den Zugriffsschutz. Ports werden aber nicht mit Hilfe eines Segmentregisters angesprochen, also steht diese Art des Zugriffsschutzes nicht zur Verf├╝gung.

Der Schutz des I/O - Adre├čbereiches erfolgt beim i386 ├╝ber zwei v├Âllig unterschiedliche Strategien: Einmal das IOPL - Flag im Flag - Register und zus├Ątzlich die I/O - Permission - Bit - Map im Task - State - Segment. Zun├Ąchst aber zum IOPL - Flag.

3.8.1. Schutz des I/O - Adre├čbereichs ├╝ber das IOPL - Flag[24]


Der Wert dieses Flags gibt die Privilegierungsstufe an, die ein Code - Segment mindestens aufweisen muss, um auf den I/O - Adre├čraum zugreifen zu k├Ânnen, d.h. es muss gelten CPL ≤ IOPL. Ist der CPL des aktuellen Task gr├Â├čer (niedrigere Privilegierungsstufe), so f├╝hren die I/O - Befehle IN, OUT, INS und OUTS zu der bereits hinl├Ąnglich bekannten Exception "allgemeiner Protection - Fehler". Vern├╝nftige Anwendungsprogramme unter einem vern├╝nftigen Betriebssystem f├╝hren solche Zugriffe ausschlie├člich ├╝ber das Betriebssystem aus. Weniger vern├╝nftige Anwendungsprogramme versuchen das direkt, um die Performance zu erh├Âhen oder andererseits bestimmte Komponenten ├╝berhaupt ansprechen zu k├Ânnen. Neben den vier bereits erw├Ąhnten I/O - Befehlen sind auch CLI (Clear Interrupt Flag) und STI (Set Interrupt Flag) vom IOPL - Flag abh├Ąngig. Diese sechs Befehle werden als IOPL - sensitive Befehle bezeichnet, da der Wert des IOPL - Flag Einflu├č auf ihre Ausf├╝hrung hat.

Sinn und Zweck dieser Einschr├Ąnkung werden sofort einsichtig, betrachtet man den Fall, dass eine Systemfunktion beispielsweise einen Datensatz von der Festplatte liest, dabei durch einen Task - Switch unterbrochen wird, und der neu aufgerufene Task durch einen unmittelbaren Zugriff auf die Steuerregister im Festplattencontroller "dazwischenfunkt". In welchen Zustand sich die unterbrochene Systemroutine nach einem erneuten Task - Switch befindet, ist v├Âllig unvorhersehbar, der PC verabschiedet sich oder zerst├Ârt sogar Daten.

Ein Task kann das IOPL - Flag nur ├╝ber die Befehle POPF (Pop Flags) und PUSHF (Push Flags) ver├Ąndern. Zur ├änderung des IOPL - Flags steht kein expliziter Befehl zur Verf├╝gung (wie z.B. CLI oder STI f├╝r das Interrupt - Flag). Die beiden genannten Befehle sind jedoch privilegiert, d.h. sie k├Ânnen nur von einem Codesegment mit CPL=0 ausgef├╝hrt werden. Diese Stufe ist ├╝blicherweise dem Betriebssystem - Kernel vorbehalten - die Anwendungsprogramme k├Ânnen das IOPL - Flag nicht ver├Ąndern. Bei einem solchen Versuch l├Âst der Prozessor die Exception "allgemeiner Protection - Fehler" aus. Da die Flags jedoch Teil des TSS sind und sich somit von Task zu Task unterscheiden, kann durchaus ein Task Zugriff auf den I/O - Adre├čraum besitzen, ein anderer dagegen nicht.

Diese Strategie der globalen Absicherung des I/O - Adre├čbereichs ├╝ber das IOPL - Flag ist bereits im 80286 implementiert. Der i386 kann die Ports zus├Ątzlich individuell sch├╝tzen. Diese Schutzstrategie f├╝r die Ports ist besonders im Hinblick auf den Virtual 8086 Mode implementiert worden. [Wie auch die Paging - Mechanismen, werde ich auf den Virtual 8086 Mode nicht n├Ąher eingehen, er sei nur der Vollst├Ąndigkeit wegen erw├Ąhnt.]

3.8.2. Schutz des I/O - Adre├čbereichs ├╝ber die I/O - Permission - Bit - Map[25]





Neben dem globalen Schutz durch das IOPL - Flag kennt der i386 einen weiteren Schutzmechanismus f├╝r Zugriffe auf den I/O - Adre├čbereich, n├Ąmlich die sogenannte I/O - Permission - Bit - Map. Sie ist im TSS des jeweiligen Task abgelegt, verschiedene Tasks k├Ânnen also unterschiedliche I/O - Permission - Bit - Maps besitzen. Der Eintrag I/O - Map - Basis im TSS - Deskriptor gibt den Offset innerhalb des TSS an, bei dem die I/O - Permission - Bit - Map beginnt. Sie erstreckt sich bis zum Ende des TSS, wie es im Limiteintrag des TSS - Deskriptors festgelegt ist. Den Raum zwischen dem Eintrag I/O - Map - Basis und dem Beginn der I/O - Permission - Bit - Map kann das Betriebssystem verwenden, um eigene Informationen abzulegen. Die I/O - Permission - Bit - Map muss also nicht unmittelbar nach den Eintr├Ągen f├╝r die Register im TSS beginnen. Vielmehr kann ein nahezu beliebig gro├čer Raum zwischen dem Eintrag I/O - Map - Basis und dem Beginn der I/O - Permission - Bit - Map zur Verwendung durch das Betriebssystem vorgesehen werden, das dort eigene Informationen ablegt. Zu beachten ist, dass das h├Âchstwertige Byte der Map, d.h. das Byte unmittelbar vor dem Ende des TSS den Wert 11111111b (=0ffh) besitzen muss. F├╝r die I/O - Permission - Bit - Map kann nur ein i386 - TSS verwendet werden, da das 80286 - TSS keinen Eintrag I/O - Map - Basis hat.

Eine g├╝ltige I/O - Permission - Bit - Map ist immer dann vorhanden, wenn die I/O - Map - Basis im TSS noch innerhalb des TSS liegt. Zeigt der Wert der Basis ├╝ber das TSS hinaus, so ignoriert der i386 alle Pr├╝fungen im Zusammenhang mit der I/O - Permission - Bit - Map, der Zugriffsschutz f├╝r den I/O - Adre├čbereich erfolgt allein durch das IOPL - Flag.

Die I/O - Permission - Bit - Map stellt praktisch einen Zugriffsschutz zweiter Ebene dar: Wenn die Werte von CPL und IOPL dem aktiven Task einen Zugriff auf den I/O - Adre├čbereich gestatten, so untersucht der i386 anschlie├čend zus├Ątzlich noch die I/O - Permission - Bit - Map, um zu ermitteln, ob der gew├╝nschte Port auch tats├Ąchlich angesprochen werden darf. Das geschieht auf einer eins - zu - eins Zuordnung von I/O - Adressen und dem entsprechenden Bit in der Map. Dem Port mit der Adresse 0 ist das Bit mit dem Offset 0 innerhalb der Map zugeordnet, dem Port 1 das Bit 1 usw. Ist das einem Port entsprechende Bit in der Map gesetzt, also gleich 1, so l├Âst der i386 beim Zugriff auf den zugeh├Ârigen Port die Exception "allgemeiner Protection - Fehler" aus. Ist das Bit gel├Âscht, so f├Ąhrt der Prozessor mit der I/O - Operation fort.

Die L├Ąnge der Map bestimmt die Zahl der so zus├Ątzlich gesch├╝tzten Ports. Es ist also nicht erforderlich, dass die I/O - Permission - Bit - Map alle I/O - Adressen abdeckt. Allen von der Map nicht erfa├čten I/O - Ports wird automatisch ein gesetztes Bit zugeordnet, d.h. ein Zugriff auf die au├čerhalb der Map liegenden Ports f├╝hrt automatisch zu einer Exception. In einem ISA - PC reicht es z.B. aus, die 3ffh niederwertigsten Ports durch eine Map abzudecken. Ein Zugriff auf Ports mit h├Âheren Adressen l├Âst eine Exception aus. Das erm├Âglicht neben der Schutzwirkung von Programmen und dem System eine wesentlich genauere Lokalisierung von Bugs.

Zu beachten ist, dass 16 - Bit - Ports zwei und 32 - Bit - Ports vier aufeinanderfolgende Bits zugeordnet sind. Nur wenn beide bzw. alle vier zugeordneten Bits gleichzeitig gel├Âscht sind, kann der i386 die I/O - Operation fortsetzen. Ist auch nur eines der Bits gleich 1, so l├Âst der Prozessor eine Exception aus.

Beispiel

Die Bit - Map lautet: {11111111} [11001101 00110000 11010100]
{Ende der Map} [I/O - Permission - Bit - Map]

Fall: 8 - Bit - Ports
Gesch├╝tzt sind die Ports 2, 4, 6, 7, 12, 13, 16, 18, 19, 22, 23
Ungesch├╝tzt sind die Ports 0, 1, 3, 5, 8, 9, 10, 11, 14, 15, 17, 20, 21
Fall: 16 - Bit - Ports
Gesch├╝tzt sind die Ports 2, 4, 6, 12, 16, 18, 22
Ungesch├╝tzt sind die Ports 0, 8, 10, 14, 20
Fall: 32 - Bit - Ports
Gesch├╝tzt sind die Ports 0, 4, 12, 16, 20
Ungesch├╝tzt ist der Port 8


3.9. Exceptions im Protected Mode


Im Protected Mode sind gegen├╝ber dem Real Mode weitere Exceptions m├Âglich, die in erster Linie Fehlerbedingungen anzeigen, deren Ursache in einer Verletzung der Schutzbedingungen des Protected Mode liegt.
Im folgenden eine Auflistung der neuen Exceptions:
    Zweifacher Fehler (Exception 8) Treten zwei Exceptions hintereinander auf und ist es dem i386 nicht m├Âglich, beide Exceptions hintereinander auszuf├╝hren, dann l├Âst er den Interrupt 8 aus. Der i386 kann die beiden Exceptions immer dann nicht sequentiell behandeln, wenn es sich um zwei der folgenden handelt: 0 (Division durch 0), 9 (Coprozessor - Segment├╝berlauf), 10 (ung├╝ltiges Task - State - Segment), 11 (Segment nicht vorhanden), 12 (Stack - Exception) oder 13 (Allgemeiner Protection - Fehler). Z.B. f├╝hrt also das Auftreten der Exceptions 11 und 13 zu einer Exception 8. Coprozessor - Segment├╝berlauf (Exception 9): Ist ein Teil des Coprozessoroperanden gesch├╝tzt oder nicht vorhanden, dann l├Âst der i386 einen Interrupt 9 aus. Ung├╝ltiges Task - State - Segment (Exception 10): Jeder Task - Switch mit einem ung├╝ltigen TSS l├Âst einen Interrupt 10 (0ah) aus. Die Ursache liegt in einer inneren Inkonsistenz des TSS (z.B. ist das durch CS bezeichnete Segment nicht ausf├╝hrbar, oder ein Selektor ├╝bersteigt das zugeh├Ârige Tabellenlimit). Segment nicht vorhanden (Exception 11): Wenn der i386 versucht, auf ein Segment zuzugreifen, das aus dem Speicher ausgelagert ist, d.h. bei dem das P - Flag gel├Âscht ist, dann ist ein Interrupt 11 (0bh) die Folge. Stack - Exception (Exception 12): Versucht ein Befehl, das Stack - Segmentlimit zu ├╝berschreiten oder ist das durch SS bezeichnete Segment z.B. nach einem Task - Switch im Speicher nicht vorhanden, dann l├Âst der i386 einen Interrupt 12 (0ch) aus. Allgemeiner Protection - Fehler (Exception 13): Erfa├čt der i386 eine Verletzung der Schutzregeln des Protected Mode, wobei sich die Ursache nicht einer der Exceptions 8 - 12 zuordnen l├Ąsst, dann ist ein Interrupt 13 (0dh) die Folge. Coprozessorfehler (Exception 16): Werden die Coprozessorfunktionen nicht vom i386 durch eine Software - Bibliothek emuliert und erfa├čt der i386 ein Fehlersignal vom Coprozessor, dann l├Âst er einen Interrupt 16 (10h) aus. Dieses Fehlersignal zeigt eine Fehlerbedingung im Coprozessor (z.B. einen ├ťber - oder Unterlauf) an.








4. Zusammenfassung der Schutzmechanismen des Protected Mode


Von den Schutzmechanismen im Protected Mode sind in erster Linie Befehle betroffen, die den Zustand der CPU steuern und lesen und auf Code - und Datensegmente zugreifen. Es soll verhindert werden, dass eine fehlerhafte oder inad├Ąquate Anweisung die CPU aufh├Ąngt oder blockiert (wie z.B. der HLT - Befehl) bzw. Daten - und Codesegmente in unsauberer Weise benutzt werden und dadurch die Systemintegrit├Ąt zerst├Ârt wird. Zu diesem Zweck sind drei Gruppen von Schutzmechanismen vorgesehen.

    Beschr├Ąnkte Nutzung von Segmenten: Beispielsweise k├Ânnen Codesegmente prinzipiell nicht und Datensegmente nur bei gesetztem Schreibbit (W) beschrieben werden. Alle ansprechbaren Segmente sind durch die GDT oder LDT beschrieben, die anderen Segmente sind nicht erreichbar.

    Beschr├Ąnkter Zugriff auf Segmente: Durch die verschiedenen Privilegierungsstufen und die Verwendung von CPL, DPL, EPL und RPL ist der Zugriff von Programmen einer bestimmten Privilegierungsstufe (CPL, RPL, EPL) auf Daten und Code anderer Segmente (DPL) beschr├Ąnkt. Ausnahmen sind nur durch zuverl├Ąssige Aufrufmechanismen (Call - Gates etc.) zul├Ąssig.

    Privilegierte Befehle: Befehle, die den Zustand der CPU unmittelbar beeinflussen (wie LGDT oder LLDT), oder die ├änderung der Deskriptortabellen sowie I/O - Operationen k├Ânnen nur von Programmen ausgef├╝hrt werden, deren CPL oder IOPL eine hohe Privilegierungsstufe angeben.

Verletzt im Protected Mode ein Vorgang einen dieser Schutzmechanismen, dann l├Âst der i386 sofort eine Fehler - Exception aus.

Zusammenfassung


Es ist also klar erkenntlich, dass der Protected Mode dem Real Mode bei weitem ├╝berlegen ist. Das ergibt sich alleine schon aus folgenden Vorteilen:
    Der gesamte Speicher steht zur Verf├╝gung Die Programme k├Ânnen vor einander gesch├╝tzt werden, wie auch das Betriebssystem vor den Programmen Der Zugriff auf externe Ressourcen (I/O - Adre├čraum) wird vom Betriebssystem alleine ├╝berwacht Multitasking wird vom Prozessor aktiv unterst├╝tzt


Obwohl die gesamte Materie ziemlich komplex ist, glaube ich doch, dass es mir gelungen ist, sie verh├Ąltnism├Ą├čig einfach wiederzugeben. Mit der Kenntnis all dieser Mechanismen war es mir m├Âglich, das beiliegende Programm zu erstellen. Es soll nur einen Einblick in die M├Âglichkeiten der Programmierung zur N├╝tzung dieser brachliegenden Ressourcen zeigen. Ein genaueres Eingehen auf diese Mechanismen w├╝rde ein um vieles komplexeres (sowie l├Ąngeres) Programm erfordern. Vor allem ist es nicht m├Âglich, diese Aufgabe ohne reichlichen Einsatz von Assembler zu erf├╝llen, was nicht mein Ziel war.
Durch die ohnehin schon ordentliche Gr├Â├če der Arbeit habe ich mich entschlossen, den Virtual - 8086 - Mode nur zu erw├Ąhnen, da seine eingehende Behandlung wiederum einige Dutzend Seiten ben├Âtigt, was den Rahmen dieser Arbeit sprengen w├╝rde. Ebenso habe ich vielfach den SystemManagementMode verschwiegen, der jedoch ohnehin kaum verwendet wird und nur sehr geringe und eingeschr├Ąnkte Bedeutung besitzt.
Das Programm ist etwas k├╝rzer geworden, als eigentlich vorgesehen, doch war es, wie oben erw├Ąhnt, niemals der Sinn der Arbeit, ein Betriebssystem oder eine Ansammlung von Assemblerroutinen zu verfassen (man m├Âge mir die Anwendung einiger Assembler - Befehle im Programm verzeihen, doch f├╝r diese existieren keine C++ - Funktionen, es gibt also keine andere M├Âglichkeit). Ich glaube, dass das Programm die wesentlichen Punkte ganz gut illustriert.
Als Referenzmaterial standen mir die Intel - Dokumente ├╝ber den Pentium PRO zur Verf├╝gung, denen ich s├Ąmtliche Daten entnahm. N├Ąher an der Quelle zu sitzen ist wohl kaum m├Âglich. Die Bilder entstammen ebenfalls allesamt diesen Dokumenten. N├Ąhere Hinweise dazu im Quellenverzeichnis.
Ich m├Âchte abschlie├čend noch meinen Eltern f├╝r die Unterst├╝tzung daheim beim Arbeiten und Herrn Professor M├╝hlegger f├╝r seine Hilfestellung danken.



5. Glossar

5.1. Allgemein


    Assembler: Ein Programm, das Mnemonics in Maschinensprache umwandelt; eine Programmiersprache, deren Elemente eben die Mnemonics sind. Bug: Ein Programmfehler, der oft zum kompletten Absturz f├╝hrt Call: Der Aufruf einer Prozedur. Compiler: Ein Programm, das in h├Âheren Programmiersprachen geschriebenen Source - Code in Assembler oder Maschinensprache umschreibt. Debug(ging): Das ├ťberpr├╝fen eines Programmes auf Fehler und die anschlie├čende Lokalisation der Fehlerursachen sowie deren Beseitigung. Deskriptor: Ein 128 - Bit - Segmentdeskriptor beschreibt das Segment genauer (Lokation, Zugriffsbestimmungen etc.). Deskriptoren befinden sich in den Deskriptortabellen. Far: Au├čerhalb des selben Segments. Gate: Erlaubt die Ausf├╝hrung von Prozeduren, die normalerweise nicht zug├Ąnglich sind. Ein Gate definiert einen genauen Einstiegspunkt in eine Routine, deshalb ist diese Art der Befehls├╝bergabe sicherer als ein einfacher Far - Jump. H├Âherwertig: Die oberste Teil eines Speicherobjekts (z.B. 0x12345678 => das h├Âherwertige Byte ist 12, Wort ist 1234) Intel - Architecture: Sammelname f├╝r die Prozessoren der 80x86 Familie, n├Ąmlich den 8086, 8088, 80186, 80286, i386, i486, Pentium, Pentium Pro und Pentium II. Interrupt - Handler: Das Programm, das aufgerufen wird, sobald ein Interrupt ausgel├Âst wird, und dessen Aufgabe es ist, die Ursache des Interrupts zu behandeln. Interrupt - Vektor: Ein Zeiger auf den Interrupt - Handler des jeweiligen Interrupts. Jump: Ein Sprung von einer Stelle des Programmes an eine andere. Kernel: Der kritische Teil eines Betriebssystems, meist die Speicher - und Task - Verwaltung. Linearer Speicher: Durch Paging wird der gesamte Adre├čraum des Computers abgebildet, obwohl viel weniger RAM eingebaut ist. Linear meint, dass der gesamte Speicher angesprochen werden kann. Dabei m├╝ssen jedoch aufeinanderfolgende Bytes nicht physikalisch aufeinanderfolgen. Logischer Speicher: Der durch Segmente beschriebene Speicher. Zumeist nur RAM, er unterscheidet sich ohne Paging nicht vom linearen Speicher. Mnemonic: Der uns verst├Ąndliche Name eines Maschinensprache - Befehls. Multitasking: Das gleichzeitige Ablaufen mehrerer Tasks (z.B. Word, die CD - Wiedergabe und Excel). Near: Innerhalb des selben Segments. Niederwertig: Der unterste Teil eines Speicherobjekts (z.B. 0x12345678 => das niederwertige Byte ist 78, Wort ist 5678) Offset: Der Offset gibt an, wie weit ein Speicherobjekt vom Beginn des aktiven Segments entfernt ist. Overflow (├ťberlauf): Ein Ergebnis wird so gro├č, dass es nicht mehr in das daf├╝r vorgesehene Register pa├čt. Page: Eine Speicherseite (in der Intel - Architecture 4kByte gro├č). Paging: Das zerteilen des Speichers in gleich gro├če Teile (Pages), die leichter auszulagern sind. Physikalischer Speicher: Der wirklich eingebaute Speicher, RAM sowie ROM. Physikalische Adressen entsprechen selten logischen oder linearen. Pointer: Ein Zeiger, der eine bestimmte Speicherstelle angibt. Segment: Der Speicher ist in Segmente zerlegt. Ein Segment beginnt an einer bestimmten Stelle und hat eine gewisse L├Ąnge. Im Real Mode sind alle Segmente gleich lang, im Protected Mode h├Ąngt dies vom Betriebssystem ab. Segmentpr├Ąfix: Der Prozessor codiert die Art des verwendeten Segments als Pr├Ąfix des Befehls. Selektor: Ein 16 - Bit - Segmentselektor gibt den Offset des Deskriptors in der jeweiligen Deskriptortabelle an. Nur durch Selektoren im Segmentregister kann auf die verschiedenen Segmente zugegriffen werden. Stack: Der einem Programm als Zwischenspeicher zur Verf├╝gung gestellte Speicher. Dort finden sich die lokalen Variablen sowie die Aufrufsparameter der verschiedenen Prozeduren. ├ťber die Mnemonics PUSH und POP kann auf den Stack zugegriffen werden. Stack - Frame: Ein Stack - Frame dient der aufgerufenen Prozedur dazu, die ├╝bergebenen Parameter auf dem Stack anzusprechen Stack - Overflow ( - ├ťberlauf): Der dem Stack zur Verf├╝gung stehende Speicher wurde vollst├Ąndig verbraucht. Es ist kein Platz f├╝r weitere Variablen. Swapping: Das Auslagern von Speicher( - segmenten) auf einen Massenspeicher (Festplatte etc.). Task: Ein Programm, mit allen von ihm beanspruchten Ressourcen (z.B. Word mit allen ge├Âffneten Fenstern). Task - Switching: Das Umschalten von einem Task zum n├Ąchsten. Underflow (Unterlauf): Ein Ergebnis wird so klein, dass es nicht mehr in das daf├╝r vorgesehene Register pa├čt. Virtual Memory: Virtueller Speicher ist die Menge an Speicher, die ein Computer zur Verf├╝gung stellt, der physikalischen Speicher auslagert, d.h. die Daten aus dem Speicher auf Festplatte sichert. Dadurch kann der zur Verf├╝gung gestellte Speicher nahezu unbegrenzt erweitert werden.

5.2. Programmiertechnisch



    ADD: Addiert den ersten und den zweiten Operanden Byte: 8 Bit CALL: Ruft die angegebene Prozedur auf CLD: L├Âscht das D - Flag CLI: L├Âscht das IE - Flag CMP: Vergleicht die beiden Operanden Double - Word (Doppelwort): 32 Bit (4 Byte; 2 Words) IN(S): Einlesen aus dem I/O - Adre├čraum JE: Jump if Equal JNE: Jump if not Equal LGDT: L├Ądt das GDTR mit dem angegebenen Operanden LIDT: L├Ądt das IDTR mit dem angegebenen Operanden LLDT: L├Ądt das LDTR mit dem angegebenen Operanden LMSW: L├Ądt das MSW mit dem angegebenen Operanden LOOP: Zur Programmierung von Schleifen mem32: ein Speicheroperand mit 32 Bit Gr├Â├če mem64: ein Speicheroperand mit 64 Bit Gr├Â├če MOV: Kopiert den zweiten Operanden in den ersten MUL: Multipliziert den Operanden mit AX OUT(S): Ausgabe an den I/O - Adre├čraum POP: Nimmt den Operanden vom Stack POPA: Nimmt alle Vielzweckregister vom Stack POPF: Nimmt das Flag - Register vom Stack PUSH: Bringt den Operanden auf den Stack PUSHA: Bringt alle Vielzweckregister auf den Stack PUSHF: Bringt das Flag - Register auf den Stack Quad - Word (Vierfachwort): 64 Bit (8 Byte; 4 Words; 2 Double - Words) reg16: Ein Registeroperand mit 16 Bit Gr├Â├če reg32: Ein Registeroperand mit 32 Bit Gr├Â├če RET: Kehrt aus einer Prozedur zur├╝ck SGDT: Speichert das GDTR im angegebenen Operanden SIDT: Speichert das IDTR im angegebenen Operanden SLDT: Speichert das LDTR im angegebenen Operanden SMSW: Speichert das MSW in den angegebenen Operanden STD: Setzt das D - Flag STI: Setzt das IE - Flag Word (Wort): 16 Bit (2 Byte)

V. Anhang


6. Quellenverzeichnis


    Intel Architecture Software Developer’s Manual, Volume 1: Basic Architecture, 1997, Number 243190 Intel Architecture Software Developer’s Manual, Volume 2: Instruction Set Reference, 1997, Number 243191 Pentium Pro Family Developer’s Manual, Volume 3: Operating System Writer‘s Guide, December 1995, Number 242692 PC Intern 4 - Systemprogrammierung, 1994, ISBN 3 - 8158 - 1094 - 9


7. Arbeitsprotokol


    15.9.97 Besprechung des Themas "Die Bedeutung des Protected Mode und seine Funktionsweise, sowie die Programmierung einer Bibliothek zur Nutzung desselben in C++". 16.9.97 Konkretisierung des Themas: praktischer - theoretischer Teil 19.9.97 Zeitplan festgelegt; Gliederung sowie Zitierregeln besprochen; 23.9.97 Festsetzen der Gliederung 30.1.98 Abgabe erster Textteile (Kapitel 1) 06.2.98 Besprechung über Änderungen im Programm 24.2.98 Vorlage der fast fertigen Arbeit 25.2.98 Vorlage des Referenzmaterials 27.2.98 Abgabe der entgültigen Version


8. pmlib.cpp



#define version 1.0 //version number

//**************************** Headers ***********************
#include
#include

//********************** Global Constants **********************
#define PIC_MASTER 0x20 //The Master ProgrammableInterruptController //Main I/O - Port
#define PIC_SLAVE 0xa0 //The Slave PIC Main I/O - Port
#define GDT_ENTRIES 2 //The number of entries in the GDT
#define true 1 //for use with the variable type bool
#define false 0 // - " -

//********************** Global Type Definitions **********************
typedef unsigned short int bool; //boolean type, false or true
typedef unsigned char byte; //byte type, 8 bit unsigned
typedef unsigned short int word; //word type, 16 bit unsigned
typedef unsigned long int dword; //double - word type, 32 bit unsigned
typedef unsigned char u8; //u8 type, 8 bit unsigned
typedef unsigned short int u16; //u16 type, 16 bit unsigned
typedef unsigned long int u32; //u32 type, 32 bit unsigned
typedef signed char s8; //s8 type, 8 bit signed
typedef signed short int s16; //s16 type, 16 bit signed
typedef signed long int s32; //s32 type, 32 bit signed

//* Enums for the following classes + Txts for use with describing them *******
enum {toGDT,toLDT}; //selector::TI()
char *ti[2]={"Global Descriptor Table","Local Descriptor Table"};
enum {SYSTEM,CODEDATA}; //descriptor::System()
char *s[2]={"System","Code/Data"};
enum {NPRESENT,PRESENT}; //descriptor::Present()
char *p[2]={"not present","present"};
enum {DS16,DS32}; //descriptor::DefaultSize()
char *ds[2]={"16 - Bit","32 - Bit"};
enum {BYTE,PAGE}; //descriptor::Granularity()
char *g[2]={"Byte","Page"};
char *f[2]={"allocated","free"};
enum {D_ro,D_roa,D_rw,D_rwa,D_roxd,D_roaxd,
D_rwxd,D_rwaxd,C_xo,C_xoa,C_xr,C_xra,C_xoc,C_xoac,C_xrc,C_xrac};
//descriptor::Type() if system==0
char *cdtype[16]={"Data Read - Only","Data Read - Only Accessed","Data Read/Write","Data Read/Write Accessed","Data Read - Only Expand - Down","Data Read - Only Expand - Down Accessed","Data Read/Write Expand - Down","Data Read/Write Expand - Down Accessed","Code Execute - Only","Code Execute - Only Accessed","Code Execute/Read","Code Execute/Read Accessed","Code Execute - Only Conforming","Code Execute - Only Conforming Accessed","Code Execute/Read Conforming","Code Execute/Read Conforming Accessed"};
enum{res0,TSS16,LDT,TSS16b,CG16,TG,IG16,TrG16,res1,TSS32,res2,TSS32b,CG32,res3,IG32,TrG32}; //descriptor::Type() if system==1

//************* Classes for Protected - Mode - Memory - Management **************************

class pseudo_descriptor { //Pseudo_descriptor to be used with LGDT/LIDT when //initializing, 48 bit
public:
word limit; //word:limit of the descriptor table (in bytes)
dword base; //dword:base of the descriptor table (physical address)
pseudo_descriptor(word l=0,dword b=0) {Limit(l);Base(b);}; //constructor
void Limit(word l) {limit=l;}; //for accessing
word Limit(void) {return limit;};
void Base(dword b) {base=b;}; //for accessing
dword Base(void) {return base;};
void ShowAll(void){cout<<"Base: "< }; //for status information

class selector { //segment selector, 16 bit
public:
word sel; //word: selector for a segment in Protected Mode
selector(bool t=toGDT,byte rpl=0,word index=0) {TI(t);RPL(rpl);Index(index);}; //constructor

byte RPL(void) {return sel&3;}; //for accessing
void RPL(byte x) {sel&=0xfffc;sel|=x&=3;};
bool TI(void) {return (sel&4)>>2;}; //for accessing
void TI(bool x) {sel&=0xfffb;sel|=((x&1)<<2);};
word Index(void) {return (sel&0xfff8)>>3;}; //for accessing
void Index(word x) {sel&=7;sel|=((x&0x1fff)<<3);};
void ShowAll(void) {cout<<"TI:"<<(word)TI()<<" RPL:"<<(word)RPL()<<" Index:"< }; //Status information, as usual


class descriptor { //segment descriptor, 64 bit
public:
dword l; //low double - word
dword h; //high double - word
descriptor(dword base=0,dword limit=0x3fff,byte dpl=3,bool system=CODEDATA,byte type=D_rw,bool free=true,bool present=true,bool ds=DS32,bool granularity=BYTE)
{Base(base);Limit(limit);DPL(dpl);System(system);Type(type);Free(free);
Present(present);DefaultSize(ds);Granularity(granularity);};
//constructor

//for accessing the various fields of the segment - descriptor
dword Limit(void) {return (l&0xffff)|(h&0xf0000);};
void Limit(dword x) {l&=0xffff0000;l|=(x&0xffff);h&=0xfff0ffff;h|=(x&0xf0000);};
dword Base(void) {return ((l&0xffff0000)>>16)|((h&0xff)<<16)|(h&0xff000000);};
void Base(dword x) {l&=0xffff;l|=(x&0xffff)<<16;h&=0xffff00;h|=(x&0xff0000)>>16;h|=(x&0xff000000);};
void Type(byte x) {h&=0xfffff0ff;h|=(x&0xf)<<8;};
byte Type(void) {return (h&0xf00)>>8;};
void DPL(byte x) {h&=0xffff9fff;h|=(x&3)<<13;};
byte DPL(void) {return (h&0x6000)>>13;};
void System(bool x) {h&=0xffffefff;h|=(x&1)<<12;};
bool System(void) {return (h&0x1000)>>12;};
void Present(bool x) {h&=0xffff7fff;h|=((dword)x&1)<<15;};
bool Present(void) {return (h&0x8000UL)>>15;};
void DefaultSize(bool x) {h&=0xffbfffff;h|=((dword)x&1)<<22;};
bool DefaultSize(void) {return (dword)(h&0x400000)>>22;};
void Granularity(bool x) {h&=0xff7fffff;h|=((dword)x&1)<<23;};
bool Granularity(void) {return (h&0x800000UL)>>23;};
void Free(bool x) {h&=0xffefffff;h|=((dword)x&1)<<20;};
bool Free(void) {return (h&0x100000)>>20;};

void ShowAll(void) {cout<<"Limit:"< };


//******* Procedures for I/O and for disabling/enabling interrupts ************
//enums to be used with set/get_interrupt_mask()
enum{INT0=1,INT1=2,INT2=4,INT3=8,INT4=16,INT5=32,INT6=64,INT7=128,INT8=256,INT9=512,INTA=1024,INTB=2048,INTC=4096,INTD=8192,INTE=16384,INTF=23768};

u8 inpb(u16 port) //byte input from
{
asm {
push dx
mov dx,port
in al,dx
pop dx
}};

u16 inpw(u16 port) //word input from
{
asm {
push dx
mov dx,port
in ax,dx
pop dx
}};

void outpb(u16 port,u8 value) //byte output to
{
asm {
push dx
mov dx,port
mov al,value
out dx,al
pop dx
}};

void outpw(u16 port,u16 value) //word output to
{
asm {
push dx
mov dx,port
mov ax,value
out dx,ax
pop dx
}};

u8 low(u16 value) //returns lower byte of word
{
asm mov ax,value
};

u8 high(u16 value) //returns higher byte of word
{
asm mov ax,value
asm mov al,ah
};

u16 get_interrupt_mask(void) //returns interrupt_mask_register from PIC
{ //shows which interrupts are disabled
asm cli
u16 intmask=((u16)inpb(PIC_SLAVE+1))<<8 | inpb(PIC_MASTER+1);
asm sti
};

void set_interrupt_mask(u16 mask) //sets the interrupt_mask_register in the PIC
{
asm cli
outpb(PIC_MASTER+1,low(mask));
outpb(PIC_SLAVE+1,high(mask));
asm sti
};

pseudo_descriptor sgdt(void) //returns limit and base of Global Descriptor Table
{
pseudo_descriptor x;
asm sgdt x;
return x;
};

void lgdt(pseudo_descriptor x) //sets limit and base of Global Descriptor Table
{
asm lgdt x;
};

void lidt(pseudo_descriptor x) //sets limit and base of Interrupt Descriptor Table
{
asm lidt x;
};

pseudo_descriptor sidt(void) //returns limit and base of Interrupt Descriptor Table
{
pseudo_descriptor x;
asm sidt x;
return x;
};

u32 physical(u32 x) //translates segment:offset (C++ far //pointer) to physical addresses
{
return ((x&0xffff0000)>>12)+(x&0xffff);
};


void main(void)
{
clrscr();
cout<<"Example of the use of pmlib.cpp:"< cout<<"Generating GDT......"< descriptor *gdt=new descriptor[GDT_ENTRIES]; //initialize GDT
cout<<"Initializing GDT[1]......."< gdt[1].Base(0x0); //initialize Entry number 1
gdt[1].Limit(0xfffff);
gdt[1].Granularity(PAGE);
gdt[1].System(CODEDATA);
gdt[1].Type(C_xr);
gdt[1].DPL(0);
gdt[1].DefaultSize(DS16);
gdt[1].Present(PRESENT);
cout<<"Generating Pseudo - Descriptor for GDT......."< pseudo_descriptor gdt_pseudo_descriptor(8*GDT_ENTRIES,physical((u32)gdt)); //Initialize gdt_pseudo_descriptor to load GDTR
cout<<"Generating Selector for GDT[1]........"< selector cs_selector(toGDT,0,1); //form selector for segment number 1 in GDT
cout< cout<<"Entry Number One in the Global Descriptor Table: "< gdt[1].ShowAll();
cout< cs_selector.ShowAll();
cout< gdt_pseudo_descriptor.ShowAll();
cout<<"************************** Raw Data **************************"< cout<<"Descriptor: 0x"< cout<<"Selector: 0x"< cout<<"Pseudo - Descriptor: 0x"< cout< getch();
};

VI. Index






9. Endnoten


Abk├╝rzungen in den Endnoten:
[BasArch] Basic Architecture, Intel Software Developer’s Manual Volume 1
[InsSR] Instruction Set Reference, Intel Software Developer’s Manual Volume 2
[OSWG] Operating System Writer‘s Guide, Intel Software Developer’s Manual Volume 3
Quelle: Internet
[1] Cf. [BasArch], Kapitel 3.1
[2] Cf. [BasArch], Kapitel 2
[3] Cf. [BasArch], Seite 2 - 1
[4] Cf. [BasArch], Seite 2 - 1
[5] Cf. [BasArch], Seite 2 - 2
[6] Cf. [BasArch], Seite 2 - 2
[7] Cf. [BasArch], Seite 2 - 3
[8] Cf. [BasArch], Kapitel 3.6
[9] Cf. [BasArch], Kapitel 3.6.3
[10] Cf. [OSWG], Kapitel 2.5
[11] Cf. [OSWG], Kapitel 3; [BasArch], Kapitel 3.3
[12] Cf. [OSWG],Kapitel 3.4.2; [BasArch], Kapitel 3.7
[13] Cf. [BasArch], Kapitel 4.2, Kapitel 3.6
[14] Cf. [BasArch], Kapitel 5.3.3.2
[15] Cf. [BasArch], Kapitel 5; [OSWG], Kapitel 5
[16] Cf. [OSWG], Kapitel 3, Kapitel 4
[17] Cf. [OSWG], Kapitel 3.4
[18] Cf. [OSWG], Kapitel 3.5.1
[19] Cf. [OSWG], Kapitel 8
[20] Cf. [OSWG], Kapitel 3.4.3
[21] Cf. [OSWG], Kapitel 4.8.4
[22] Cf. [OSWG], Kapitel 5.7
[23] Cf. [OSWG], Kapitel 6
[24] Cf. [BasArch], Kapitel 3.6.4
[25] Cf. [OSWG], Kapitel 6.2.1

19490 Worte in "deutsch"  als "hilfreich"  bewertet