| Zeile 1: | Zeile 1: | ||
| − | Dieses Tutorial ist für alle gedacht, die in ihrem Betriebssystem Multitasking auf Softwarebasis | + | Dieses Tutorial ist für alle gedacht, die in ihrem Betriebssystem Multitasking auf Softwarebasis implementieren wollen. Ich bin am Anfang an diesem Thema fast verzweifelt, daher schreibe ich dieses Tutorial, damit andere einen leichten Einstieg in diese Thema haben. Ich werde hier nicht "alles" erklären, aber danach sollte einfache Tests mit zwei Tasks möglich sein. |
| − | |||
| − | implementieren wollen. Ich bin am Anfang an diesem Thema fast verzweifelt, daher schreibe ich dieses | ||
| − | |||
| − | Tutorial, damit andere einen leichten Einstieg in diese Thema haben. Ich werde hier nicht "alles" | ||
| − | |||
| − | erklären, aber danach sollte einfache Tests mit zwei Tasks möglich sein. | ||
== Bevor wir anfangen... == | == Bevor wir anfangen... == | ||
| − | Eines vorweg: Du solltest mit deinem Betriebssystem bereits auf einem gewissen Stand sein, d.h. du | + | Eines vorweg: Du solltest mit deinem Betriebssystem bereits auf einem gewissen Stand sein, d.h. du solltest IRQs verwalten und den PIT umprogrammieren können, eine Speicherverwaltung wird auch benötigt. Ansonsten brauchst du nur noch gesunden Menschenverstand. Wenn du das alles hast, kann es losgehen. |
| − | |||
| − | solltest IRQs verwalten und den PIT umprogrammieren können, eine Speicherverwaltung wird auch | ||
| − | |||
| − | benötigt. Ansonsten brauchst du nur noch gesunden Menschenverstand. Wenn du das alles hast, kann es | ||
| − | |||
| − | losgehen. | ||
== Einige Überlegungen == | == Einige Überlegungen == | ||
| − | Einfach drauflos zu coden ist hier der falsche Weg. Überlegen wir uns zuerst einmal, wie | + | Einfach drauflos zu coden ist hier der falsche Weg. Überlegen wir uns zuerst einmal, wie Software-Multitasking überhaupt funktioniert. |
| − | |||
| − | Software-Multitasking überhaupt funktioniert. | ||
* Generelles: | * Generelles: | ||
| − | ** Wir benötigen eine Tabelle mit den Tasks, in der Stack, ProzessID und der Status gespeichert | + | ** Wir benötigen eine Tabelle mit den Tasks, in der Stack, ProzessID und der Status gespeichert werden. Dann noch eine Variable, in der wir die ID des aktuellen Prozesses festhalten. |
| − | |||
| − | werden. Dann noch eine Variable, in der wir die ID des aktuellen Prozesses festhalten. | ||
* Initialisierung: | * Initialisierung: | ||
| − | ** Wir sagen dem PIT, wie oft er in der Sekunde einen IRQ feuern soll. Danach erzeugen wir in der IDT | + | ** Wir sagen dem PIT, wie oft er in der Sekunde einen IRQ feuern soll. Danach erzeugen wir in der IDT an der Stelle des IRQs 0 einen Eintrag, der auf unsere Task-Switch Routine verweist. |
| − | |||
| − | an der Stelle des IRQs 0 einen Eintrag, der auf unsere Task-Switch Routine verweist. | ||
* Task-Switch: | * Task-Switch: | ||
| − | ** Wir legen die Register auf den Stack (das ist der Stack des unterbrochenen Tasks). Dann rufen wir | + | ** Wir legen die Register auf den Stack (das ist der Stack des unterbrochenen Tasks). Dann rufen wir unseren Scheduler auf. Er legt die übergebene Stackaddresse in der Task-Tabelle ab, sucht den nächsten Task heraus und gibt dessen Stackaddresse zurück. Die Task-Switch Routine lädt dann die Register von dem neuen Stack und kehrt mit einem IRET zurück zum Task. |
| − | |||
| − | unseren Scheduler auf. Er legt die übergebene Stackaddresse in der Task-Tabelle ab, sucht den | ||
| − | |||
| − | nächsten Task heraus und gibt dessen Stackaddresse zurück. Die Task-Switch Routine lädt dann die | ||
| − | |||
| − | Register von dem neuen Stack und kehrt mit einem IRET zurück zum Task. | ||
* Task erstellen: | * Task erstellen: | ||
| − | ** Diese Routine erhält als Parameter die ID, an der der Task in der Tabelle eingetragen werden soll, | + | ** Diese Routine erhält als Parameter die ID, an der der Task in der Tabelle eingetragen werden soll, und die Adresse des Codes. Wir blocken dann die Interrupts mit CLI und holen uns erst einmal Speicher für den Stack. Wir sichern dann die Adresse des Kernelstacks und laden den neuen. Dann benutzen wir einen kleinen Trick: Wir lassen den Stack des Tasks so aussehen, als wäre der Task gleich am Anfang unterbrochen worden, also legen wir die vorbereiteten Register auf den Stack. Dann stellen wir einfach den Kernelstack wieder her, tragen den Tasks in die Tabelle ein, erlauben die Interrupts wieder und fertig. |
| − | |||
| − | und die Adresse des Codes. Wir blocken dann die Interrupts mit CLI und holen uns erst einmal Speicher | ||
| − | |||
| − | für den Stack. Wir sichern dann die Adresse des Kernelstacks und laden den neuen. Dann benutzen wir | ||
| − | |||
| − | einen kleinen Trick: Wir lassen den Stack des Tasks so aussehen, als wäre der Task gleich am Anfang | ||
| − | |||
| − | unterbrochen worden, also legen wir die vorbereiteten Register auf den Stack. Dann stellen wir | ||
| − | |||
| − | einfach den Kernelstack wieder her, tragen den Tasks in die Tabelle ein, erlauben die Interrupts | ||
| − | |||
| − | wieder und fertig. | ||
== Der Code == | == Der Code == | ||
| − | Kommen wir nun zum Hauptteil dieses Tutorials: Dem Code. Ich werde hier erst ein Stück FreeBASIC-code | + | Kommen wir nun zum Hauptteil dieses Tutorials: Dem Code. Ich werde hier erst ein Stück FreeBASIC-code zeigen und dann erklären, was dieser Code macht. |
| − | |||
| − | zeigen und dann erklären, was dieser Code macht. | ||
Zuerst kommen ein paar Konstanten: | Zuerst kommen ein paar Konstanten: | ||
| Zeile 79: | Zeile 41: | ||
| − | Nicht wirklich schwer. Wir machen eine Typendefinition für die Task-Tabelle. Enthalten sind die | + | Nicht wirklich schwer. Wir machen eine Typendefinition für die Task-Tabelle. Enthalten sind die ProzessID (positive 16bit Zahl), die Stack-Adresse (positive 32bit Zahl) und der Status des Tasks (8bit Zahl). Danach legen wir ein Array an, das wir jetzt mit den Daten füttern können. |
| − | |||
| − | ProzessID (positive 16bit Zahl), die Stack-Adresse (positive 32bit Zahl) und der Status des Tasks | ||
| − | |||
| − | (8bit Zahl). Danach legen wir ein Array an, das wir jetzt mit den Daten füttern können | ||
| − | |||
| − | |||
| − | "currentPID" beinhaltet die ProzessID des aktuell laufenden Tasks. | + | Dann legen wir noch zwei Variablen an. "AllTasks" gibt an, wieviele Einträge der Tabelle belegt sind. "currentPID" beinhaltet die ProzessID des aktuell laufenden Tasks. |
function dec (byref tPTR as UINTEGER PTR) as UINTEGER PTR | function dec (byref tPTR as UINTEGER PTR) as UINTEGER PTR | ||
| Zeile 94: | Zeile 50: | ||
end function | end function | ||
| − | Das hat jetzt nichts direkt mit Multitasking zu tun, ist aber nützlich. Damit können wir so etwas wie | + | Das hat jetzt nichts direkt mit Multitasking zu tun, ist aber nützlich. Damit können wir so etwas wie "*--stack = 0" in FreeBASIC folgendermaßen schreiben: "*(dec(stack)) = 0". Kurz gesagt, nimmt sie einen UINTEGER-pointer und setzt ihn um 4bytes (also ein UINTEGER) runter. Das macht später einiges einfacher. |
| − | |||
| − | "*--stack = 0" in FreeBASIC folgendermaßen schreiben: "*(dec(stack)) = 0". Kurz gesagt, nimmt sie | ||
| − | |||
| − | einen UINTEGER-pointer und setzt ihn um 4bytes (also ein UINTEGER) runter. Das macht später einiges | ||
| − | |||
| − | einfacher. | ||
sub Multitasking_Init | sub Multitasking_Init | ||
| Zeile 107: | Zeile 57: | ||
end sub | end sub | ||
| − | Auch einfach. Wir programmieren den PIT so, dass er 100 mal in der Sekunde (vermutlich kein guter | + | Auch einfach. Wir programmieren den PIT so, dass er 100 mal in der Sekunde (vermutlich kein guter Wert für später) den IRQ 0 auslöst. Dann legen wir in der IDT einen Eintrag an, an der Stelle für IRQ 0. Da tragen wir die Adresse der Task-Switch Routine ein, und geben als Selector den Hex-Wert 0x08 an. Die Flags setzen wir auf 0x8E. |
| − | |||
| − | Wert für später) den IRQ 0 auslöst. Dann legen wir in der IDT einen Eintrag an, an der Stelle für IRQ | ||
| − | |||
| − | 0. Da tragen wir die Adresse der Task-Switch Routine ein, und geben als Selector den Hex-Wert 0x08 | ||
| − | |||
| − | an. Die Flags setzen wir auf 0x8E. | ||
sub Multitasking_Switch naked | sub Multitasking_Switch naked | ||
| Zeile 146: | Zeile 90: | ||
end sub | end sub | ||
| − | Kurz, oder? Als erstes fällt wohl das "naked" auf. Damit sagen wir FreeBASIC (Vorsicht, funktioniert | + | Kurz, oder? Als erstes fällt wohl das "naked" auf. Damit sagen wir FreeBASIC (Vorsicht, funktioniert erst aber Version 0.21!), dass er am Anfang und Ende nichts am Stack macht und kein "ret" anfügt. Sonst könnten wir die Routine nicht direkt in die IDT eintragen. |
| − | + | Zuerst legen wir die Register auf den Stack. Dann laden wir die Segmentregister wieder mit einem gültigen Wert, damit es keine Probleme gibt (wenn der Task die Werte zerstört hat, und wir das nicht machen würden, würde unser Kernel nicht mehr lange mitspielen). Dann folgt ein kleiner Trick, wie rufen den Scheduler direkt aus dem Assembler-Code auf. Dazu legen wir das Stack-Register (das ist der Stack des unterbrochenen Tasks) auf den Stack, da der Scheduler die Stack-Adresse als Parameter erwartet. Dann rufen wir den Scheduler auf, und er gitb uns eine neue Stackadresse in eax zurück. Diesen Wert laden wir in esp, und schon haben wir den Stack des neuen Tasks. | |
| − | erst aber Version 0.21!), dass er am Anfang und Ende nichts am Stack macht und kein "ret" anfügt. | ||
| − | |||
| − | Sonst könnten wir die Routine nicht direkt in die IDT eintragen. | ||
| − | Zuerst legen wir die Register auf den Stack. Dann laden wir die Segmentregister wieder mit einem | ||
| − | |||
| − | gültigen Wert, damit es keine Probleme gibt (wenn der Task die Werte zerstört hat, und wir das nicht | ||
| − | |||
| − | machen würden, würde unser Kernel nicht mehr lange mitspielen). Dann folgt ein kleiner Trick, wie | ||
| − | |||
| − | rufen den Scheduler direkt aus dem Assembler-Code auf. Dazu legen wir das Stack-Register (das ist der | ||
| − | |||
| − | Stack des unterbrochenen Tasks) auf den Stack, da der Scheduler die Stack-Adresse als Parameter | ||
| − | |||
| − | erwartet. Dann rufen wir den Scheduler auf, und er gitb uns eine neue Stackadresse in eax zurück. | ||
| − | |||
| − | Diesen Wert laden wir in esp, und schon haben wir den Stack des neuen Tasks. | ||
Wir müssen natürlich auch noch dem PIC sagen, dass wir mit unserem Interrupt fertig sind. | Wir müssen natürlich auch noch dem PIC sagen, dass wir mit unserem Interrupt fertig sind. | ||
| Zeile 180: | Zeile 108: | ||
end function | end function | ||
| − | Auch recht einfach. Wir sichern die Stackadresse im Tabelleneintrag des aktuellen Tasks. Dann erhöhen | + | Auch recht einfach. Wir sichern die Stackadresse im Tabelleneintrag des aktuellen Tasks. Dann erhöhen wir currentPID bis wir einen Task finden, der auf seine Aktivierung wartet. Sollte der Wert in currentPID zu hoch werden, setzen wir ihn wieder auf den Tabellenanfang. Haben wir als einen Task gefunden, geben wir dessen Stackadresse zurück. |
| − | |||
| − | wir currentPID bis wir einen Task finden, der auf seine Aktivierung wartet. Sollte der Wert in | ||
| − | |||
| − | currentPID zu hoch werden, setzen wir ihn wieder auf den Tabellenanfang. Haben wir als einen Task | ||
| − | |||
| − | gefunden, geben wir dessen Stackadresse zurück. | ||
sub Task_Create (ID as USHORT, priority as UBYTE, address as any ptr) | sub Task_Create (ID as USHORT, priority as UBYTE, address as any ptr) | ||
| Zeile 219: | Zeile 141: | ||
Tasks(ID).PID = ID | Tasks(ID).PID = ID | ||
| − | AllTasks += 1 '// We now have more tasks than | + | AllTasks += 1 '// We now have more tasks than before (what a surprise!) |
| − | |||
| − | before (what a surprise!) | ||
asm sti '// allow Interrupts again | asm sti '// allow Interrupts again | ||
end sub | end sub | ||
| − | Puh. Auf zur Erklärung: Wir prüfen zuerst, ob die gewünschte ProzessID schon belegt ist. Wenn ja, | + | Puh. Auf zur Erklärung: Wir prüfen zuerst, ob die gewünschte ProzessID schon belegt ist. Wenn ja, kerhen wir sofort zurück (besser wäre es, wenn wir einen Fehler-Wert zurückgeben würden). Dann erstellen wir eine Variable, in der wir die Stack-Adresse des Tasks zwischenspeichern. |
| − | |||
| − | kerhen wir sofort zurück (besser wäre es, wenn wir einen Fehler-Wert zurückgeben würden). Dann | ||
| − | |||
| − | erstellen wir eine Variable, in der wir die Stack-Adresse des Tasks zwischenspeichern. | ||
Wichtig ist, das wir hier mit "cli" die Interrupts ausschalten. | Wichtig ist, das wir hier mit "cli" die Interrupts ausschalten. | ||
| − | Dann holen wir uns 4kb Speicher und legen die Adresse dazu in "stack" ab (das "+ 1024" ist notwendig, | + | Dann holen wir uns 4kb Speicher und legen die Adresse dazu in "stack" ab (das "+ 1024" ist notwendig, weil der Stack von oben nach unten wächst. Also ist der Stackanfang am oberen Ende des reservierten Speicherbereichs! Zu beachten ist hier, das wir zwar 1024 addieren, aber die Adresse in Wirklichkeit um 4096 Bytes ansteigt. Das liegt daran, das wir das zu einem UINTEGER-Pointer addieren. Das ist also alles korrekt so.) |
| − | |||
| − | weil der Stack von oben nach unten wächst. Also ist der Stackanfang am oberen Ende des reservierten | ||
| − | |||
| − | Speicherbereichs! Zu beachten ist hier, das wir zwar 1024 addieren, aber die Adresse in Wirklichkeit | ||
| − | |||
| − | um 4096 Bytes ansteigt. Das liegt daran, das wir das zu einem UINTEGER-Pointer addieren. Das ist also | ||
| − | |||
| − | alles korrekt so.) | ||
| − | |||
| − | |||
| − | für was unsere Hilfsfunktion genutzt wird. Wir simulieren damit praktisch einen "push"-Befehl. | + | Jetzt basteln wir den Task-Stack zusammen, und zwar ohne den Stack zu wechseln! Hier wird auch klar, für was unsere Hilfsfunktion genutzt wird. Wir simulieren damit praktisch einen "push"-Befehl. Natürlich legen wir hier keine richtigen Register auf den Stack, sondern nur Anfangs-Werte.Dann sieht es so aus, als ob der Task sofot am Anfang unterbrochen worden wäre, so gibt es keine Probleme beim ersten aktivieren des Tasks. |
| − | + | Würden wir noch ein Register cr3 (für Paging) dazu legen, müssten wir natürlich auch unsere Switch-Routine anpassen, sonst schrotten wir unseren Stack. | |
| − | + | Nun legen wir den Tabelleneintrag für unseren Task an. Wir setzen ihn auf ready und legen die Stackadresse rein. Die Stack-Adresse ist tatsächlich korrekt, dafür hat unsere Hilfsfunktion schon gesorgt. Naja, dann schreiben wir noch die ProzessID rein und erhöhen den Wert von "AllTasks" um eins, da wir ja jetzt einen Task mehr haben. | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | Nun legen wir den Tabelleneintrag für unseren Task an. Wir setzen ihn auf ready und legen die | ||
| − | |||
| − | Stackadresse rein. Die Stack-Adresse ist tatsächlich korrekt, dafür hat unsere Hilfsfunktion schon | ||
| − | |||
| − | gesorgt. Naja, dann schreiben wir noch die ProzessID rein und erhöhen den Wert von "AllTasks" um | ||
| − | |||
| − | eins, da wir ja jetzt einen Task mehr haben. | ||
Abschließend erlauben wir mit "sti" die Interrupts wieder und fertig sind wir. | Abschließend erlauben wir mit "sti" die Interrupts wieder und fertig sind wir. | ||
| Zeile 270: | Zeile 162: | ||
== Abschließende Worte == | == Abschließende Worte == | ||
| − | Puh. Fertig. Es hat mir Spaß gemacht, dieses Tutorial zu schreiben, und ich hoffe, es hat euch auch | + | Puh. Fertig. Es hat mir Spaß gemacht, dieses Tutorial zu schreiben, und ich hoffe, es hat euch auch Spaß gemacht, es zu lesen. Solltet ihr etwas nicht verstanden haben, könnt ihr eure Fragen im Forum posten. Wahrscheinlich wäre den meisten C-Code hier lieber gewesen, aber ich kann nicht gut C, außerdem ist so das Risiko geringer, dass jemand per copy&paste den Code übernimmt ohne ihn zu verstehen ;). Ich hoffe aber trotzdem, das alles verständlich war. |
| − | |||
| − | Spaß gemacht, es zu lesen. Solltet ihr etwas nicht verstanden haben, könnt ihr eure Fragen im Forum | ||
| − | + | Natürlich sind wir hier noch nicht beim perfekten Multitasking angelangt. Wie gesagt müsste cr3 noch gesichert werden, auch Prioritäten wären wünschenswert. Also hört hier nicht einfach auf, sondern bastelt noch ein wenig, und verbessert euren Code. Dieses Tutorial zeigt euch nur wie es geht, was ihr letztendlich damit anstellt, ist eure Sache ;) | |
| − | + | Noch ein Hinweis: Der Code stammt aus meinem Kernel, den ihr hier finden könnt: http://sourceforge.net/projects/frostkernel | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | Noch ein Hinweis: Der Code stammt aus meinem Kernel, den ihr hier finden könnt: | ||
| − | |||
| − | http://sourceforge.net/projects/frostkernel | ||
Happy Coding, | Happy Coding, | ||
| Zeile 299: | Zeile 175: | ||
[http://www.fh-zwickau.de/doc/prmo/pmtutor/text/ Protected-Mode Tutorial der FH-Zwickau] | [http://www.fh-zwickau.de/doc/prmo/pmtutor/text/ Protected-Mode Tutorial der FH-Zwickau] | ||
| − | [http://code.google.com/p/onyxkernel/wiki/SoftwareMultitasking Software-Multitasking Tutorial auf | + | [http://code.google.com/p/onyxkernel/wiki/SoftwareMultitasking Software-Multitasking Tutorial auf google code] |
| − | |||
| − | google code] | ||
[[Category:Tutorials]] | [[Category:Tutorials]] | ||