Wie bereits im Kapitel 2 kurz erwähnt wurde, bezeichnet Multitasking die gleichzeitige Ausführung mehrerer Programme (oder Programmteile) bzw. im Allgemeinen die gleichzeitige Ausführung von Prozessen. Diese gleichzeitige Ausführung findet bei Systemen mit nur einem Prozessor in Wirklichkeit nicht statt. Statt dessen wird eine quasi-parallele Ausführung aller Prozesse erreicht, indem die verfügbare Prozessorzeit auf mehrere Tasks verteilt wird. Wie ebenfalls in Kapitel 2 erwähnt wurde, existieren dazu eine Reihe von Strategien, um diese Teilung möglichst effektiv zu organisieren. Allen Prinzipien ist jedoch gemeinsam, daß ein laufender Prozeß (an irgendeiner Stelle seines Programmcodes, Berechnungen etc.) unterbrochen werden muß, damit die Ausführung mit einem anderen Prozeß fortgesetzt werden kann.
Diese Unterbrechung wird als Task-Wechsel bezeichnet. Der Task-Wechsel muß dafür sorgen, daß der gesamte(!) Prozessorzustand festgehalten wird, da das unterbrochene Programm nicht von dieser Unterbrechung beeinflußt werden darf. Nur so ist es möglich, den unterbrochenen Task später wieder fortzusetzen.
Task State Segment (TSS)Für das notwendige "Einfrieren" des Prozessorzustandes wird das sogenannte Task State Segment (TSS) verwendet. Dabei handelt es sich um einen Speicherbereich, der es dem Prozessor ermöglicht, den kompletten Prozessorzustand (z.B. den Inhalt der Register) zu sichern. Bei einem Task-Wechsel liest (Eigenschaften des neuen Prozeß) bzw. schreibt (Eigenschaften des alten Prozeß) der Prozessor selbständig aus bzw. in die entsprechenden TS-Segmente. Jedes TS-Segment besitzt seinen eigenen Deskriptor, der die Basisadresse und die Größe des jeweiligen TS-Segmentes beschreibt. Abb. 3.6 zeigt den Aufbau eines TS-Segmentes der 80386/80486 Prozessoren. Der 80286 besitzt zwar ebenfalls ein TSS, dieses ist jedoch aufgrund der 16 Bit breiten Register des 80286 nur 44 Byte groß. Das TSS des 80286 soll hier nicht beachtet werden.
Abb. 3.6: Aufbau des Task-State-Segmentes (TSS)
Abb. 3.7 zeigt den dazugehörigen TSS-Deskriptor.
Abb. 3.7: TSS-Deskriptor
TSS-Deskriptoren besitzen ein zurückgesetztes S-Bit und gehören damit zur Gruppe der Systemsegmente. TSS-Deskriptoren lassen sich nur in der Globalen Deskriptor Tabelle (GDT) speichern und werden anhand ihres Type-Feldes unterschieden. Dabei kann das Type-Feld nur 4 verschiedene Werte annehmen (Vgl. Tabelle 3.1).
Wert | binär | Beschreibung |
---|---|---|
1 | 0001b | 80286-TSS |
3 | 0011b | aktives 80286-TSS |
9 | 1001b | 80386-TSS |
11 | 1011b | aktives 80386-TSS |
80286 TSS-Deskriptoren sollen hier nicht weiter betrachtet werden.
Das variable Bit 2 des Type-Feldes wird als "Busy-Bit" bezeichnet, es gibt den Status des beschriebenen Tasks an. Besitzt das Bit 2 den Wert 0 (Type= 1001b), dann wartet der beschriebene Task auf seine Aktivierung. Ist das Bit 2 gesetzt (Type=1011b), dann wurde der Task bereits aktiviert und ist der momentan aktive Task. Wird versucht, zu einem bereits aktivierten Task zu wechseln, wird eine Exception ausgelöst. Tasks sind nicht reentrant, d.h ein bereits aktivierter Task darf nicht noch einmal aktiviert werden. In diesem Fall hat der Prozessor keine andere Möglichkeit, als eine Exception auszulösen, denn das zum aktiven Prozeß gehörende TSS-Segment wird ja bereits vom aktiven Task selbst benutzt. Soll nun zum aktiven Task ein Task-Wechsel durchgeführt werden, so müßte diese Datenstruktur überschrieben werden und eine Rückkehr zum unterbrochenen Task wäre dann nicht mehr möglich. Das obige trifft natürlich nur auf einen Task-Wechsel zu, ein JMP oder CALL innerhalb des aktiven Tasks ist natürlich möglich.
Der momentan aktive Task wird durch den Inhalt des Prozessor-Registers TR beschrieben. Dieses Register enthält einen Selektor auf einen TSS-Deskriptor. Zusätzlich wird dieser TSS-Deskriptor in einem "unsichtbaren" Schattenregister festgehalten. Über die Befehle LTR (Load Task Register) und STR (Store Task Register) kann auf das TR-Register zugegriffen werden. Der Inhalt des Task Registers (TR) hat nur indirekt mit dem eigentlichen Task-Wechsel zu tun, er beschreibt lediglich, in welchem TS-Segment der Prozessor im Falle eines Taskwechsels seine aktuellen Registerinhalte ablegt. Das Laden von TR mit einem neuen Wert ist lediglich Teil des Taskwechsels und nicht der Taskwechsel selbst (Vgl. auch mit Abb. 3.8: Task-Register).
Abb. 3.8: Task-Register
Die folgenden vier Ereignisse können einen Task-Wechsel auslösen:
Bei einem Task-Wechsel führt der Prozessor die folgenden Schritte durch:
Privilegprüfung
Ein Taskwechsel, der durch die Befehle JMP oder CALL ausgelöst
wird, veranlaßt eine Prüfung der Zieldeskriptor-Privilegstufe.
Sie muß höher bzw. mindestens gleich (d.h. numerisch
kleiner oder gleich) der Privilegstufe des aufrufenden Tasks (CPL)
und der im Selektor angegebenen, gewünschten Privilegstufe
(RPL) sein. Zugriffsverletzungen werden dabei über die Exception
13 gemeldet.
Exceptions, Interrupts und durch IRET ausgelöste Task-Wechsel
können unabhängig von der DPL des Ziel-Task-Gates bzw.
des Ziel-TSS-Deskriptors einen Task-Wechsel durchführen.
Segment- und Präsenzprüfung des angesprochenen
TS-Segmentes
In diesem Schritt wird sichergestellt, daß das angesprochene
TS-Segment im Speicher vorhanden ist (d.h. das "present"-Bit
im TSS-Deskriptor muß gesetzt sein) und daß genügend
Platz zur Speicherung der Register im beschriebenen TS-Segment
bereitsteht (mindestens 104 Byte). Auch hier reagiert der Prozessor
bei Fehlern mit einer Exception.
Bis einschließlich diesem 2. Schritt verliefen alle Prüfungen
im Kontext des alten (momentan noch aktiven) Prozesses. Alle bis
zu diesem Punkt aufgetretenen Fehler konnten korrigiert und die
entsprechenden Prüfungen wiederholt werden, ohne daß bisher
der aktive Task gewechselt wurde. Die Fehlerbehandlung liegt in
der Verantwortung des gerade aktiven Tasks.
Festhalten des Task-Status
Der Selektor für das TS-Segment des unterbrochenen Tasks
befindet sich im Taskregister (TR). Der Prozessor kopiert nun
den Inhalt der Prozessorregister in das so adressierte TS-Segment.
Der dabei in CS:EIP gespeicherte Wert zeigt hinter den Befehl,
der den Task-Wechsel ausgelöst hat. Der Prozessor kann an
dieser Adresse (bei der späteren Wiederaufnahme des Tasks)
seine Arbeit fortsetzen.
Register TR mit neuem Inhalt laden
Der Selektor des zum neuen Task gehörigen TSS-Deskriptors
wird in das Task Register geladen. Der Wert des Selektors wird
dabei entweder dem entsprechenden Gate oder dem Selektor eines
FAR-Calls entnommen. Danach wird das "Busy"-Bit im neuen
TSS-Deskriptor gesetzt, damit der Task als "aktiviert"
gekennzeichnet wird. Weiterhin wird das TS-Bit ("Task-Switched"-Bit)
im Statusregister 0 (CR0) gesetzt, um einen stattgefundenen Task-Wechsel
zu signalisieren.
Neuen Task-Status laden und mit der Ausführung des
neuen Tasks beginnen
Alle Register werden mit den Werten aus dem neuen TS-Segment
geladen. Wird der Task zum erstenmal aufgerufen, beginnt die Ausführung
mit dem ersten Befehl des Tasks. Alle späteren Aufrufe setzen
die Arbeit mit dem Befehl fort, der dem auslösenden Befehl
für einen Task-Wechsel folgt.
Durch einen CALL- oder einen Interrupt-Aufruf wird die Ausführung eines Tasks nur temporär unterbrochen (im Gegensatz dazu zum Beispiel: JMP). Nach der Ausführung des entsprechenden Interrupt-Handlers muß der unterbrochene Task fortgesetzt werden. Über das Backlink-Feld im TSS und über das NT-Flag wird dabei eine Task-Kette aufgebaut, die es ermöglicht, die unterbrochenen Tasks wieder fortzusetzen. Das NT-Flag ("Nested-Tasks") im Flagregister wird durch einen Task-Wechsel (über CALL, Interrupt oder Trap) gesetzt und zeigt im gesetzten Zustand an, daß ein Zurückschalten zum vorhergehenden Task über einen IRET Befehl möglich ist. In diesem Fall handelt es sich also um verkettete Tasks. Beendet der verschachtelte Task seine Arbeit mit IRET, interpretiert der Prozessor die Ausführung von IRET als Task-Wechsel. Er schaltet zu dem Task zurück, auf den das Backlink-Feld im aktuellen TSS verweist.
Anmerkung: Auch wenn CALL benutzt wird, um einen Task-Wechsel auszulösen, muß IRET (nicht RET!!!) benutzt werden, um zum unterbrochenen Task zurückzukehren.
I/O Permission BitmapWelche Portadressen einem Task zur Verfügung stehen, wird ab dem 80386-Prozessor in einem zweistufigen System festgelegt. Das Feld IOPL ("I/O Privilege Level", 2 Bit breit) im Flagregister legt fest, ab welcher Privilegstufe ein Prozeß ohne Einschränkungen über alle Portadressen verfügen kann. Das Flagregister kann dabei nur über die Befehle PUSHF und POPF verändert werden, beide Befehle können jedoch nur vom Betriebssystem (Privilegstufe 0) ausgeführt werden.
Hat ein Prozeß eine höhere oder die gleiche Privilegstufe (numerisch kleiner oder gleich) wie die durch den Inhalt des IOPL-Feldes festgelegte Privilegstufe, so wird keine weitere Prüfung durchgeführt und der Portzugriff gestattet. Anderenfalls legt das Betriebssystem über die "I/O Permission Bitmap" (gehört zum TS-Segment) fest, welche Portadressen vom Prozeß benutzt werden dürfen und welche nicht.
Bei der I/O Permission Bitmap handelt es sich (wie der Name vermuten läßt), um eine Bitmap, also einen Speicherbereich, bei der jedes Bit eine Funktion besitzt. Im Fall der I/O Permission Bitmap legt der Status eines einzelnen Bits das Zugriffsrecht auf eine einzelne Portadresse fest. Ist das Bit gelöscht (0), kann der Prozeß die entsprechende Portadresse benutzen, ist das Bit hingegen gesetzt (1), führt ein Zugriffsversuch auf diese Portadresse zu einer Exception. Da es insgesamt 65536 I/O Adressen gibt, ist zur Speicherung des Status aller Portadressen ein Bereich von 65536 Bit (=65536 / 8 Byte = 8192 Byte) notwendig. Die Startadresse dieses Bereiches im TS-Segment wird über den Eintrag I/OP Bitmap Base festgelegt. Die Länge des Feldes und damit die maximal verfügbare Portadresse ergibt sich aus dem Limit-Feld des zum Segment gehörenden Deskriptors. Der Wert 0 im Eintrag des "I/OP Bitmap Base" Feldes steht für "kein Zugriffsrecht" auf die Portadressen. Dasselbe gilt für Startadressen, die größer als das Segment-Limit des TSS-Deskriptors sind. Da die ersten 104 Byte in einem TS-Segment für die Zustandsbeschreibung des Prozesses reserviert sind, muß die Basisadresse der "I/O Permission Bitmap" größer oder gleich 104 sein.
Das Limit-Feld eines TSS-Deskriptors betrage, bei zurückgesetztem Granularity-Bit (G-Bit), 106 und der Inhalt der Speicherstellen 104 und 105 in dem so beschriebenen TSS sei jeweils 3Fh. Weiterhin sei vorausgesetzt, daß das Feld "I/OP Bitmap Base" den Wert 104 enthält. Welche Portadressen stehen diesem Task zur Verfügung, wenn er in der Privilegstufe 3 (Ring 3) läuft und das Feld IOPL des Flagregisters den Wert 10b (2) besitzt ? Zuerst überprüft der Prozessor bei einem Zugriffsversuch auf die Portadressen (über die Befehle IN und OUT) die Privilegstufe des Tasks. Die Privilegstufe (im Beispiel 3) muß numerisch kleiner oder gleich (also höher oder gleich privilegiert) dem im Feld IOPL des Flagregisters angegebenen Wert (im Beispiel 2) sein, damit der Task uneingeschränkten Zugriff auf die Portadressen erhält. In diesem Beispiel wird eine zusätzliche Überprüfung des Portzugriffs durch die "I/OP Bitmap" durchgeführt, da die CPL numerisch größer und damit geringer privilegiert als die im Feld IOPL festgelegte Privilegstufe ist. Im zweiten Schritt überprüft der Prozessor die "I/O Permission Bitmap", falls der Wert des "I/OP Bitmap Base"-Feldes innerhalb des zum aktiven Task gehörigen TSS nicht 0 ist. Da dieses Feld im Beispiel den Wert 104 enthält und das TSS maximal 106 Byte groß werden kann, kann für 2*8Bit=16 Portadressen einzeln festgelegt werden, ob auf sie zugegriffen werden kann oder ob ein Zugriffsversuch eine Exception auslöst. Im Beispiel sieht die Bitmap so aus: Byte 104= 3Fh = 00111111b und Byte 105= 3Fh=00111111b, demnach ergibt sichdie Bitmap: Das heißt, die einzigen Portadressen, auf die der Task zugreifen kann, sind die Ports 6, 7, 14 und 15. Ein Zugriffsversuch auf alle anderen Portadressen würde eine Exeception 13 (allgemeine Zugriffsverletzung) auslösen (einschließlich aller Portadressen > 15). |
Ein Beispielprogramm zur Anwendung von Tasks finden Sie am Ende des nächsten Punktes: 3.3 Call- und Task- Gates.