| Zeile 97: | Zeile 97: | ||
angegeben wurde, die Deskriptornummer des Auslösertasks. Zu diesem kehrt sie bei | angegeben wurde, die Deskriptornummer des Auslösertasks. Zu diesem kehrt sie bei | ||
gesetztem NT-Flag im Flagregister mit einem IRET zurück. Die Bereiche, die die | gesetztem NT-Flag im Flagregister mit einem IRET zurück. Die Bereiche, die die | ||
| − | Namen der Register tragen dürften klar sein. Beim Eintrag | + | Namen der Register tragen dürften klar sein. Beim Eintrag ''I/O Port Bitmap Base'' wird |
das Offset einer Tabelle angegeben, die angibt, auf welche Portadressen der Task | das Offset einer Tabelle angegeben, die angibt, auf welche Portadressen der Task | ||
Zugriff hat. Bei dem Wert 0 ist diese deaktiviert, sodass der Zugriff alleine durch das | Zugriff hat. Bei dem Wert 0 ist diese deaktiviert, sodass der Zugriff alleine durch das | ||
| Zeile 107: | Zeile 107: | ||
und nicht in der Task-Tabelle speichern will. Wie ich schon sagte braucht jeder Task ein | und nicht in der Task-Tabelle speichern will. Wie ich schon sagte braucht jeder Task ein | ||
TSS und dies muss in der GDT eingetragen werden. Ein TSS hat als die 4 Typ-Bits den | TSS und dies muss in der GDT eingetragen werden. Ein TSS hat als die 4 Typ-Bits den | ||
| − | Wert | + | Wert 1001 als Binärzahl. Dies ist der Aufbau eines TSS-Deskriptors: |
| − | + | ||
| − | Jede Zeile der Tabelle gibt ein Word wieder, eine 16 Bit lange Zahl. | + | <img src="http://www.joachim-neu.de/public/lowlevel/7/3_2.png" alt="TSS-Deskriptor"> |
| − | nichts, was unterschiedlich zu dem normalen Aufbau eines Deskriptors | + | |
| − | deswegen erkläre ich nur kurz alle Felder. Alle, die mit | + | Jede Zeile der Tabelle gibt ein Word wieder, eine 16 Bit lange Zahl. Eigentlich gibt es |
| − | die Größe des Segments an. Alle mit | + | nichts, was unterschiedlich zu dem normalen Aufbau eines Deskriptors wäre, |
| − | Speicher an. Das Bit | + | deswegen erkläre ich nur kurz alle Felder. |
| − | sollte immer auf 1 gesetzt sein, da es meiner Meinung nach nicht zu den schlausten | + | * Alle, die mit ''<span style="background: #FFFF00">Limit</span>'' bezeichnet sind geben die Größe des Segments an. * Alle mit ''<span style="background: #FF0000">Basis</span>'' bezeichneten geben die Basisadresse im |
| + | Speicher an. Das Bit ''<span style="background: #FF00FF">P</span>'' gibt an, ob sich das Segment im Speicher (present) befindet. Es sollte immer auf 1 gesetzt sein, da es meiner Meinung nach nicht zu den schlausten | ||
Lösungen zählt, TSSs auszulagern, die man ständig braucht, zumal diese nicht sehr viel | Lösungen zählt, TSSs auszulagern, die man ständig braucht, zumal diese nicht sehr viel | ||
| − | Speicher sparen. Die 2 Bit lange Stelle | + | Speicher sparen. |
| + | *Die 2 Bit lange Stelle ''<span style="background: #80FFFF">DPL</span>'' gibt das DescriptorPrivilegeLevel an. | ||
Damit kann man kontrollieren, welche Privilegstufe Programme brauchen, die auf | Damit kann man kontrollieren, welche Privilegstufe Programme brauchen, die auf | ||
| − | dieses Segment zugreifen wollen. Das Bit | + | dieses Segment zugreifen wollen. |
| + | *Das Bit ''<span style="background: #0000FF">S</span>'' ist das System-Bit. Es unterscheidet | ||
spezielle Segmente (wie TSS und alle Gates) von Standartsegmenten (Codesegment, | spezielle Segmente (wie TSS und alle Gates) von Standartsegmenten (Codesegment, | ||
| − | Datensegment). Bei speziellen Segmenten ist es nicht gesetzt, sprich 0. Dann gibt es | + | Datensegment). Bei speziellen Segmenten ist es nicht gesetzt, sprich 0. |
| − | noch das | + | * Dann gibt es noch das ''<span style="background: #800080">G</span>''(ranularity)-Bit, das angibt, ob die Größenangabe des Deskriptors in Bytes (=0) oder in Kbytes (=1) zu verstehen ist. Da ich noch kein TSS mit 1 Kbyte oder mehr Speicherverbrauch gesehen habe, sollte dies den Wert 0 haben. |
| − | (=0) oder in Kbytes (=1) zu verstehen ist. Da ich noch kein TSS mit 1 Kbyte oder mehr | + | * Das ''<span style="background: #90C6FF">D</span>''-Bit gibt an, ob |
| − | Speicherverbrauch gesehen habe, sollte dies den Wert 0 haben. Das | ||
der Deskriptor zu dem 286er kompatibel ist, oder nicht. Dort empfehle ich den Wert 1, | der Deskriptor zu dem 286er kompatibel ist, oder nicht. Dort empfehle ich den Wert 1, | ||
also inkompatibel, dann kann man das Segment vergrößern, ohne sich irgendwann man | also inkompatibel, dann kann man das Segment vergrößern, ohne sich irgendwann man | ||
| − | damit wieder auseinander setzen zu müssen. Das | + | damit wieder auseinander setzen zu müssen. |
| − | Verfügung. Das | + | * Das ''<span style="background: #808080">AVL</span>''-Flag steht zur freien Verfügung. |
| − | Wert 1001b beinhalten. | + | * Das ''<span style="background: #00FF00">Typ</span>''-Feld haben wir ja schon besprochen, es muss bei einem TSS den Wert 1001b beinhalten. |
| − | Wenn ein Task aktiviert wird, so wird sein | + | Wenn ein Task aktiviert wird, so wird sein ''Typ''-Feld mit dem Wert ''1011'' als |
Binärzahl belegt. Das zeigt, dass das aktuelle Segment aktiv ist und man nicht zu ihm | Binärzahl belegt. Das zeigt, dass das aktuelle Segment aktiv ist und man nicht zu ihm | ||
wechseln kann. Davon wissen wir schon, dass man mindestens zwei Tasks braucht, | wechseln kann. Davon wissen wir schon, dass man mindestens zwei Tasks braucht, | ||
damit Multitasking mit TSS überhaupt richtig funktioniert. Ebenso wird beim Verlassen | damit Multitasking mit TSS überhaupt richtig funktioniert. Ebenso wird beim Verlassen | ||
eines Tasks der Wert wieder zu 1001b. | eines Tasks der Wert wieder zu 1001b. | ||
| − | + | ||
| + | ===Task-Tabelle=== | ||
Desweiteren braucht man noch, wie besprochen, eine Tabelle, die die | Desweiteren braucht man noch, wie besprochen, eine Tabelle, die die | ||
Deskriptornummern und TaskIDs speichert. Auch der Status wird dort oft gespeichert, | Deskriptornummern und TaskIDs speichert. Auch der Status wird dort oft gespeichert, | ||
| Zeile 147: | Zeile 150: | ||
TaskID und TSS-Nummer. Als <span style="background: #0000FF">Status</span>bereich dürfte auch ein Word (16 Bits) reichen. | TaskID und TSS-Nummer. Als <span style="background: #0000FF">Status</span>bereich dürfte auch ein Word (16 Bits) reichen. | ||
Auch in dieser Tabelle ist eine Zeile ein Word. | Auch in dieser Tabelle ist eine Zeile ein Word. | ||
| − | + | ||
| + | <img src="http://www.joachim-neu.de/public/lowlevel/7/3_3.png" alt="Task-Tabelle"> | ||
| + | |||
Es gibt 2 Möglichkeiten, das Ende dieser Tabelle aus Task-Tabellen-Einträgen zu | Es gibt 2 Möglichkeiten, das Ende dieser Tabelle aus Task-Tabellen-Einträgen zu | ||
finden. | finden. | ||
1. Man speichert in einer Variable, wie viele Einträge man hat und überprüft das | 1. Man speichert in einer Variable, wie viele Einträge man hat und überprüft das | ||
aktuelle und das maximale Offset, um einen WrapArround (beim Anfang anfangen) | aktuelle und das maximale Offset, um einen WrapArround (beim Anfang anfangen) | ||
| − | zu vollziehen. | + | zu vollziehen. |
2. Man gibt am Ende der Tabelle dem nächsten leeren Eintrag die ID 0xFFFF oder ein | 2. Man gibt am Ende der Tabelle dem nächsten leeren Eintrag die ID 0xFFFF oder ein | ||
| − | sonstiges | + | sonstiges "Endezeichen" und führt einen WrapAround aus, sobald man als ID den |
| − | Wert des Endezeichens gefunden hat. | + | Wert des Endezeichens gefunden hat. |
| + | |||
Das war eigendlich schon das Wichtigste über die Task-Tabelle. | Das war eigendlich schon das Wichtigste über die Task-Tabelle. | ||
| − | + | ===TaskGates=== | |
Außerdem gehören zu TSS-Multitasking noch TaskGates. TaskGates kann man sowohl | Außerdem gehören zu TSS-Multitasking noch TaskGates. TaskGates kann man sowohl | ||
in der GDT als auch in der IDT eintragen. Wenn man sie an erste Stelle stellt, so wird | in der GDT als auch in der IDT eintragen. Wenn man sie an erste Stelle stellt, so wird | ||
| Zeile 164: | Zeile 170: | ||
bei allen anderen Interrupts und/oder Exceptions. Ein TaskGate ist wie folgt aufgebaut: | bei allen anderen Interrupts und/oder Exceptions. Ein TaskGate ist wie folgt aufgebaut: | ||
(wieder ist jede Zeile ein Word) | (wieder ist jede Zeile ein Word) | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | TaskGates werden an sich | + | <img src="http://www.joachim-neu.de/public/lowlevel/7/3_4.png" alt="Taskgate"> |
| + | |||
| + | Alle Bereiche, die ''<span style="background: #FF0000">Reserviert</span>'' sind, sollten den Wert 0 beinhalten. Im Bereich ''<span style="background: #FFFF00">TSS-Deskriptor-Nummer</span>'' wird der Deskriptor festgehalten, auf den das TaskGate verweist. | ||
| + | |||
| + | Die Felder ''<span style="background: #0000FF">P</span>'', ''<span style="background: #80FFFF">DPL</span>'' und ''<span style="background: #9966CC">S</span>'' habe ich vorher schon erklärt, sodass dies | ||
| + | hier überflüssig ist. Der ''<span style="background: #00FF00">Typ</span>'' muss bei einem TaskGate den Wert 0101 als Binärzahl beinhalten. | ||
| + | |||
| + | TaskGates werden an sich eigentlich beim Multitasking selten verwendet, sondern | ||
lediglich als Einträge in der IDT, damit bei einer Exception oder einem Interrupt ein | lediglich als Einträge in der IDT, damit bei einer Exception oder einem Interrupt ein | ||
eigener Task aufgerufen wird. Dann wird das NT-Flag gesetzt und die CPU wechselt bei | eigener Task aufgerufen wird. Dann wird das NT-Flag gesetzt und die CPU wechselt bei | ||
IRET zu dem Task, dessen TSS-Deskriptor im BackLink-Feld eingetragen ist. | IRET zu dem Task, dessen TSS-Deskriptor im BackLink-Feld eingetragen ist. | ||
| − | + | ||
| − | So, nun aber nach dem ganzen Aufbauzeug zur Theorie. Ein | + | ===Dispatcher=== |
| + | So, nun aber nach dem ganzen Aufbauzeug zur Theorie. Ein Taskwechselalgorithmus | ||
sollte idealerweise aus zwei Teilen bestehen, dem Scheduler und dem Dispatcher, um | sollte idealerweise aus zwei Teilen bestehen, dem Scheduler und dem Dispatcher, um | ||
| − | den wir uns nun kümmern wollen. Der Dispatcher ist dafür verantwortlich, dass der | + | den wir uns nun kümmern wollen. |
| + | |||
| + | Der Dispatcher ist dafür verantwortlich, dass der | ||
Task die CPU zugeteilt und entzogen bekommt. Er muss alle Register laden bzw | Task die CPU zugeteilt und entzogen bekommt. Er muss alle Register laden bzw | ||
| − | speichern und den | + | speichern und den eigentlichen Taskwechsel veranlassen. Beim softwarebasierenden |
Multitasking besteht diese Aufgabe des Dispatchers darin, zuerst die Werte des | Multitasking besteht diese Aufgabe des Dispatchers darin, zuerst die Werte des | ||
aktuellen Tasks auf dessen Stack zu pushen, dann den neuen Stack zu laden und die | aktuellen Tasks auf dessen Stack zu pushen, dann den neuen Stack zu laden und die | ||
Werte des Tasks zu popen. Bei hardwarebasierendem Multitasking besteht die Aufgabe | Werte des Tasks zu popen. Bei hardwarebasierendem Multitasking besteht die Aufgabe | ||
lediglich darin, einen Task-Switch zu veranlassen, in dem die CPU dann eigenständig | lediglich darin, einen Task-Switch zu veranlassen, in dem die CPU dann eigenständig | ||
| − | die Register sichert und die neuen | + | die Register sichert und die neuen lädt. |
| − | + | ||
| + | ===Scheduler=== | ||
Und hier setzt auch schon der Scheduler an. Während der Dispatcher nur die Taskwerte | Und hier setzt auch schon der Scheduler an. Während der Dispatcher nur die Taskwerte | ||
sichert und die neuen setzt besteht die Aufgabe des Schedulers darin, den nächsten Task | sichert und die neuen setzt besteht die Aufgabe des Schedulers darin, den nächsten Task | ||
| − | auszuwählen. Dies tut er anhand der Scheduling Strategie. Es gibt verschiedene dieser | + | auszuwählen. Dies tut er anhand der Scheduling-Strategie. Es gibt verschiedene dieser |
Strategien, welche wir später noch genauer besprechen werden. Bei der einfachsten | Strategien, welche wir später noch genauer besprechen werden. Bei der einfachsten | ||
Strategie sucht der Scheduler einfach nur den nächsten Task in der Task-Tabelle raus | Strategie sucht der Scheduler einfach nur den nächsten Task in der Task-Tabelle raus | ||
| Zeile 195: | Zeile 206: | ||
ist dies nur die Nummer des TSS-Deskriptors, bei softwarebasierendem Multitasking ist | ist dies nur die Nummer des TSS-Deskriptors, bei softwarebasierendem Multitasking ist | ||
dies SS und ESP des Tasks. | dies SS und ESP des Tasks. | ||
| − | + | ||
| + | ===Scheduling-Strategien=== | ||
Dies ist einer der umfangreichsten Punkte und der schwierigste, so meinen Experten. | Dies ist einer der umfangreichsten Punkte und der schwierigste, so meinen Experten. | ||
Das Problem sei ihrer Meinung nach nicht das Wechseln der Tasks sondern das | Das Problem sei ihrer Meinung nach nicht das Wechseln der Tasks sondern das | ||
| Zeile 203: | Zeile 215: | ||
sich anfangs mit dem einfachsten Verfahren abgeben sollte, einfach den nächsten Task | sich anfangs mit dem einfachsten Verfahren abgeben sollte, einfach den nächsten Task | ||
zu nehmen, der in der Tabelle kommt. Allgemein werden an eine Scheduling Strategie | zu nehmen, der in der Tabelle kommt. Allgemein werden an eine Scheduling Strategie | ||
| − | folgende Anforderungen gestellt: | + | folgende Anforderungen gestellt: |
| + | |||
1. Sie muss fair sein, was heißt, dass alle Tasks von Grund auf gleich zu behandeln | 1. Sie muss fair sein, was heißt, dass alle Tasks von Grund auf gleich zu behandeln | ||
| − | sind, sodass kein Task ständig dran kommt und kein Task nie dran kommt. | + | sind, sodass kein Task ständig dran kommt und kein Task nie dran kommt. |
2. Sie darf nicht zu komplex sein, sodass siche die CPU mehr mit dieser als mit den | 2. Sie darf nicht zu komplex sein, sodass siche die CPU mehr mit dieser als mit den | ||
| − | Tasks beschäftigen würde. | + | Tasks beschäftigen würde. |
3. Sie muss unabhängig sein, das heißt, sie darf nicht mit der Anzahl der Tasks | 3. Sie muss unabhängig sein, das heißt, sie darf nicht mit der Anzahl der Tasks | ||
| − | zusammenhängen, zum Beispiel nur bei 5 laufenden Tasks funktionieren. | + | zusammenhängen, zum Beispiel nur bei 5 laufenden Tasks funktionieren. |
| + | 4. Sie sollte berechenbar sein, also nicht auf Zufallswerten basieren oder auf jedem | ||
| + | System anderst funktionieren. | ||
| + | 5. Sie darf nicht beeinflussbar oder manipulierbar sein. | ||
| − | |||
| − | |||
| − | |||
Es gibt viele verschiedene Strategien. Die einen benutzen Prioritätsstufen zum Einteilen | Es gibt viele verschiedene Strategien. Die einen benutzen Prioritätsstufen zum Einteilen | ||
der Tasks, andere benutzen die Länge der Tasks. Bei Prioritätseinteilungen gibt es | der Tasks, andere benutzen die Länge der Tasks. Bei Prioritätseinteilungen gibt es | ||
| Zeile 220: | Zeile 233: | ||
zuzuteilen, oder aber das Privileglevel zu erhöhen, wenn der Task sich selber pausiert | zuzuteilen, oder aber das Privileglevel zu erhöhen, wenn der Task sich selber pausiert | ||
und damit anderen den Vortritt lässt, wenn er gerade keine Rechenzeit benötigt. Sehr | und damit anderen den Vortritt lässt, wenn er gerade keine Rechenzeit benötigt. Sehr | ||
| − | häufige Scheduling Strategien sind | + | häufige Scheduling Strategien sind ''Round Robin'', ''First Task First'' und ''Shortest |
| − | Time | + | Time Left''. Diese und einige andere werde ich nun ein bisschen erleutern: |
| − | + | ||
| + | ====Round Robin=== | ||
Bei Round Robin werden alle Tasks in einer Kette hintereinander abgearbeitet und | Bei Round Robin werden alle Tasks in einer Kette hintereinander abgearbeitet und | ||
jeder bekommt die gleiche Rechenzeit. Wenn man Prioritäten benutzt, so hat man | jeder bekommt die gleiche Rechenzeit. Wenn man Prioritäten benutzt, so hat man | ||
| Zeile 228: | Zeile 242: | ||
ist, dass es jedes Prioritätlevel nur einmal gibt und ein Task beim Erhöhen seines | ist, dass es jedes Prioritätlevel nur einmal gibt und ein Task beim Erhöhen seines | ||
Levels einfach mit seinem direkten oberen Nachbarn tauscht, und dadruch nach oben | Levels einfach mit seinem direkten oberen Nachbarn tauscht, und dadruch nach oben | ||
| − | kommt. | + | kommt. |
| − | + | ||
| + | ====First Task First==== | ||
Dieses Verfahren ist eigendlich mehr für Batchbetrieb, also dem Abarbeiten der | Dieses Verfahren ist eigendlich mehr für Batchbetrieb, also dem Abarbeiten der | ||
| − | Tasks nacheinander als gleichzeitig gedacht. Es folgt dem | + | Tasks nacheinander als gleichzeitig gedacht. Es folgt dem Sprichwort ''wer zuerst |
| − | kommt | + | kommt mahlt zuerst'', sodass ein Programm also den ersten Task ausführt und aber |
derzeit auch noch andere annimmt. Und nach Beendigung dieses Tasks wird der | derzeit auch noch andere annimmt. Und nach Beendigung dieses Tasks wird der | ||
nächste genommen. Das Verfahren ist unbrauchbar für Systeme mit Mikrokerneln, | nächste genommen. Das Verfahren ist unbrauchbar für Systeme mit Mikrokerneln, | ||
bei denen Treiber und Module auch Tasks darstellen, da diese nicht aufgerufen | bei denen Treiber und Module auch Tasks darstellen, da diese nicht aufgerufen | ||
werden können. Desweiteren scheiden sich die Geister, ob diese Methode überhaupt | werden können. Desweiteren scheiden sich die Geister, ob diese Methode überhaupt | ||
| − | den Namen | + | den Namen "Multitasking" verdient, da die Tasks nicht gleichzeitig ausgeführt |
| − | werden, jedoch aber hintereinander. | + | werden, jedoch aber hintereinander. |
| − | |||
| + | ====Shortest Time Left==== | ||
Dieses Konzept besagt, dass der Task, der die kleinste Zeit in Anspruch nehmen wird | Dieses Konzept besagt, dass der Task, der die kleinste Zeit in Anspruch nehmen wird | ||
zuerst an die Reihe kommt. Meist bemisst man die Zeit anhand der Größe des Tasks. | zuerst an die Reihe kommt. Meist bemisst man die Zeit anhand der Größe des Tasks. | ||
Jedoch müssen diese Werte ständig aktualisiert werden und es ist schwer für das OS | Jedoch müssen diese Werte ständig aktualisiert werden und es ist schwer für das OS | ||
zu sagen, was Code und was Daten sind. Ein Task könnte nur ein Ladeprogramm | zu sagen, was Code und was Daten sind. Ein Task könnte nur ein Ladeprogramm | ||
| − | beinhalten, dass den richtigen Task | + | beinhalten, dass den richtigen Task lädt und würde somit als erstes ausgeführt und |
| − | umgeht damit das System. Somit ist einer der Grundsätze nicht erfüllt. | + | umgeht damit das System. Somit ist einer der Grundsätze nicht erfüllt. |
| − | + | ||
| + | ====Longest Time Left==== | ||
Dieses Konzept ist exakt das Gegenteil des vorherigen. Es bevorzugt den längsten | Dieses Konzept ist exakt das Gegenteil des vorherigen. Es bevorzugt den längsten | ||
Task und wieder gelten hier alle Einwände, welche bei „Shortest Time Left“ genannt | Task und wieder gelten hier alle Einwände, welche bei „Shortest Time Left“ genannt | ||
| − | wurden. | + | wurden. |
| + | |||
Das waren auch schon die meiner Meinung nach wichtigsten. Es steht jedem frei, | Das waren auch schon die meiner Meinung nach wichtigsten. Es steht jedem frei, | ||
weitere dazu zu entwickeln und bestehende zu verfeinern. Ich selber benutze Round | weitere dazu zu entwickeln und bestehende zu verfeinern. Ich selber benutze Round | ||
Robin ohne jegliche Form von Prioritäten. | Robin ohne jegliche Form von Prioritäten. | ||
| − | + | ||
| + | ===Taskwechsel=== | ||
Nun aber zum meiner Meinung kompliziertesten Punkt der Praxis, dem Wechsel selber. | Nun aber zum meiner Meinung kompliziertesten Punkt der Praxis, dem Wechsel selber. | ||
Das Implementieren eines Round-Robin-Schedulers sollte nicht alzu schwer gewesen | Das Implementieren eines Round-Robin-Schedulers sollte nicht alzu schwer gewesen | ||
| Zeile 260: | Zeile 278: | ||
hatte, als ich TSS für mein Betriebssystem implementierte. | hatte, als ich TSS für mein Betriebssystem implementierte. | ||
Zuerst möchte ich schildern, wodurch ein Taskwechsel an sich ausgelöst wird. Es gibt | Zuerst möchte ich schildern, wodurch ein Taskwechsel an sich ausgelöst wird. Es gibt | ||
| − | mehrere Möglichkeiten: | + | mehrere Möglichkeiten: |
| + | |||
1. Ein Far-Jump oder Far-Call bei dem als Selektor ein TSS angegeben wird. Der | 1. Ein Far-Jump oder Far-Call bei dem als Selektor ein TSS angegeben wird. Der | ||
| − | Offsetteil wird hierbei ignoriert. | + | Offsetteil wird hierbei ignoriert. |
2. Ein IRET nachdem ein Task durch ein TaskGate in der IDT aufgerufen wird, also das | 2. Ein IRET nachdem ein Task durch ein TaskGate in der IDT aufgerufen wird, also das | ||
| − | NT-Flag gesetzt ist. | + | NT-Flag gesetzt ist. |
3. Ein Far-Jump oder Far-Call auf ein Task-Gate. Auch hier wird der Offsetteil | 3. Ein Far-Jump oder Far-Call auf ein Task-Gate. Auch hier wird der Offsetteil | ||
| − | ignoriert. | + | ignoriert. |
| + | |||
| + | Es gibt also 2 Möglichkeiten: | ||
| + | * Man macht einen Far-Jump auf ein TSS. | ||
| + | * Man manipuliert das NT-Flag und simuliert den Aufruf als ein Interrupt durch ein | ||
| + | TaskGate. | ||
| − | |||
| − | |||
| − | |||
| − | |||
Die zweite Methode habe ich noch nie erfolgreich zum Laufen gebracht und sie ist | Die zweite Methode habe ich noch nie erfolgreich zum Laufen gebracht und sie ist | ||
zweifellos die schwierigere, da man nicht nur die Flags sondern auch BackLink-Felder | zweifellos die schwierigere, da man nicht nur die Flags sondern auch BackLink-Felder | ||
und Stacks manipulieren muss. Deswegen werde ich nun auch die erste Methode | und Stacks manipulieren muss. Deswegen werde ich nun auch die erste Methode | ||
| − | ausgiebig | + | ausgiebig erläutern. |
| − | Nun, man möchte sich nun denken | + | |
| − | seinen Code | + | Nun, man möchte sich nun denken "nichts leichter als das" und schreibt munter in |
| + | seinen Code <tt>JMP AX,0x00000000</tt>, wobei man gedenkt, AX mit der | ||
Deskriptornummer zu beladen. Spätestens beim Versuch zu Assemblieren wird man | Deskriptornummer zu beladen. Spätestens beim Versuch zu Assemblieren wird man | ||
feststellen: das geht nicht. Man muss sich also was anderes ausdenken. Erstmal möchte | feststellen: das geht nicht. Man muss sich also was anderes ausdenken. Erstmal möchte | ||
| − | ich sicherheitshalber das ganze zu diesem ausbauen: | + | ich sicherheitshalber das ganze zu diesem ausbauen: <tt>JMP DWORD AX:0x12345678</tt>. |
| − | So wird | + | So wird erzwungen, dass der Assembler auch den kompletten Offsetteil mit übersetzt und |
nicht Teile durch Abkürzen unter den Tisch kehrt. Das Offset habe ich zu dieser | nicht Teile durch Abkürzen unter den Tisch kehrt. Das Offset habe ich zu dieser | ||
Zahlenfolge gemacht, da ich es für noch ein Schritt sicherer halte, dass das ganze nicht | Zahlenfolge gemacht, da ich es für noch ein Schritt sicherer halte, dass das ganze nicht | ||
gekürzt wird. Noch haben wir aber das Problem mit dem AX nicht geklärt. Da dies auf | gekürzt wird. Noch haben wir aber das Problem mit dem AX nicht geklärt. Da dies auf | ||
| − | jeden Fall nicht geht wandeln wir auch das um: | + | jeden Fall nicht geht wandeln wir auch das um: <tt>JMP DWORD 0x1234:0x12345678</tt> |
Dies wird der Assembler zwar problemlos schlucken, aber leider bringt es uns nicht | Dies wird der Assembler zwar problemlos schlucken, aber leider bringt es uns nicht | ||
viel. Wie man vielleicht schon merkt setze ich viel darauf, dass der Assembler dies | viel. Wie man vielleicht schon merkt setze ich viel darauf, dass der Assembler dies | ||
vollständig und ungekürzt in die Opcodes umwandelt, denn das ist unser Angriffspunkt: | vollständig und ungekürzt in die Opcodes umwandelt, denn das ist unser Angriffspunkt: | ||
HexDump. Wir manipulieren einfach den vom Assembler erstellten Maschinencode | HexDump. Wir manipulieren einfach den vom Assembler erstellten Maschinencode | ||
| − | während der Ausführung. Ein paar Versuche haben ergeben: | + | während der Ausführung. Ein paar Versuche haben ergeben: <tt>JMP DWORD |
| − | 0x1234: | + | 0x1234:0x12345678</tt> wird vom Assembler in die Bytes 0xED, 0x78, 0x56, 0x34, 0x12, |
0x34 und 0x12 umgewandelt. Wir sehen also, dass die letzten zwei Bytes den Selektor | 0x34 und 0x12 umgewandelt. Wir sehen also, dass die letzten zwei Bytes den Selektor | ||
angeben. Wir bleiben also weiterhin dabei, in AX unsere TSS-Deskriptornummer zu | angeben. Wir bleiben also weiterhin dabei, in AX unsere TSS-Deskriptornummer zu | ||
| Zeile 298: | Zeile 319: | ||
derartigen Verdrehungen, da die CPU andere Zahlendarstellungsverfahren benutzt, als | derartigen Verdrehungen, da die CPU andere Zahlendarstellungsverfahren benutzt, als | ||
der Mensch und die Zahlen konvertiert werden (näheres in meinem Tutorial | der Mensch und die Zahlen konvertiert werden (näheres in meinem Tutorial | ||
| − | + | ''Datenveränderungen im RAM''). Wir können also getrost einfach ein Word an die | |
Stelle mit dem Selektor schreiben, der sich 5 Bytes hinter Befehlbeginn befindet. Den | Stelle mit dem Selektor schreiben, der sich 5 Bytes hinter Befehlbeginn befindet. Den | ||
Befehlsbeginn finden wir anhand eines Labels und schon haben wir alles zusammen um | Befehlsbeginn finden wir anhand eines Labels und schon haben wir alles zusammen um | ||
folgenden Code zu bauen: | folgenden Code zu bauen: | ||
| − | + | ||
| + | <pre> | ||
PUSH EDI | PUSH EDI | ||
MOV EDI, tss_switch_label ; Offset laden | MOV EDI, tss_switch_label ; Offset laden | ||
| Zeile 310: | Zeile 332: | ||
JMP DWORD 0x1234:0x12345678 ; springen! | JMP DWORD 0x1234:0x12345678 ; springen! | ||
POP EDI | POP EDI | ||
| − | </pre> | + | </pre> |
| + | |||
Der Code verlangt lediglich, dass sich in AX die TSS-Deskriptornummer des Tasks | Der Code verlangt lediglich, dass sich in AX die TSS-Deskriptornummer des Tasks | ||
befindet, zu dem gewechselt werden soll. Alles andere macht die CPU. Somit dürfte das | befindet, zu dem gewechselt werden soll. Alles andere macht die CPU. Somit dürfte das | ||
Wechseln auch kein Problem mehr sein. | Wechseln auch kein Problem mehr sein. | ||
| − | + | ||
| + | ===Implementierung und Praxis=== | ||
Tja, wir haben jetzt alles wichtige besprochen, Zeit, zusammen mal einen Code zu | Tja, wir haben jetzt alles wichtige besprochen, Zeit, zusammen mal einen Code zu | ||
coden, den ihr in euer TimerIRQ einbauen könnt, und der für euch den Taskwechsel | coden, den ihr in euer TimerIRQ einbauen könnt, und der für euch den Taskwechsel | ||
macht. Zuerst einmal brauchen wir den Scheduler. Ich gehe davon aus, dass die Task- | macht. Zuerst einmal brauchen wir den Scheduler. Ich gehe davon aus, dass die Task- | ||
Tabelle an der Adresse 0x00005000 liegt. Die Größe wird anhand einer Variable | Tabelle an der Adresse 0x00005000 liegt. Die Größe wird anhand einer Variable | ||
| − | angegeben, die das Offset des Endes beinhaltet, welche | + | angegeben, die das Offset des Endes beinhaltet, welche ''tasking_end'' heißt und vom |
Typ Double Word ist. Außerdem existiert eine Variable mit dem Namen | Typ Double Word ist. Außerdem existiert eine Variable mit dem Namen | ||
| − | + | ''tasking_current'', welche ebenfalls vom Typ Double Word ist und den aktuellen Punkt | |
| − | in der Tabelle festhält. | + | in der Tabelle festhält. |
In unserem Code bauen wir reines Round Robin, wir brauchen also Task-Tabellen- | In unserem Code bauen wir reines Round Robin, wir brauchen also Task-Tabellen- | ||
Einträge nach der Form, welche unter „4. Task-Tabelle“ gezeigt ist. Ein Eintrag ist also | Einträge nach der Form, welche unter „4. Task-Tabelle“ gezeigt ist. Ein Eintrag ist also | ||
| − | 6 Bytes groß. | + | 6 Bytes groß. |
| + | |||
Coden wir nun einfach mal den Scheduler. Da wir am Anfang des IRQs keinen | Coden wir nun einfach mal den Scheduler. Da wir am Anfang des IRQs keinen | ||
Dispatcher brauchen, können wir gleich mit dem Scheduler anfangen. | Dispatcher brauchen, können wir gleich mit dem Scheduler anfangen. | ||
| − | + | ||
| + | <pre> | ||
ADD DWORD [tasking_current],0x00000006 ;Werte des | ADD DWORD [tasking_current],0x00000006 ;Werte des | ||
; aktuellen Tasks überspringen | ; aktuellen Tasks überspringen | ||
| Zeile 425: | Zeile 451: | ||
POP EDI | POP EDI | ||
POP EAX | POP EAX | ||
| − | </pre> | + | </pre> |
| + | |||
So, fertig ist der Praxisteil. Ich bitte alle Leser, diesen Code nicht einfach nur blind zu | So, fertig ist der Praxisteil. Ich bitte alle Leser, diesen Code nicht einfach nur blind zu | ||
kopieren, denn er lässt sich sicher nicht fehlerlos in das System integrieren, und so | kopieren, denn er lässt sich sicher nicht fehlerlos in das System integrieren, und so | ||
schwer ist das Implementieren nicht. Also selber versuchen und bei Fragen mich einfach | schwer ist das Implementieren nicht. Also selber versuchen und bei Fragen mich einfach | ||
| − | kontaktieren. | + | kontaktieren. |
| + | |||
Damit dieser Code richtig geht brauchen wir aber auch einen Task, der gerade läuft. | Damit dieser Code richtig geht brauchen wir aber auch einen Task, der gerade läuft. | ||
Angezeigt wird dieser Task durch das TR, das TaskRegister. Dieses beinhaltet einen | Angezeigt wird dieser Task durch das TR, das TaskRegister. Dieses beinhaltet einen | ||
| Zeile 436: | Zeile 464: | ||
Deskriptornummer des Tasks, der gerade aktiv ist angegeben. Beim Wechsel aktualisiert | Deskriptornummer des Tasks, der gerade aktiv ist angegeben. Beim Wechsel aktualisiert | ||
die CPU das TR dann automatisch. Man braucht den Befehl also nur einmal. | die CPU das TR dann automatisch. Man braucht den Befehl also nur einmal. | ||
| − | + | ||
| + | ===Tipps & Tricks=== | ||
Jetzt habe ich zu Schluss noch ein paar Tipps zum Programmieren von Multitasking und | Jetzt habe ich zu Schluss noch ein paar Tipps zum Programmieren von Multitasking und | ||
| − | zum Programmieren allgemein. | + | zum Programmieren allgemein. |
| + | |||
1. Programmiere auf Sicherheit! Es geht nicht um einen Geschwindigkeitsrekord, | 1. Programmiere auf Sicherheit! Es geht nicht um einen Geschwindigkeitsrekord, | ||
sondern um ein stabiles Betriebssystem. Deswegen beachte und bedenke jeden | sondern um ein stabiles Betriebssystem. Deswegen beachte und bedenke jeden | ||
| Zeile 447: | Zeile 477: | ||
2. Schreibe nicht alles ab! Code den Code selber. Es wird dir helfen, das Zeug zu | 2. Schreibe nicht alles ab! Code den Code selber. Es wird dir helfen, das Zeug zu | ||
verstehen und eigener Code lässt sich besser ins System integrieren.<br> | verstehen und eigener Code lässt sich besser ins System integrieren.<br> | ||
| − | |||
3. Entscheide dich fest für ein System und mach keine Abstriche. Versuche deine | 3. Entscheide dich fest für ein System und mach keine Abstriche. Versuche deine | ||
Gedanken durchzusetzen und gib' nicht auf, blos, weil was nicht klappt.<br> | Gedanken durchzusetzen und gib' nicht auf, blos, weil was nicht klappt.<br> | ||
| Zeile 454: | Zeile 483: | ||
5. Halte alles Modular, sprich baue auch für deinen Scheduler eine Funktion. Das wird | 5. Halte alles Modular, sprich baue auch für deinen Scheduler eine Funktion. Das wird | ||
es später erleichtern, die Funktion auszutauschen, wenn du eine andere Strategie | es später erleichtern, die Funktion auszutauschen, wenn du eine andere Strategie | ||
| − | verwenden willst. | + | verwenden willst. |
6. Und als letztes: Kommentiere deinen Code gut. Gerade in Assembler ist das sehr | 6. Und als letztes: Kommentiere deinen Code gut. Gerade in Assembler ist das sehr | ||
| − | wichtig. | + | wichtig. |
| + | |||
Tja, das waren auch schon meine Tipps. | Tja, das waren auch schon meine Tipps. | ||
| − | + | ||
| + | ===Schluss=== | ||
Wir sind nun schon am Ende dieses Tutorials angelangt. Es ist etwas lang geworden, | Wir sind nun schon am Ende dieses Tutorials angelangt. Es ist etwas lang geworden, | ||
warscheinlich deshalb, dass Multitasking nunmal ein komplexes Thema ist, dass man | warscheinlich deshalb, dass Multitasking nunmal ein komplexes Thema ist, dass man | ||