| Zeile 19: | Zeile 19: | ||
Durch eine einfache Abwandlung des Testprogramms wird der Fehler aber deutlich: | Durch eine einfache Abwandlung des Testprogramms wird der Fehler aber deutlich: | ||
| − | <c>#include <stdint.h> | + | <syntaxhighlight lang="c">#include <stdint.h> |
int i = 0; | int i = 0; | ||
| Zeile 30: | Zeile 30: | ||
while(1); | while(1); | ||
| − | }</ | + | }</syntaxhighlight> |
Es wird sicher jeder zustimmen, dass dieses Programm eigentlich die gleiche Ausgabe haben sollte. Dadurch, dass ''i'' jetzt aber nicht mehr auf dem Stack, sondern in ''.data'' liegt, wird es aber zwischen den beiden Tasks geteilt und wir bekommen: | Es wird sicher jeder zustimmen, dass dieses Programm eigentlich die gleiche Ausgabe haben sollte. Dadurch, dass ''i'' jetzt aber nicht mehr auf dem Stack, sondern in ''.data'' liegt, wird es aber zwischen den beiden Tasks geteilt und wir bekommen: | ||
| Zeile 61: | Zeile 61: | ||
Nachdem wir uns mit den theoretischen Grundlagen ein bisschen befasst haben, kommen wir zur zentralen Frage: Wie schaltet man Paging ein? Die einfach Antwort lautet: Das Paging-Bit (Bit 31) im Steuerregister ''cr0'' muss gesetzt werden (direkt nach dem Initialisieren der physischen Speicherverwaltung ist vermutlich ein guter Platz, um ''vmm_init'' aufzurufen): | Nachdem wir uns mit den theoretischen Grundlagen ein bisschen befasst haben, kommen wir zur zentralen Frage: Wie schaltet man Paging ein? Die einfach Antwort lautet: Das Paging-Bit (Bit 31) im Steuerregister ''cr0'' muss gesetzt werden (direkt nach dem Initialisieren der physischen Speicherverwaltung ist vermutlich ein guter Platz, um ''vmm_init'' aufzurufen): | ||
| − | <c>void vmm_init(void) | + | <syntaxhighlight lang="c">void vmm_init(void) |
{ | { | ||
uint32_t cr0; | uint32_t cr0; | ||
| Zeile 68: | Zeile 68: | ||
cr0 |= (1 << 31); | cr0 |= (1 << 31); | ||
asm volatile("mov %0, %%cr0" : : "r" (cr0)); | asm volatile("mov %0, %%cr0" : : "r" (cr0)); | ||
| − | }</ | + | }</syntaxhighlight> |
Wenn wir den Kernel jetzt aber ausprobieren, merken wir schnell, dass das so keine gute Idee war: Ein Triple Fault führt dazu, dass es einen CPU-Reset gibt. Was ist passiert? Schauen wir uns an, was qemu dazu zu sagen hat: Mit dem Paramenter ''-d int,cpu_reset'' werden alle Interrupts in die Logdatei in ''/tmp/qemu.log'' geschrieben (unter Windows kann im Monitor per ''logfile'' ein anderer Pfad gesetzt werden; ab qemu 0.13 wird standardmäßig ''qemu.log'' im aktuellen Verzeichnis benutzt). Die interessanten Einträge sehen (mit zusammengekürztem Registerdump) wie folgt aus: | Wenn wir den Kernel jetzt aber ausprobieren, merken wir schnell, dass das so keine gute Idee war: Ein Triple Fault führt dazu, dass es einen CPU-Reset gibt. Was ist passiert? Schauen wir uns an, was qemu dazu zu sagen hat: Mit dem Paramenter ''-d int,cpu_reset'' werden alle Interrupts in die Logdatei in ''/tmp/qemu.log'' geschrieben (unter Windows kann im Monitor per ''logfile'' ein anderer Pfad gesetzt werden; ab qemu 0.13 wird standardmäßig ''qemu.log'' im aktuellen Verzeichnis benutzt). Die interessanten Einträge sehen (mit zusammengekürztem Registerdump) wie folgt aus: | ||
| Zeile 128: | Zeile 128: | ||
Aber fangen wir zunächst einfach einmal damit an, unseren virtuellen Adressraum (auch ''Speicherkontext'' genannt) in Code zu gießen. Der gesamte Adressraum wird durch das Page Directory beschrieben. Wir haben also folgendes (später kommt hier noch mehr dazu, deswegen gleich ein struct): | Aber fangen wir zunächst einfach einmal damit an, unseren virtuellen Adressraum (auch ''Speicherkontext'' genannt) in Code zu gießen. Der gesamte Adressraum wird durch das Page Directory beschrieben. Wir haben also folgendes (später kommt hier noch mehr dazu, deswegen gleich ein struct): | ||
| − | <c>struct vmm_context { | + | <syntaxhighlight lang="c">struct vmm_context { |
uint32_t* pagedir; | uint32_t* pagedir; | ||
| − | };</ | + | };</syntaxhighlight> |
Ein neues leeres Page Directory anzulegen ist auch nicht schwer: Wir holen uns einfach eine freie Page (sowohl Page Directory als auch Page Tables sind genau 4k groß) und schreiben sie mit Nullen voll: | Ein neues leeres Page Directory anzulegen ist auch nicht schwer: Wir holen uns einfach eine freie Page (sowohl Page Directory als auch Page Tables sind genau 4k groß) und schreiben sie mit Nullen voll: | ||
| − | <c>struct vmm_context* vmm_create_context(void) | + | <syntaxhighlight lang="c">struct vmm_context* vmm_create_context(void) |
{ | { | ||
struct vmm_context* context = pmm_alloc(); | struct vmm_context* context = pmm_alloc(); | ||
| Zeile 147: | Zeile 147: | ||
return context; | return context; | ||
| − | }</ | + | }</syntaxhighlight> |
Jetzt müssen wir in diesem Page Directory nur noch die passenden ''Mappings'' anlegen, d.h. für jede benutzte virtuelle Adresse die zugehörige physische Adresse angeben. Im Moment ist das nicht besonders schwer. Wir müssen folgendermaßen vorgehen: | Jetzt müssen wir in diesem Page Directory nur noch die passenden ''Mappings'' anlegen, d.h. für jede benutzte virtuelle Adresse die zugehörige physische Adresse angeben. Im Moment ist das nicht besonders schwer. Wir müssen folgendermaßen vorgehen: | ||
| Zeile 178: | Zeile 178: | ||
| − | <c>int vmm_map_page(struct vmm_context* context, uintptr_t virt, uintptr_t phys) | + | <syntaxhighlight lang="c">int vmm_map_page(struct vmm_context* context, uintptr_t virt, uintptr_t phys) |
{ | { | ||
/* Page Table heraussuchen bzw. anlegen */ | /* Page Table heraussuchen bzw. anlegen */ | ||
| Zeile 219: | Zeile 219: | ||
cr0 |= (1 << 31); | cr0 |= (1 << 31); | ||
asm volatile("mov %0, %%cr0" : : "r" (cr0)); | asm volatile("mov %0, %%cr0" : : "r" (cr0)); | ||
| − | }</ | + | }</syntaxhighlight> |
Mit diesem Code funktioniert unser Kernel jetzt wieder. Er bricht allerdings mit einer Exception 14 (einem Page Fault) ab: Das passiert, sobald er in einen Userspace-Task springen möchte. Bisher haben wir alle Pages nur für den Kernel gemappt. Man könnte jetzt natürlich hergehen und einfach das User-Flag setzen, dann funktionieren auch die Tasks wieder. Aber zukunftsfähig ist die Methode, einfach mal die ersten vier Megabytes zu mappen natürlich nicht. | Mit diesem Code funktioniert unser Kernel jetzt wieder. Er bricht allerdings mit einer Exception 14 (einem Page Fault) ab: Das passiert, sobald er in einen Userspace-Task springen möchte. Bisher haben wir alle Pages nur für den Kernel gemappt. Man könnte jetzt natürlich hergehen und einfach das User-Flag setzen, dann funktionieren auch die Tasks wieder. Aber zukunftsfähig ist die Methode, einfach mal die ersten vier Megabytes zu mappen natürlich nicht. | ||
| Zeile 259: | Zeile 259: | ||
Nachdem der Kernel darauf vorbereitet ist, Speicherbereiche einzeln zu mappen und mit den richtigen Berechtigungen zu versehen, können wir uns endlich daran machen, den Konflikt unserer beiden Tasks aufzulösen. Dazu erweitern wir zunächst die Taskstruktur um einen Eintrag für das Page Directory (ja, unsere Tasks entsprechen im Moment Prozessen - mehrere Threads würden denselbenb Adressraum benutzen, wenn wir sie denn unterstützen würden). | Nachdem der Kernel darauf vorbereitet ist, Speicherbereiche einzeln zu mappen und mit den richtigen Berechtigungen zu versehen, können wir uns endlich daran machen, den Konflikt unserer beiden Tasks aufzulösen. Dazu erweitern wir zunächst die Taskstruktur um einen Eintrag für das Page Directory (ja, unsere Tasks entsprechen im Moment Prozessen - mehrere Threads würden denselbenb Adressraum benutzen, wenn wir sie denn unterstützen würden). | ||
| − | <c>struct task { | + | <syntaxhighlight lang="c">struct task { |
struct cpu_state* cpu_state; | struct cpu_state* cpu_state; | ||
struct task* next; | struct task* next; | ||
struct vmm_context* context; | struct vmm_context* context; | ||
| − | };</ | + | };</syntaxhighlight> |
Beim Anlegen eines Tasks müssen wir jetzt einen neuen Speicherkontext anlegen (dafür gibt es ja schon ''vmm_create_context'') und ggf. den Kernelteil des Page Directory kopieren. ''init_elf'' kann den Speicher jetzt im Kernel an eine beliebige Stelle im aktuellen Page Directory mappen, muss das Programm aber zusätzlich noch in dessen Page Directory an die richtige Stelle (nämlich 0x200000) mappen. | Beim Anlegen eines Tasks müssen wir jetzt einen neuen Speicherkontext anlegen (dafür gibt es ja schon ''vmm_create_context'') und ggf. den Kernelteil des Page Directory kopieren. ''init_elf'' kann den Speicher jetzt im Kernel an eine beliebige Stelle im aktuellen Page Directory mappen, muss das Programm aber zusätzlich noch in dessen Page Directory an die richtige Stelle (nämlich 0x200000) mappen. | ||
| Zeile 269: | Zeile 269: | ||
Den Wechsel des Page Directory kann man am Ende des Interrupt-Handlers durchführen: | Den Wechsel des Page Directory kann man am Ende des Interrupt-Handlers durchführen: | ||
| − | <c>struct cpu_state* handle_interrupt(struct cpu_state* cpu) | + | <syntaxhighlight lang="c">struct cpu_state* handle_interrupt(struct cpu_state* cpu) |
{ | { | ||
... | ... | ||
| Zeile 278: | Zeile 278: | ||
return new_cpu; | return new_cpu; | ||
| − | }</ | + | }</syntaxhighlight> |
Weil ein Kontextwechsel teuer ist, wird cr3 nur dann neu geladen, wenn tatsächlich ein anderer Task laufen soll. Außerdem brauchen wir jetzt für cr3 die ''physische'' Adresse des Page Directory. ''vmm_create_context'' muss also sowohl die virtuelle als auch die physische Adresse abspeichern, damit wir mit dem Page Directory arbeiten können. | Weil ein Kontextwechsel teuer ist, wird cr3 nur dann neu geladen, wenn tatsächlich ein anderer Task laufen soll. Außerdem brauchen wir jetzt für cr3 die ''physische'' Adresse des Page Directory. ''vmm_create_context'' muss also sowohl die virtuelle als auch die physische Adresse abspeichern, damit wir mit dem Page Directory arbeiten können. | ||