| Zeile 13: | Zeile 13: | ||
Für jeden Task im Voraus Speicher zu reservieren, ist zu unflexibel, also müssen die Variablen ''stack_a'', ''stack_b'', ''user_stack_a'' und ''user_stack_b'' weg. Die Funktion ''init_task'' braucht die entsprechenden Parameter jetzt auch nicht mehr, denn sie soll sich den benötigten Speicher jetzt selbst (und zwar zur Laufzeit) holen. Wir schreiben also ungefähr so etwas: | Für jeden Task im Voraus Speicher zu reservieren, ist zu unflexibel, also müssen die Variablen ''stack_a'', ''stack_b'', ''user_stack_a'' und ''user_stack_b'' weg. Die Funktion ''init_task'' braucht die entsprechenden Parameter jetzt auch nicht mehr, denn sie soll sich den benötigten Speicher jetzt selbst (und zwar zur Laufzeit) holen. Wir schreiben also ungefähr so etwas: | ||
| − | <c>struct cpu_state* init_task(void* entry) | + | <syntaxhighlight lang=c>struct cpu_state* init_task(void* entry) |
{ | { | ||
uint8_t* stack = pmm_alloc(); | uint8_t* stack = pmm_alloc(); | ||
uint8_t* user_stack = pmm_alloc(); | uint8_t* user_stack = pmm_alloc(); | ||
... | ... | ||
| − | }</ | + | }</syntaxhighlight> |
Den neuen Speicher muss uns die [[physische Speicherverwaltung]] (engl. ''physical memory manager'', daher das ''pmm'') zur Verfügung stellen. Die Aufgabe der Speicherverwaltung ist es - Überraschung - den vorhandenen Speicher zu verwalten, so dass zur Laufzeit neuer Speicher angefordert werden und auch wieder freigegeben werden kann. Man arbeitet an dieser Stelle üblicherweise mit Speicherblöcken einer festen Größe - auf x86 sind das üblicherweise 4 Kilobyte (was nicht völlig willkürlich ist: Wir werden später dazu kommen, dass das genau die Größe ist, mit der man für virtuellen Speicher auch arbeiten muss). | Den neuen Speicher muss uns die [[physische Speicherverwaltung]] (engl. ''physical memory manager'', daher das ''pmm'') zur Verfügung stellen. Die Aufgabe der Speicherverwaltung ist es - Überraschung - den vorhandenen Speicher zu verwalten, so dass zur Laufzeit neuer Speicher angefordert werden und auch wieder freigegeben werden kann. Man arbeitet an dieser Stelle üblicherweise mit Speicherblöcken einer festen Größe - auf x86 sind das üblicherweise 4 Kilobyte (was nicht völlig willkürlich ist: Wir werden später dazu kommen, dass das genau die Größe ist, mit der man für virtuellen Speicher auch arbeiten muss). | ||
| Zeile 32: | Zeile 32: | ||
Eine relativ simple Art, den Speicher zu verwalten, ist eine Bitmap, daher benutzen wir sie hier als Beispiel. Die Idee ist ganz einfach, dass man für jede Seite (also für je 4k) ein Bit benutzt, das anzeigt, ob die Seite frei oder belegt ist. Man könnte die Datenstruktur also wie folgt definieren: | Eine relativ simple Art, den Speicher zu verwalten, ist eine Bitmap, daher benutzen wir sie hier als Beispiel. Die Idee ist ganz einfach, dass man für jede Seite (also für je 4k) ein Bit benutzt, das anzeigt, ob die Seite frei oder belegt ist. Man könnte die Datenstruktur also wie folgt definieren: | ||
| − | <c>/* | + | <syntaxhighlight lang=c>/* |
* Der Einfachheit halber deklarieren wir die maximal benoetige Bitmapgroesse | * Der Einfachheit halber deklarieren wir die maximal benoetige Bitmapgroesse | ||
* statisch (Wir brauchen 4 GB / 4 kB = 1M Bits; 1M Bits sind 1M/32 = 32k | * statisch (Wir brauchen 4 GB / 4 kB = 1M Bits; 1M Bits sind 1M/32 = 32k | ||
| Zeile 40: | Zeile 40: | ||
*/ | */ | ||
#define BITMAP_SIZE 32768 | #define BITMAP_SIZE 32768 | ||
| − | static uint32_t bitmap[BITMAP_SIZE];</ | + | static uint32_t bitmap[BITMAP_SIZE];</syntaxhighlight> |
Wenn jetzt zum Beispiel bitmap[0] = 0xc wäre, würde das bedeuten, dass die Seiten 2 und 3 frei sind (von Null an gezählt; also der Speicherbererich von 8k bis 16k) während die Seiten 0, 1 und 4 bis 31 belegt sind. Beim Reservieren einer neuen Seite geht man jetzt das Array durch bis man ein gesetztes Bit, also eine freie Seite findet. Man löscht dann das Bit und gibt die gefundene Adresse zurück. Beim Freigeben wird das Bit entsprechend wieder gesetzt. | Wenn jetzt zum Beispiel bitmap[0] = 0xc wäre, würde das bedeuten, dass die Seiten 2 und 3 frei sind (von Null an gezählt; also der Speicherbererich von 8k bis 16k) während die Seiten 0, 1 und 4 bis 31 belegt sind. Beim Reservieren einer neuen Seite geht man jetzt das Array durch bis man ein gesetztes Bit, also eine freie Seite findet. Man löscht dann das Bit und gibt die gefundene Adresse zurück. Beim Freigeben wird das Bit entsprechend wieder gesetzt. | ||
| Zeile 62: | Zeile 62: | ||
Wir müssen also zunächst einmal zurück in die start.S und den Pointer weitergeben: | Wir müssen also zunächst einmal zurück in die start.S und den Pointer weitergeben: | ||
| − | <asm>// C-Code aufrufen und Multiboot-Infostruktur als Parameter uebergeben | + | <syntaxhighlight lang=asm>// C-Code aufrufen und Multiboot-Infostruktur als Parameter uebergeben |
push %ebx | push %ebx | ||
| − | call init</ | + | call init</syntaxhighlight> |
Und auch die init-Funktion gibt den Pointer einfach direkt weiter an pmm_init: | Und auch die init-Funktion gibt den Pointer einfach direkt weiter an pmm_init: | ||
| − | <c>void init(struct multiboot_info *mb_info) | + | <syntaxhighlight lang=c>void init(struct multiboot_info *mb_info) |
{ | { | ||
pmm_init(mb_info); | pmm_init(mb_info); | ||
| − | ...</ | + | ...</syntaxhighlight> |
pmm_init geht anschließend wie folgt vor: | pmm_init geht anschließend wie folgt vor: | ||
| Zeile 78: | Zeile 78: | ||
Widmen wir uns zunächst der Memory Map. Wir haben Basisadresse und Länge und können damit über alle Einträge laufen und die freien Bereiche (Typ 1) als frei markieren: | Widmen wir uns zunächst der Memory Map. Wir haben Basisadresse und Länge und können damit über alle Einträge laufen und die freien Bereiche (Typ 1) als frei markieren: | ||
| − | <c>struct multiboot_mmap* mmap = mb_info->mbs_mmap_addr; | + | <syntaxhighlight lang=c>struct multiboot_mmap* mmap = mb_info->mbs_mmap_addr; |
struct multiboot_mmap* mmap_end = (void*) | struct multiboot_mmap* mmap_end = (void*) | ||
((uintptr_t) mb_info->mbs_mmap_addr + mb_info->mbs_mmap_length); | ((uintptr_t) mb_info->mbs_mmap_addr + mb_info->mbs_mmap_length); | ||
| Zeile 94: | Zeile 94: | ||
} | } | ||
mmap++; | mmap++; | ||
| − | }</ | + | }</syntaxhighlight> |
Den vom OS benutzten Speicher als belegt zu markieren (z.B. die Multiboot-Infostruktur oder zusätzliche Multiboot-Module) ist einfach und braucht wohl keine weitere Erläuterung. Die einzige Stelle, die nicht offensichtlich sein dürfte, ist, wie man herausbekommt, wo der Kernel eigentlich anfängt und aufhört. Dazu sagen wir dem Linker im Linkerskript, er soll ein neues Symbol vor den ganzen Sektionen anlegen und ein weiteres nach den Sektionen: | Den vom OS benutzten Speicher als belegt zu markieren (z.B. die Multiboot-Infostruktur oder zusätzliche Multiboot-Module) ist einfach und braucht wohl keine weitere Erläuterung. Die einzige Stelle, die nicht offensichtlich sein dürfte, ist, wie man herausbekommt, wo der Kernel eigentlich anfängt und aufhört. Dazu sagen wir dem Linker im Linkerskript, er soll ein neues Symbol vor den ganzen Sektionen anlegen und ein weiteres nach den Sektionen: | ||
| Zeile 118: | Zeile 118: | ||
In C können wir diese Symbole anschließend als extern-Variablen deklarieren. Da die Variable keinen Datentyp hat (es zeigt ja nur ein Symbol an diese Stelle, ohne dass dort Platz für irgendwelche Daten reserviert wäre), ist void die richtige Wahl (da dieses Nichts zudem so ziemlich unveränderlich ist, kann man gleich ein const void verwenden (sonst kann es passieren, dass der Compiler eine Warnung ausgibt)). Wir sind hinterher sowieso nur an der Adresse der Variablen interessiert: | In C können wir diese Symbole anschließend als extern-Variablen deklarieren. Da die Variable keinen Datentyp hat (es zeigt ja nur ein Symbol an diese Stelle, ohne dass dort Platz für irgendwelche Daten reserviert wäre), ist void die richtige Wahl (da dieses Nichts zudem so ziemlich unveränderlich ist, kann man gleich ein const void verwenden (sonst kann es passieren, dass der Compiler eine Warnung ausgibt)). Wir sind hinterher sowieso nur an der Adresse der Variablen interessiert: | ||
| − | <c>extern const void kernel_start; | + | <syntaxhighlight lang=c>extern const void kernel_start; |
extern const void kernel_end; | extern const void kernel_end; | ||
| Zeile 125: | Zeile 125: | ||
pmm_mark_used((void*) addr); | pmm_mark_used((void*) addr); | ||
addr += 0x1000; | addr += 0x1000; | ||
| − | }</ | + | }</syntaxhighlight> |
==Taskliste== | ==Taskliste== | ||
| Zeile 131: | Zeile 131: | ||
Der Task muss jetzt aber immer noch von Hand ins task_states-Array geschrieben werden. Ein Array dazu noch, das besser kein Array wäre, denn wir wollen die Anzahl der Tasks ja nicht begrenzen. Es wäre also an der Zeit, eine verkettete Liste herzunehmen. Im Moment haben wir noch Task und Prozessorzustand gleichgesetzt, aber ein Task ist natürlich noch mehr. Daher wäre jetzt eine gute Gelegenheit, einen Task neu zu definieren: | Der Task muss jetzt aber immer noch von Hand ins task_states-Array geschrieben werden. Ein Array dazu noch, das besser kein Array wäre, denn wir wollen die Anzahl der Tasks ja nicht begrenzen. Es wäre also an der Zeit, eine verkettete Liste herzunehmen. Im Moment haben wir noch Task und Prozessorzustand gleichgesetzt, aber ein Task ist natürlich noch mehr. Daher wäre jetzt eine gute Gelegenheit, einen Task neu zu definieren: | ||
| − | <c>struct task { | + | <syntaxhighlight lang=c>struct task { |
struct cpu_state* cpu_state; | struct cpu_state* cpu_state; | ||
struct task* next; | struct task* next; | ||
| Zeile 137: | Zeile 137: | ||
static struct task* first_task = NULL; | static struct task* first_task = NULL; | ||
| − | static struct task* current_task = NULL;</ | + | static struct task* current_task = NULL;</syntaxhighlight> |
init_task muss jetzt statt nur dem Stack entsprechend auch eine Taskstruktur anlegen und zurückgeben (wobei sie strenggenommen gar nichts zurückgeben müsste, denn der Task wird jetzt gleich richtig überall eintragen. Aber irgendwann später können wir den Rückgabewert garantiert brauchen). Ändern wir also das Ende ab: | init_task muss jetzt statt nur dem Stack entsprechend auch eine Taskstruktur anlegen und zurückgeben (wobei sie strenggenommen gar nichts zurückgeben müsste, denn der Task wird jetzt gleich richtig überall eintragen. Aber irgendwann später können wir den Rückgabewert garantiert brauchen). Ändern wir also das Ende ab: | ||
| − | <c> /* | + | <syntaxhighlight lang=c> /* |
* Neue Taskstruktur anlegen und in die Liste einhaengen | * Neue Taskstruktur anlegen und in die Liste einhaengen | ||
*/ | */ | ||
| Zeile 148: | Zeile 148: | ||
first_task = task; | first_task = task; | ||
return task; | return task; | ||
| − | }</ | + | }</syntaxhighlight> |
Der Scheduler muss jetzt statt den Arrayindex weiterzuzählen zum nächsten Listenelement weitergehen (current_task = current_task->next) und wenn das Ende erreicht ist (next also NULL ist), wieder beim ersten Task anfangen. Das Array task_states, num_tasks und das alte current_task mit dem Arrayindex sind jetzt unnötig geworden und können gelöscht werden. Einen neuen Task anzulegen bedeutet jetzt nur noch ein init_task mit dem passenden Einsprungpunkt aufzurufen. | Der Scheduler muss jetzt statt den Arrayindex weiterzuzählen zum nächsten Listenelement weitergehen (current_task = current_task->next) und wenn das Ende erreicht ist (next also NULL ist), wieder beim ersten Task anfangen. Das Array task_states, num_tasks und das alte current_task mit dem Arrayindex sind jetzt unnötig geworden und können gelöscht werden. Einen neuen Task anzulegen bedeutet jetzt nur noch ein init_task mit dem passenden Einsprungpunkt aufzurufen. | ||