In English

Übersetzung von Philipp Strebl (viele danke!)

Was ist protected mode?

Die CPU des 8088 im ursprünglichen IBM PC war nicht besonders erweiterungsfähig. Vor allem gab es keinen einfachen Weg, auf mehr als ein MB physischen Speicher zuzugreifen. Um dieses Problem zu beseitigen und trotzdem Abwärtskompatibilität zu wahren, entwickelte Intel die 80286 CPU mit zwei Ausführungsmodi: dem real mode, in dem der 80286 sich wie ein schneller 8088 verhält, und den protected mode (heute als 16-bit protected mode bezeichnet). Protected mode erlaubt Programmen den Zugriff auf mehr als ein MB physischen Speicher und schützt gleichzeitig vor falscher Verwendung von Speicher (zum Beispiel der Ausführung eines data segment als code, oder dem Schreiben in ein code segment). Eine verbesserte Version, 32-bit protected mode, wurde mit der 80386 CPU präsentiert.


Was unterscheidet protected mode von real mode?

Unterschiede zwischen protected mode und real mode:

real mode 16-bit protected mode 32-bit protected mode
Segmentbasisaddresse 20-bit (1 MB range) = 16 * Segmentregister 24-bit (16 MB range), im descriptor 32-bit (4 GB range), im descriptor
Segmentgröße (höchstens) 16-bit, 64 KB (fix) 16-bit, 1 B - 64 KB 20-bit, 1 B - 1 MB oder 4 KB - 4 GB
Segmentschutz nein ja ja
Segmentregister segment base adr / 16 selector selector


Ich dachte protected mode kommt ohne segmentierten Speicher aus...

Die Segmente sind immer noch da, aber im 32-bit protected mode ist es möglich, das segment limit, d.h. die Grenze für die Größe eines einzelnen Segments, die im real mode fix bei 64 KB liegt, auf 4 GB zu setzen. Das ist das Maximum an physischem Speicher, das von einer CPU mit 32-bit address bus überhaupt addressiert werden kann. Dadurch "verschwinden" die Segmente, weil es nur ein (sehr großes) Segment mehr gibt (oder geben kann). Trotzdem bleiben andere Schutzmechanismen wirkungsvoll. Einzig und allein diese Tatsache machte 32-bit protected mode beliebt.


Was ist ein descriptor?

Im real mode gibt es wenig, was man über die Segmente wissen muss. Jedes ist 64 KB groß und man kann damit machen, was man will: Daten darin speichern, den Stack hineinplatzieren oder code ausführen, der im jeweiligen Segment gespeichert ist. Die base address des Segments ist einfach das 16-fache des Werts in einem der Segmentregister. Im protected mode brauchen wir neben der base address eines Segments auch seine maximale Größe und einige flags, die seinen Verwendungszweck angeben. Diese Information kommt in einen 8 bytes großen record mit dem klingenden Namen descriptor:

Der Aufbau eines code/data segment descriptor:

Niedrigstes byte byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 Höchstes byte
limit 7:0 limit 15:8 base 7:0 base 15:8 base 23:16 access flags, limit 19:16 base 31:24

Das ist ein 32-bit (80386) descriptor. Bei 16-bit (80286) descriptors sind die beiden höchsten bytes (limit 19:16 und base 31:24) auf 0 gesetzt. Das access-byte gibt die Verwendung des Segments an (data segment, stack segment, code segment etc.).

Der Aufbau eines access byte:

Höchstes bit present present bit: Muss auf 1 gesetzt sein, um den Zugriff auf das Segment zu gestatten
bits 6, 5 privilege privilege bits: 0 ist das höchste Niveau an privilege (Ring 0), 3 ist das niedrigste (Ring 3)
bit 4 1 für code und data/stack segments. Wenn nicht gesetzt, handelt es sich um ein system segment.
bit 3 executable executable bit: Wenn dieses bit als Wert 1 hat, ist dieses Segment code segment, sonst handelt es sich um ein stack/data segment
bit 2 expansion direction/conforming expansion direction (stack/data segment): Wenn dieses bit den Wert 1 hat, wächst das Segment (den Adressen nach) nach unten und offsets müssen größer als das limit sein.
conforming (code segment): steht in Verbindung mit privilege
bit 1 writable/readable writable (stack/data segment): Wenn dieses bit den Wert 1 hat, kann in das Segment geschrieben werden
readable (code segment): Wenn dieses bit den Wert 1 hat, kann von diesem Segment gelesen werden (in code segments kann nicht geschrieben werden)
Niedrigstes bit accessed accessed bit: Dieses bit wird gesetzt wann immer aus dem Segment gelesen oder in das Segment geschrieben wird.

Der Wert des 4-bit flag in byte 6 eines descriptor ist nur für 32-bit Segmente nicht 0:

Highest bit Bit 6 Bit 5 Bit 4
Granularity Default Size 0 0

Das granularity bit (G) entscheidet, ob das segment limit in Einheiten zu 4 KB Seiten (G=1) oder ob es in Einheiten zu einem byte (G=0) angegeben ist.

