Im
vorherigen Artikel haben wir das Kernel-Systemprotokoll implementiert. Jetzt ist es Zeit, einen dynamischen Speichermanager zu implementieren.
Inhaltsverzeichnis
- Build-System (make, gcc, gas). Erster Start (Multiboot). Starten Sie (qemu). C-Bibliothek (strcpy, memcpy, strext).
- C-Bibliothek (sprintf, strcpy, strcmp, strtok, va_list ...). Erstellen der Bibliothek im Kernelmodus und im Benutzeranwendungsmodus.
- Das Kernel-Systemprotokoll. Videospeicher Ausgabe an das Terminal (kprintf, kpanic, kassert).
- Dynamischer Speicher, Heap (kmalloc, kfree).
- Organisation der Speicher- und Interrupt-Behandlung (GDT, IDT, PIC, Syscall). Ausnahmen
- Virtueller Speicher (Seitenverzeichnis und Seitentabelle).
- Prozess. Planer Multitasking. Systemaufrufe (kill, exit, ps).
- Das Dateisystem des Kernels (initrd), elf und seiner Interna. Systemaufrufe (exec).
- Zeichengerätetreiber. Systemaufrufe (ioctl, fopen, fread, fwrite). C-Bibliothek (fopen, fclose, fprintf, fscanf).
- Shell als komplettes Programm für den Kernel.
- Benutzerschutzmodus (Ring3). Aufgabenstatussegment (tss).
Dynamischer Kernelspeicher. Ein Haufen
Die Kernel-Heap-Oberfläche sieht folgendermaßen aus:
void *kmalloc(size_t size); void kfree(void *addr);
Da es noch keine Heaps gibt, müssen Aufzeichnungen von belegten und freien Speicherbereichen gespeichert werden.
in einem Array von begrenzter Größe. Je kleiner die Abmessung ist, desto geringer ist die Heap-Kapazität um
kleine Fragmente. Unter Linux werden beispielsweise viele Kernelstrukturen wiederverwendbar gespeichert
Speicher, dessen Größe sich beim Freigeben nicht ändert (Platte). Aber davon sind wir noch weit entfernt,
weil Entwicklung ein iterativer Prozess ist.
Jedes Element, das ein Element einer Liste sein soll, die über ein Array organisiert ist
erforderlich, um eine Schnittstelle zu implementieren.
struct slist_head_t { struct slist_head_t *prev; struct slist_head_t *next; bool is_valid; void *data; } attribute(packed);
In C gibt es keine Schnittstellen, diese werden jedoch nicht benötigt. Wir wandeln einfach den Zeiger eines Objekts auf den Typ struct slist_head_t * um, wenn dieses Objekt physisch so aussieht:
struct my_list_entry_t { struct slist_head_t list_head; ... }
Damit der verallgemeinerte Algorithmus jedoch die Dimension des Arrays kennt, in dem die Liste gespeichert wird
und die Größe seines Elements, das erste und letzte Element der Liste und die Adresse des Arrays, in dem
Wenn eine Liste gespeichert ist, benötigt jede Funktion der Betriebsliste einen anderen Zeiger
zur Listendefinitionsstruktur:
struct slist_definition_t { size_t base; u_int slots; size_t slot_size; struct slist_head_t *head; struct slist_head_t *tail; };
Die Funktionen zum Bedienen der Liste sehen folgendermaßen aus:
struct slist_head_t *slist_insert_entry_after(struct slist_definition_t *list, struct slist_head_t *pos); struct slist_head_t *slist_insert_entry_before(struct slist_definition_t *list, struct slist_head_t *pos); void slist_delete_entry(struct slist_definition_t *list, struct slist_head_t *entry);
Funktionsalgorithmus einfügen:
Suchen Sie einen freien Steckplatz im Array.
Fülle es mit einem neuen Element.
Korrigieren Sie die Zeiger logisch (nicht physisch) benachbarter Listenelemente.
Slot als besetzt markieren.
Passen Sie den Anfang und das Ende der Liste an.
Der Algorithmus der Funktion, die aus der Liste entfernt werden soll:
Korrigieren Sie die Zeiger logisch (nicht physisch) benachbarter Listenelemente.
Passen Sie den Anfang und das Ende der Liste an.
Steckplatz als frei markieren.
Der dynamische Speicher wird durch eine Liste mit folgenden Elementen beschrieben:
struct kheap_entry_t { struct slist_head_t list_head; size_t addr; size_t size; bool is_buzy; } attribute(packed);
Ein Array, das den dynamischen Speicher aus solchen Datensätzen beschreibt, sieht folgendermaßen aus:
struct kheap_entry_t kheap_blocks[KHEAP_MAX_ENTRIES];
Das Definieren einer Liste von Speicherbereichen sieht folgendermaßen aus:
struct slist_definition_t kheap_list = { .head = null, .tail = null, .slot_size = sizeof(struct kheap_entry_t), .slots = KHEAP_MAX_ENTRIES, .base = (size_t)kheap_blocks};
Wir haben also das dynamische Gedächtnis beschrieben. Der gesamte Speicher innerhalb eines bestimmten Bereichs
ist in Blöcke beliebiger Größe unterteilt, von denen jeder entweder leer sein kann,
entweder beschäftigt. Zusätzlich wird jeder Block durch ein Array-Element mit dem Flag is_valid = true beschrieben.
Das ist ein Listenelement.
Betrachten Sie nun den einfachsten Heap-Allokator-Algorithmus (kmalloc):
Wenn keine freien Blöcke vorhanden sind, wählen Sie einen neuen Block mit der erforderlichen Größe aus (jedoch nicht mehr als die maximale Heap-Größe).
Ansonsten schauen wir uns alle freien Blöcke an. Wenn wir einen freien Block finden, schauen Sie sich seine Größe an. Wenn es reicht, nimm es. Wenn es einen Überschuss gibt, erstellen Sie einen neuen leeren Block mit der Größe des Überschusses. Andernfalls erweitern wir, wenn es nicht ausreicht und letzteres, die Heap-Grenze (aber nicht mehr als das Maximum).
extern void* kmalloc(size_t size) { struct kheap_entry_t* current_data = null; struct slist_head_t* current = null; struct slist_head_t* head = kheap_list.head; assert(size > 0); for (current = head; current != null; current = current->next) { current_data = (struct kheap_entry_t*)current->data; if (current_data->is_buzy) { continue; } if (current_data->size < size) { if (current->prev != null) { struct slist_head_t* sibling = current->prev; struct kheap_entry_t* sibling_data = (struct kheap_entry_t*)sibling->data; if (!sibling_data->is_buzy) { size_t lack = size - current_data->size; sibling_data->size -= lack; current_data->addr -= lack; current_data->size += lack; assert(current_data->size == size); if (sibling_data->size == 0) { slist_delete_entry(&kheap_list, sibling); } current_data->is_buzy = true; assert(current_data->addr >= KHEAP_START_ADDR); assert(current_data->addr < KHEAP_END_ADDR); return (void*)current_data->addr; } } if (current->next == null) { size_t heap_end_addr = current_data->addr + current_data->size; size_t lack = size - current_data->size; if (heap_end_addr + lack < KHEAP_END_ADDR) { current_data->size += lack; current_data->is_buzy = true; assert(current_data->addr >= KHEAP_START_ADDR); assert(current_data->addr < KHEAP_END_ADDR); return (void*)current_data->addr; } } } else { current_data->is_buzy = true; size_t surplus = current_data->size - size; bool is_sibling_bizy = false; if (current->next != null) { struct slist_head_t* sibling = current->next; struct kheap_entry_t* sibling_data = (struct kheap_entry_t*)sibling->data; is_sibling_bizy = sibling_data->is_buzy; if (!is_sibling_bizy) { current_data->size -= surplus; sibling_data->addr -= surplus; sibling_data->size += surplus; assert(current_data->size == size); } } if (current->next == null || is_sibling_bizy) { { struct slist_head_t* new_sibling; new_sibling = slist_insert_entry_after(&kheap_list, current); struct kheap_entry_t* sibling_data = (struct kheap_entry_t*)new_sibling->data; if (new_sibling != null) { assert((size_t)new_sibling == (size_t)sibling_data); current_data->size -= surplus; assert(current_data->size > 0); sibling_data->is_buzy = false; sibling_data->addr = current_data->addr + current_data->size; sibling_data->size = surplus; } } } assert(current_data->addr >= KHEAP_START_ADDR); assert(current_data->addr < KHEAP_END_ADDR); return (void*)current_data->addr; } } size_t heap_end_addr = KHEAP_START_ADDR; if (kheap_list.tail) { current = kheap_list.tail; current_data = (struct kheap_entry_t*)current->data; heap_end_addr = current_data->addr + current_data->size; } if (heap_end_addr + size >= KHEAP_END_ADDR) { abort("heap size exceeded\n"); } struct slist_head_t* tail = kheap_list.tail; current = slist_insert_entry_after(&kheap_list, kheap_list.tail); current_data = (struct kheap_entry_t*)current->data; assert((size_t)current == (size_t)current_data); current_data->addr = heap_end_addr; current_data->size = size; current_data->is_buzy = true; assert(current->next == null); assert(current->prev == tail); assert(current_data->addr >= KHEAP_START_ADDR); assert(current_data->addr < KHEAP_END_ADDR); return (void*)current_data->addr; }
Der speicherfreie Algorithmus ist ähnlich (kfree):
Suchen Sie einen Block, dessen Adresse mit der freizugebenden Adresse beginnt. Überprüfen Sie, ob er beschäftigt ist. Als frei markieren.
Wenn es rechts oder links einen freien Nachbarn gibt, der sich in einem Block mit ihm vereinigt.
extern void kfree(void* addr) { struct slist_head_t* current = null; struct kheap_entry_t* current_data = null; struct slist_head_t* head = kheap_list.head; for (current = head; current != null; current = current->next) { current_data = (struct kheap_entry_t*)current->data; if (current_data->addr == (size_t)addr && current_data->is_buzy) { struct slist_head_t* prev = current->prev; struct slist_head_t* next = current->next; struct kheap_entry_t* prev_data = prev != null ? (struct kheap_entry_t*)prev->data : null; struct kheap_entry_t* next_data = next != null ? (struct kheap_entry_t*)next->data : null; current_data->is_buzy = false; if (prev != null && !prev_data->is_buzy) { prev_data->size += current_data->size; slist_delete_entry(&kheap_list, (struct slist_head_t*)current); } if (next != null && !next_data->is_buzy) { current_data->size += next_data->size; slist_delete_entry(&kheap_list, (struct slist_head_t*)next); } return; } } abort("invalid address to free\n", addr); }
Das ist alles. Bis zum nächsten Mal!
Referenzen
Öffnen Sie nun das
Video-TutorialUnd schauen Sie sich das
Git-Repository parallel an
(Sie benötigen einen Lektion4-Zweig).Referenzliste
1. James Molloy. Rollen Sie Ihr eigenes UNIX-Klon-Betriebssystem.
2. Zähne. Assembler für DOS, Windows, Unix
3. Kalaschnikow. Assembler ist einfach!
4. Tanenbaum. Betriebssysteme. Implementierung und Entwicklung.
5. Robert Love. Linux-Kernel Beschreibung des Entwicklungsprozesses.