在
上一篇文章中,我们实现了内核系统日志。 现在是时候实现动态内存管理器了。
目录
- 构建系统(make,gcc,gas)。 初始引导(多次引导)。 启动(qemu)。 C库(strcpy,memcpy,strext)。
- C库(sprintf,strcpy,strcmp,strtok,va_list ...)。 以内核模式和用户应用程序模式构建库。
- 内核系统日志。 显存 输出到终端(kprintf,kpanic,kassert)。
- 动态内存,堆(kmalloc,kfree)。
- 内存和中断处理的组织(GDT,IDT,PIC,syscall)。 例外情况
- 虚拟内存(页面目录和页面表)。
- 过程。 策划人 多任务处理。 系统调用(kill,exit,ps)。
- 内核(initrd),elf及其内部文件系统。 系统调用(执行)。
- 字符设备驱动程序。 系统调用(ioctl,fopen,fread,fwrite)。 C库(fopen,fclose,fprintf,fscanf)。
- Shell作为内核的完整程序。
- 用户保护模式(ring3)。 任务状态段(tss)。
动态内核内存。 一堆
内核堆接口将如下所示:
void *kmalloc(size_t size); void kfree(void *addr);
由于还没有堆,因此必须存储已占用和可用内存区域的记录。
以有限的大小排列。 尺寸越小,堆容量越低
小碎片。 例如,在Linux上,许多内核结构存储在可重用的环境中
释放时不会改变大小的内存(平板)。 但是我们还差得远
因为发展是一个反复的过程。
任何打算成为通过数组组织的列表的元素的元素
需要实现一个接口。
struct slist_head_t { struct slist_head_t *prev; struct slist_head_t *next; bool is_valid; void *data; } attribute(packed);
C中没有接口,但是不需要它们。 如果此对象实际上看起来像这样,我们只需将任何对象的指针转换为struct slist_head_t *类型:
struct my_list_entry_t { struct slist_head_t list_head; ... }
但是,这样广义算法知道列表将存储在其中的数组的维数
以及其元素的大小,列表的第一个和最后一个元素以及其中的数组的地址
存储一个列表,操作列表的每个功能将需要另一个指针
到列表定义结构:
struct slist_definition_t { size_t base; u_int slots; size_t slot_size; struct slist_head_t *head; struct slist_head_t *tail; };
用于操作列表的功能如下所示:
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);
插入函数算法:
在阵列中找到一个空闲插槽。
用新元素填充它。
更正逻辑上(而非物理上)相邻列表项的指针。
将插槽标记为忙。
调整列表的开头和结尾。
从列表中删除的函数的算法:
更正逻辑上(而非物理上)相邻列表项的指针。
调整列表的开头和结尾。
将插槽标记为空闲。
动态内存由具有以下各项的列表描述:
struct kheap_entry_t { struct slist_head_t list_head; size_t addr; size_t size; bool is_buzy; } attribute(packed);
从这些记录描述动态内存的数组如下所示:
struct kheap_entry_t kheap_blocks[KHEAP_MAX_ENTRIES];
定义内存区域列表如下所示:
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};
因此,我们已经描述了动态内存。 所有内存在一定范围内
分为任意大小的块,每个块可以为空,
要么忙。 此外,每个块都由一个带有is_valid = true标志的数组元素描述,
这是一个列表项。
现在考虑最简单的堆分配器算法(kmalloc):
如果没有可用块,请选择所需大小(但不超过最大堆大小)的新块。
否则,我们将浏览所有空闲块。 如果找到空闲块,请查看其大小。 如果足够,请接受。 如果有剩余,则创建一个新的剩余大小的空白块。 否则,如果不足,则扩大堆边界(但不超过最大值)。
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; }
无内存算法将类似(kfree):
查找一个地址以要释放的地址开头的块。 检查他是否忙。 标记为免费。
如果右边或左边有一个自由的邻居,与他团结在一起。
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); }
仅此而已。 直到下一次!
参考文献
现在,打开
视频教程并并行观看
git存储库(您需要一个lesson4分支)参考文献
1.詹姆斯·莫洛伊(James Molloy)。 滚动自己的玩具UNIX克隆操作系统。
2.牙齿。 DOS,Windows,Unix的汇编器
3.卡拉什尼科夫。 汇编程序很简单!
4. Tanenbaum。 操作系统。 实施与开发。
5.罗伯特·洛夫(Robert Love)。 Linux内核 开发过程的描述。