Im Falle der Verwendung eines Segments als stack segment wird das default size bit (D) auch als B (Big) bit bezeichnet und legt fest, ob 16- oder 32-bit Werte auf den Stack gelegt werden (mit den Anweisungen push und pop). Wird das Segment als code segment verwendet, gibt das D bit an, ob Anweisungen prinzipiell in 16-bit (D=0) oder 32-bit (D=1) Einheiten verarbeitet werden. Um etwas näher darauf einzugehen: Ist das D bit gesetzt, dann ist das Segment USE32, benannt nach der Assemblerdirektive mit demselben Namen. Die folgende Sequenz von hexadezimalen bytes:

B8 90 90 90 90

wird von der CPU als eine 32-bit Anweisung betrachtet und würde in Assembler folgendermaßen lauten:

mov eax, 90909090h

Ist das D bit nicht gesetzt, ist das Segment USE16, also 16-bit code, und die gleiche Sequenz von bytes würde wie folgt interpretiert:

mov ax, 9090h
nop
nop

Zwei spezielle opcode bytes, nämlich das operand size prefix und das address length prefix kehren die Wirkung des D bit für das Ziel beziehungsweise für die Quelle der unmittelbar folgenden Anweisung um.

Bit 4 des access byte ist gesetzt, wenn ein code oder data/stack segment vorliegt. Wenn das bit den Wert 0 hat, handelt es sich um ein system segment. Davon gibt es wiederum verschiedene Varianten:

task state segment (TSS) Wird zur Vereinfachung des multitasking verwendet. Die CPU vom 80386 aufwärts kennt vier Untertypen des TSS.
local descriptor table (LDT) Hier können tasks anstatt im GDT ihre eigenen privaten descriptors speichern.
gate Kontrolliert Übergänge der CPU von einem privilege-Niveau zu einem anderen. gate descriptors haben einen anderen Aufbau als die übrigen descriptors.

Aufbau eines gate descriptor:

Niedrigstes byte byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 Höchstes byte
offset 7:0 offset 15:8 selector 7:0 selector 15:8 word count 4:0 access offset 23:16 offset 31:24

Beachten Sie das selector-Feld. gates arbeiten über Umwege und benötigen einen eigenen code oder ein eigenes TSS, um zu funktionieren.

Der Aufbau des access byte eines system segment descriptor:

Höchstes bit bits 6, 5 bit 4 bits 3, 2, 1, 0
Present Privilege 0 Type

Die unterschiedlichen system segment-Typen:

0 ungültig 8 ungültig
1 verfügbares '286 TSS 9 verfügbares '386 TSS
2 LDT 10 undefiniert, reserviert
3 belegtes '286 TSS 11 belegtes '386 TSS
4 '286 call gate 12 '386 call gate
5 task gate 13 undefiniert, reserviert
6 '286 interrupt gate 14 '386 interrupt gate
7 '286 trap gate 15 '386 trap gate

So. Wenn das Verwirrung stiften sollte: Es reicht vorerst vollauf, wenn Sie sich merken, dass die Haupttypen von system segments TSS (task state segment), LDT (local descriptor table) und gates sind.


Wo befinden sich die descriptors?

Die descriptors sind in einer Tabelle, dem global descriptor table (GDT), dem interrupt descriptor table (IDT) oder in einem der local descriptor tables (LDT) aufgelistet. Die CPU verfügt über 3 Register, nämlich den GDTR, den IDTR (wenn interrupts benutzt werden) und den LDTR (wenn ein LDT benutzt wird) - das "R" am Ende jeder Bezeichnung steht für "Register". Diese Register müssen jeweils die Addresse des GDT, des IDT beziehungsweise des LDT beinhalten. Jede descriptor-Tabelle kann bis zu 8192 descriptors enthalten (eine Tabelle ist daher 64 KB gro0).


Was ist ein Selektor?

Im protected mode enthalten die Segmentregister selectors, die auf einen descriptor tables verweisen. Nur die höchsten 13 bits des selector werden für diesen Verweis gebraucht, das nächstniedrigere bit wählt zwischen dem GDT und dem LDT aus. Die beiden niedrigsten bits legen ein privilege-Niveau fest (0-3).


Wie aktiviere ich den protected mode?

Den protected mode zu aktivieren ist eigentlich recht einfach. Man muss:


Wie komme ich zurück in den real mode?

Dazu muss man am '386:

Vor dem Zurückschalten in den real mode müssen CS und SS selectors enthalten, die auf "real mode-geeignete" descriptors verweisen. real mode-geeignet bedeutet, dass sie ein limit von 64 KB haben, byte-granular sind, d.h. das flags nibble=0, das sie nach oben wachsen, beschreibbar (gilt nur für data/stack segments) und present sind (access byte=1xx1001xb).

Am '286 kann man nicht einfach das PE bit auf 0 setzen, um den protected mode zu verlassen. Die einzige Möglichkeit ist, die CPU neu zu starten. Das kann geschehen indem man dem keyboard controller die Tastenkombination zum Warmstart übergibt oder indem man die CPU triple-faulted (siehe die Website von Robert Collins).


Welche Fallen gibt es?


Wenn Sie einfach beginnen wollen, versuchen Sie es mit diesen Ratschlägen: