Computerhardware: Prozessor: Details: Protected Mode
Real Mode
[Bearbeiten]Der Real Mode gehört eigentlich nicht in dieses Thema, doch es soll hier als Vergleich dienen.
Speicheradressierung
[Bearbeiten]Im Real Mode ist der Speicher in 65536 "Frames" (zu je 64 kB) eingeteilt. Diese Frames beginnen in einem Abstand von 16 Bytes. So können insgesamt 1MB adressiert werden. Die Adresse wird dann wie folgt gebildet:
Segment:Offset
Segment gibt die Nummer der Frame (16 bit) an und kann in eines der Segmentregister (cs, ds, ss, es, fs, gs) geladen werden. Man kann die absolute Adresse so berechnen:
ssss ssss ssss ssss : xxxx xxxx xxxx xxxx ssss ssss ssss ssss + xxxx xxxx xxxx xxxx
Die Segmentadresse wird um 4 bits nach links verschoben und mit dem Offset (16 bit) addiert.
Protected Mode
[Bearbeiten]Einleitung
[Bearbeiten]Da im Real Mode nur 1MB adressiert werden kann und keine Schutzmechanismen existieren, gibt es seit dem Intel 80286-Prozessor den Protected Mode im 16-Bit-Modus. Ab dem Intel 80386 funktioniert der Protected Mode auch im 32-Bit-Modus.
Man kann ihn auf zwei Weisen aktivieren:
1. Man setzt das 1. Bit im CR0-Register (PE-Bit / protection enable)
mov eax, cr0 or eax, 1 mov cr0, eax
Achtung: Man kann auf das CR0-Register nur mit mov-Befehlen zugreifen.
2. Man setzt das 1. Bit im "machine status word" (Maschinenstatuswort)
smsw ax or ax, 1 lmsw ax
Bevor man den Protected Mode aktiviert, sollte man die 20. Addressleitung (A20) einschalten, da sonst auf den Speicher über 1MiB nicht zugegriffen werden kann. Dies kann über den Keyboard-Controller geschehen.
call empty_8042 ; Schreib-Kommando senden mov al, 0xd1 out 0x64,al call empty_8042 ; A20 aktivieren mov al, 0xdf out 0x60, al call empty_8042 ... empty_8042: ; delay out 0x80, al ; warte bis Input-Puffer leer in al, 0x64 test al, 2 jnz empty_8042 ret
Speicheradressierung
[Bearbeiten]Es sind jetzt keine festen Frames sondern Segmente vorhanden. Diese werden in sogenannten Deskriptortabellen mit Beginn, Größe und Zugriffsrechten festgelegt. Die absolute Adresse bildet sich nun aus der Summe des Segmentbeginns und des Offsets. Das Offset muss aber kleiner als die Segmentgröße sein. In die Segmentregister werden jetzt die Selektoren (siehe unten) geladen.
Privilegstufen
[Bearbeiten]Damit ein Anwendungsprogramm nicht die gleichen Rechte wie das Betriebssystem hat, sind im Protected Mode vier Privilegstufen definiert. Die geringsten Rechte hat ein Programm mit der Privilegstufe 3 und die höchsten mit 0. Die Privilegstufe wird im Selektor des Codesegments festgelegt (CPL = Current Privileg Level). Diese Privilegstufen werden auch als Ringe bezeichnet.
Deskriptoren
[Bearbeiten]Man unterscheidet zwischen Datensegment- und Systemdeskriptoren. Die Datensegmentdeskriptoren beschreiben ein Daten- oder Codesegment im Speicher. Ein Systemdeskriptor gibt Gates, LDTs, TSSs u.v.m. an. Ein Deskriptor ist beim Intel 80386 8 Byte groß.
Deskriptortabellen
[Bearbeiten]Damit auf Dekriptoren zugegriffen werden kann, müssen sie in einer Tabelle abgelegt werden. Es gibt drei verschiede Arten: Globale Deskriptor Tabelle (global descriptor table GDT), Interrupt Deskriptor Tabelle (interrupt descriptor table IDT) und dir Lokale Deskriptor Tabelle (local descriptor table LDT). Auf die GDT kann jeder Prozess zugreifen, aber es kann für jeden Prozess eine eigene LDT angelegt werden. Für eine LDT muss ein Eintrag in der GDT existieren, welcher den Beginn bzw. das Ende der LDT beschreibt.
Selektoren
[Bearbeiten]Selektoren selektieren einen Eintrag in einer GDT bzw. LDT. Sie sind 16 Bit groß. Die ersten beiden Bits stehen für die angeforderte Privilegstufe (RPL = <engl.> requested privileg level). Sie wird mit der DPL (= descriptor privileg level) im Deskriptor verglichen. Die RPL muss kleiner oder gleich der DPL sein um auf den Deskriptor zuzugreifen. Damit ist der Schutz der einzelnen Ringe gewährleistet. Die RPL im Codesegmentselektor gibt die Privilegstufe des Prozesses an.
Gates
[Bearbeiten]Damit ein Programmteil im Ring 3 (Anwendungsprogramm) Prozeduren des Betriebssystemteils (Ring 0) benutzten kann, müssen sogenannte Gates (Gate = <engl.> Tor) definiert werden. Wenn man versucht, über normale "CALL"-Befehle die Prozeduren aufzurufen, ist das ein klarer Verstoß gegen das Schutzkonzept. Mit Gates lassen sich so Prozeduren in anderen Ringen mit anderer Privilegstufe aufrufen. Die Gates können in allen Deskriptortabellen liegen, denn auch bei einem Interrupt muss die Privilegstufe geändert werden (Interruptbehandlungroutinen im Betriebssystem). Außerdem werden bei vielen Systemen die Systemaufrufe über Interrupts getätigt (Linux int 0x80, MenuetOS int 0x40, DOS int 0x21 ...).
Interrupts und Exceptions
[Bearbeiten]Um das Betriebssystem über einen Fehler im Programm, eine Hardewaremeldung ... zu unterrichten werden Interrupts (to interrupt = <engl.> unterbrechen) ausgelöst. Man unterscheidet zwischen externen (Hardwareinterrupts) und internen ("INT"-Befehle, Exception ...) Interrupts. Es existiert eine Tabelle, in der vermerkt ist, wo die Codeausführung fortgesetzt werden soll, wenn ein Interrupt ausgelöst wird. Normalerweise sind dort Gates eingetragen. Diese Tabelle heißt Interrupt Descriptor Table, kurz IDT. Registriert die CPU einen Interrupt. schaut sie in der IDT nach, an welche Adresse (Segment und Offset) gesprungen werden soll. Die Prozedur, die an dieser Adresse steht, wertet den Interrupt aus und informiert andere Programme oder verändert irgendwelche Variablen ...
Bei einem PC ist aber die Verteilung der Interrupts durcheinander. So überschneiden sich ein Teil Hardwareinterrupts mit manchen Exceptions. Dafür gibt es den Interrupt Contoller Chip. Man kann ihn so programmieren, dass die Hardwareinterrupts in einen anderen Bereich verlagert werden (z.B. int 0x20-0x2F). Es gibt 2 Interrupt Controller Chips. Jeder verwaltet 8 Hardwareinterrupts. Einer der beiden ist "Master" (primär) und einer "Slave" (sekundär). Wenn der sekundäre Interrupt Controller Chip eine Interruptanfrage bekommt, sendet dieser eine Interruptanfrage an den primären. Dieser unterrichtet dann die CPU.
Tritt ein Fehler bei der Programmausführung auf, wird eine Exception ausgelöst (exception = <engl.> Ausnahme). Es gibt 16 Exceptions. Bei jeder wird ein Interrupt ausgelöst. Die Interrupts beginnen bei int 0 (Division durch Null) und endet bei int 0x0F. Bei manchen Exceptions wird ein Fehlercode (error code) auf dem Stack abgelegt. Dieser ist vor dem Rücksprung zu entfernen, da sonst eine weitere Exception auftritt.
Der Rücksprung erfolgt mittels dem "IRET"-Befehl.
Paging
[Bearbeiten]Früher hatte man nur kleine Speicher, der für mehrerer Anwendungsprogramme nicht ausreichte. Also hat man damals seine Programme in kleinere Stücke, die man auslagern kann, zerteilt und einzeln ausführt. Dies war zwar schon ein erster Ansatz für die Speicherauslagerung, aber noch nicht gut genug. Denn wenn man versucht zurück in einen anderen Programmteil zu springen, hat man ein Problem. Die Lösung besteht heute darin, dass der gesamte Speicher in einzelne sogenannte "Frames" unterteilt. Diese Frames werden zu einem linearen und virtuellen Speicher zusammengefügt. Das geschieht mit den "Pagetables" (page = <engl.> Seite, table = <engl.> Tabelle) und einem kleinen Stück Hardware, die die virtuellen Adressen in reelle umwandelt, MMU (Memory Manager Unit) genannt. Die Frames sind normalerweise 4 kB (4096 Bytes) groß. Daher kann es bei einem 32-Bit-Prozessor maximal 1048576 Frames geben, was einer Gesamtspeichergröße von 4 GB entspricht.
So wird die Berechnug der physikalischen Adresse in zwei Schritten durchgeführt. Als erstes übefrührt der Prozessor den angegebenen Selektor und die Offsetadresse in eine lineare Adresse. Die wird dann mithilfe der Pagetabellen in die physiklische Adresse umgerechnet.
Die Pagetabellen sind aus Einträgen zu je 4 Byte aufgebaut, die das folgende Format haben. Die Nummer der physikalischen Pageframe gibt die Frame an, die in den jeweiligen Speicherbereich eingeblendet werden soll. AVL (available) sind für das Betriebssystem frei verfügbar. D (dirty) zeigt an, dass ein Schreibzugriff auf die Seite getätigt wurde. A (accessed) zeigt einen allgemeinen Zugriff an. C und W sind nur beim 80486er definiert und können auf Null gesetzt werden. U (user) ist ein einfacher Schutzmechanismus. Ist das U-Bit gesetzt, dürfen nur Programme mit den Privilegstufen 0, 1 und 2 auf die Seite zugreifen. Ein nicht gesetztes R-Bit zeigt an, dass die Seite schreibgeschützt ist. P (present) ist der Indikator für eine vorhandene (also eingebundene) Seite.
/--------------------+---+-+-+-+-+-+-+-+-+-\ | Phys. Pageframe |AVL|0|0|D|A|C|W|U|R|P| \--------------------+---+-+-+-+-+-+-+-+-+-/
Multitasking
[Bearbeiten]Multitasking ist die parallele Ausführung mehrerer Prozesse (Tasks, Task = <engl.> Aufgabe). Eigentlich laufen die Prozesse quasi-parallel ab. D.h., dass sich die Prozesse so nach einer kurzen Zeitspanne abwechseln, dass es wie eine Parallelausführung dieser aussieht. Heutzutage ist Multitaskingfähigkeit eine wichtige Eigenschaft eines Betriebssystems. Im Protected Mode unterstützt der Prozessor dies.
Man unterscheidet zwischen prä-emptiven und kooperativen Multitasking. Beim prä-emptiven Multitasking wird der Prozess gezwungen den Prozessor (d.h. die Ausführung auf diesem) an einen anderen Prozess abzugeben. Dies geschieht meist durch einen Zeitgeberinterrupt, der den Prozess normalerweise vom Benutzermodus in den Systemmodus schaltet, damit das Betriebssystem den Scheduler aufrufen kann. Somit hat der Prozess eine bestimmte Ausführungszeit. Beim kooperativen Multitasking gibt der Prozess freiwillig den Prozessor, z.B. durch einen Systemaufruf, ab. Schlecht programmierte oder böswillige Programme haben somit die Möglichkeit das System anzuhalten.
Das Task State Segment (TSS)
[Bearbeiten]Wenn ein Prozess den Prozessor abgibt, verändert der nächste Prozess die u.a. Register. Damit der Prozess seine Arbeit wieder aufnehmen kann, muss der vorherige Prozesszustand wiederhergestellt werden. Um den Prozesszustand (Register) zu sichern muss ein bestimmter Speicherbereich im Speicher sein, in dem die Zustandsinformationen gesichert sind. So ein Speicherbereich wird Task State Segment (TSS) genannt. Der Deskriptor auf das TSS muss in der GDT liegen.
Der Intel-Prozessor (ab dem 386er) stellt ein Register bereit, dessen Wert ein Selektor auf den TSS-Eintrag ist. In diesem TS-Segment wird der Prozesszustand (Register) gespeichert.
Task-Switch
[Bearbeiten]Um den laufenden Prozess zu wechseln muss das Betriebssystem den Zustand des laufenden Prozesses sichern, den neuen Prozesszustand wiederherstellen und das TR-Register neuladen. Es reicht ein jmp-Befehl, dessen Selektor auf den TSS-Eintrag in der GDT zeigt, aus, obwohl es Befehle zum Sichern (str) und Setzten (ltr) des TR-Registers gibt. Das Offset wird hierbei ignoriert, denn das Befehlszähler (EIP) befindet sich im TSS.
Ein Beispiel: Der TSS-Eintrag ist der 4, also ist der Selektor für den 4. Eintrag, GDT, 0. Ring 0x20.
jmp 0x20:0x00 ; Aufruf des neuen Prozesses ... str ax ; TR-Register sichern ... mov ax, 0x20 ; Wert des Selektors laden ltr ax ; und setzten
Bei "ltr" ist jedoch zu beachten, dass dieser Befehl nur das TR-Register verändert und nicht den neuen Prozess lädt.
Virtueller 8086-Modus
[Bearbeiten]Beim virtuellen 8086-Modus simuliert der Prozessor dem Prozess (Task) einen Intel 8086 Prozessor. Man kann ihn durch Setzen des 17. Bits im Flagregister aktivieren. Das Programm läuft dann im 16-Bit-Modus. Man kann den virtuellen 8086-Modus zum Beispiel für DOS-Multitasking verwenden. Es laufen mehrere Prozesse mit DOS, zwischen denen hin- und hergeschaltet wird.
Probleme
[Bearbeiten]Was geschieht, wenn ein Programm versucht, auf Ports oder das Video-RAM (0xA0000-0xAFFFF) zuzugreifen? Die Lösung ist relativ einfach: das Video-RAM kann beim Paging als nicht präsent markiert werden. Dann wird ein Zugriff durch eine Exception abgefangen und das Betriebssystem kann entsprechend reagieren. Die I/O-Permission-Bitmap im TSS wird einfach "geleert". Dadurch wird jeder Portzugriff ebenfalls mit einer Exception abgefangen.