From a2e68899321a3943ec203e44cde4d729566196b3 Mon Sep 17 00:00:00 2001 From: Microindole Date: Sat, 9 May 2026 11:23:46 +0800 Subject: [PATCH] feat(mm): add minimal kernel heap --- docs/agents/tasks/03-memory.md | 10 +- docs/agents/tasks/README.md | 4 +- docs/roadmap.md | 8 +- include/tianole/mm.h | 4 + mm/Makefile | 1 + mm/heap.c | 235 +++++++++++++++++++++++++++++++++ mm/page_alloc.c | 1 + scripts/checks/boot.sh | 8 +- scripts/checks/page-fault.sh | 1 + 9 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 mm/heap.c diff --git a/docs/agents/tasks/03-memory.md b/docs/agents/tasks/03-memory.md index cd8d640..e5dda59 100644 --- a/docs/agents/tasks/03-memory.md +++ b/docs/agents/tasks/03-memory.md @@ -56,14 +56,15 @@ - 已提供最小 `map_page()`、`unmap_page()` 和 `virt_to_phys()` 接口。 - 已加入页表 map/unmap/query selftest。 - 已拆出 x86 page fault 诊断路径,能输出 fault address、错误码、访问类型和权限来源。 -- `scripts/check.sh` 已验证 `physical pages free=`、物理页 selftest、页表根切换、页表 selftest 和 page fault 日志。 +- 已建立最小内核堆,提供 `kmalloc()` 和 `kfree()`,底层通过页表按需映射物理页。 +- 已加入内核堆分配、写入、释放、复用 selftest。 +- `scripts/check.sh` 已验证 `physical pages free=`、物理页 selftest、页表根切换、页表 selftest、内核堆 selftest 和 page fault 日志。 后续扩展: - 长期内存区域模型,不直接依赖 UEFI memory type。 - page metadata。 - buddy allocator。 -- 最小内核堆。 - slab/slub 或等价小对象缓存。 验收依据: @@ -72,8 +73,11 @@ - 正常启动日志包含 `physical page allocator selftest ok`。 - 正常启动日志包含 `kernel page table root active`。 - 正常启动日志包含 `page table selftest ok`。 +- 正常启动日志包含 `kernel heap initialized`。 +- 正常启动日志包含 `kernel heap selftest ok`。 - page fault 测试日志包含 `page fault: address=` 和 `access=write mode=kernel reason=not-present`。 下一阶段: -- 继续在 `03-memory.md` 内推进最小内核堆。 +- `03-memory.md` 的最小基础已经闭环。 +- 下一阶段可以进入 timer/scheduler;如果继续打磨内存,则应补 page metadata、buddy allocator 和 slab/slub。 diff --git a/docs/agents/tasks/README.md b/docs/agents/tasks/README.md index c544d9f..a89a836 100644 --- a/docs/agents/tasks/README.md +++ b/docs/agents/tasks/README.md @@ -27,7 +27,7 @@ | --- | --- | --- | | `01-early-debug.md` | 基础完成 | 已有 early log 前端、QEMU debug port、COM1 串口和最小 `panic()`。 | | `02-cpu-interrupts.md` | 基础完成 | 已有 GDT/TSS/IDT、exception vector 0-31、`trap_frame` 和 invalid opcode 回归测试。 | -| `03-memory.md` | 进行中 | 已有最小物理页 allocator、页表 map/unmap/query 和 page fault 诊断基础;内核堆未完成。 | +| `03-memory.md` | 基础完成 | 已有最小物理页 allocator、页表 map/unmap/query、page fault 诊断和内核堆。 | | `04-time-scheduler.md` | 未开始 | 需要 timer、kernel thread、调度和等待队列。 | | `05-input-events.md` | 未开始 | 需要输入事件模型和键盘接入。 | | `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 | @@ -36,7 +36,7 @@ | `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 | | `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 | -当前最合适的下一步仍在 `03-memory.md` 内:继续做最小内核堆。 +当前最合适的下一步是 `04-time-scheduler.md`:先建立 timer interrupt,再引入最小 kernel thread 和调度循环。 ## Linux 级能力缺口路由 diff --git a/docs/roadmap.md b/docs/roadmap.md index ec363e4..77b4d4c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -22,6 +22,7 @@ - 页表基础:已切换到内核自有 PML4,并提供最小 `map_page/unmap_page/virt_to_phys`。 - 页表验证:页表 map/unmap/query selftest 已接入启动检查。 - page fault 基础:已能输出 fault address、错误码、访问类型和权限来源。 +- 内核堆基础:已提供 `kmalloc/kfree`,并接入启动 selftest。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 - `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。 - `.clang-format` 已用于强制当前 C 代码风格。 @@ -30,18 +31,19 @@ - 完整日志体系:printk、log level、ring buffer、oops、符号化、crash dump。 - 完整中断体系:PIC/APIC、外部 IRQ、timer interrupt。 -- 内存管理扩展:长期内存区域模型、内核堆、后续完整缺页策略。 +- 内存管理扩展:长期内存区域模型、page metadata、buddy/slab、后续完整缺页策略。 - 后续主线:调度、进程、文件系统、用户态和 shell。 ## 当前下一步 下一步执行: -- `docs/agents/tasks/03-memory.md` +- `docs/agents/tasks/04-time-scheduler.md` 目标: -- 建立最小内核堆。 +- 建立 timer interrupt 基础。 +- 建立最小 kernel thread 和调度循环。 ## 任务路由 diff --git a/include/tianole/mm.h b/include/tianole/mm.h index cd2bc28..d649e54 100644 --- a/include/tianole/mm.h +++ b/include/tianole/mm.h @@ -1,6 +1,7 @@ #ifndef TIANOLE_MM_H #define TIANOLE_MM_H +#include #include #include @@ -17,9 +18,12 @@ typedef uint64_t virt_addr_t; void mm_init(const boot_info_t *boot_info); phys_addr_t alloc_page(void); void free_page(phys_addr_t page); +void *kmalloc(size_t size); +void kfree(void *ptr); int map_page(virt_addr_t virt, phys_addr_t phys, uint64_t flags); int unmap_page(virt_addr_t virt); int virt_to_phys(virt_addr_t virt, phys_addr_t *phys); +void heap_init(void); void page_table_selftest(void); #endif diff --git a/mm/Makefile b/mm/Makefile index 35a68a2..0961dc2 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -1,4 +1,5 @@ MM_SRCS := \ + mm/heap.c \ mm/page_alloc.c MM_OBJS := \ diff --git a/mm/heap.c b/mm/heap.c new file mode 100644 index 0000000..26c724d --- /dev/null +++ b/mm/heap.c @@ -0,0 +1,235 @@ +#include +#include + +#include +#include + +#define HEAP_BASE 0xffffff2000000000ull +#define HEAP_ALIGNMENT 16u + +struct heap_block { + size_t size; + uint8_t free; + struct heap_block *prev; + struct heap_block *next; +}; + +static struct heap_block *heap_first; +static struct heap_block *heap_last; +static virt_addr_t heap_end = HEAP_BASE; +static int heap_ready; + +static size_t align_up_size(size_t value, size_t alignment) +{ + return (value + alignment - 1) & ~(alignment - 1); +} + +static virt_addr_t align_up_addr(virt_addr_t value, uint64_t alignment) +{ + return (value + alignment - 1) & ~(alignment - 1); +} + +static void block_insert_after( + struct heap_block *block, struct heap_block *next) +{ + next->prev = block; + next->next = block->next; + + if (block->next != 0) { + block->next->prev = next; + } else { + heap_last = next; + } + + block->next = next; +} + +static void block_merge_next(struct heap_block *block) +{ + struct heap_block *next = block->next; + + if (next == 0 || next->free == 0) { + return; + } + + block->size += sizeof(*next) + next->size; + block->next = next->next; + + if (next->next != 0) { + next->next->prev = block; + } else { + heap_last = block; + } +} + +static void block_split(struct heap_block *block, size_t size) +{ + struct heap_block *next; + + if (block->size < size + sizeof(*next) + HEAP_ALIGNMENT) { + return; + } + + next = (struct heap_block *)((uint8_t *)(block + 1) + size); + next->size = block->size - size - sizeof(*next); + next->free = 1; + + block->size = size; + block_insert_after(block, next); +} + +static int map_heap_range(virt_addr_t start, size_t bytes) +{ + virt_addr_t current; + virt_addr_t end = start + bytes; + + for (current = start; current < end; current += PAGE_SIZE) { + phys_addr_t page = alloc_page(); + + if (page == 0 || + map_page(current, + page, + PAGE_WRITABLE | PAGE_NO_EXECUTE) != 0) { + return -1; + } + } + + return 0; +} + +static int heap_extend(size_t min_size) +{ + size_t bytes = + align_up_size(min_size + sizeof(struct heap_block), PAGE_SIZE); + struct heap_block *block = (struct heap_block *)(uintptr_t)heap_end; + + if (map_heap_range(heap_end, bytes) != 0) { + return -1; + } + + heap_end += bytes; + + block->size = bytes - sizeof(*block); + block->free = 1; + block->prev = heap_last; + block->next = 0; + + if (heap_last != 0) { + heap_last->next = block; + } else { + heap_first = block; + } + + heap_last = block; + + if (block->prev != 0 && block->prev->free != 0) { + block_merge_next(block->prev); + } + + return 0; +} + +static struct heap_block *find_free_block(size_t size) +{ + struct heap_block *block; + + for (block = heap_first; block != 0; block = block->next) { + if (block->free != 0 && block->size >= size) { + return block; + } + } + + return 0; +} + +void *kmalloc(size_t size) +{ + struct heap_block *block; + + if (size == 0) { + return 0; + } + + size = align_up_size(size, HEAP_ALIGNMENT); + block = find_free_block(size); + + if (block == 0) { + if (heap_extend(size) != 0) { + return 0; + } + block = find_free_block(size); + } + + if (block == 0) { + return 0; + } + + block_split(block, size); + block->free = 0; + + return block + 1; +} + +void kfree(void *ptr) +{ + struct heap_block *block; + + if (ptr == 0) { + return; + } + + block = (struct heap_block *)ptr - 1; + block->free = 1; + + block_merge_next(block); + if (block->prev != 0 && block->prev->free != 0) { + block_merge_next(block->prev); + } +} + +static void heap_selftest(void) +{ + uint64_t *first = kmalloc(sizeof(*first)); + uint8_t *second = kmalloc(128); + uint64_t *third; + + if (first == 0 || second == 0) { + panic("kernel heap selftest allocation failed"); + } + + *first = 0x54494f4c45484541ull; + second[0] = 0x31; + second[127] = 0x32; + + if (*first != 0x54494f4c45484541ull || second[0] != 0x31 || + second[127] != 0x32) { + panic("kernel heap selftest memory corrupted"); + } + + kfree(first); + third = kmalloc(sizeof(*third)); + if (third == 0) { + panic("kernel heap selftest reuse failed"); + } + + kfree(third); + kfree(second); + + early_log_puts("kernel heap selftest ok\n"); +} + +void heap_init(void) +{ + if (heap_ready != 0) { + return; + } + + heap_end = align_up_addr(heap_end, PAGE_SIZE); + if (heap_extend(PAGE_SIZE - sizeof(struct heap_block)) != 0) { + panic("kernel heap initialization failed"); + } + + heap_ready = 1; + early_log_puts("kernel heap initialized\n"); + heap_selftest(); +} diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 7d2ac81..cf60b7e 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -178,4 +178,5 @@ void mm_init(const boot_info_t *boot_info) page_allocator_selftest(); page_table_selftest(); + heap_init(); } diff --git a/scripts/checks/boot.sh b/scripts/checks/boot.sh index cca69ad..f520245 100755 --- a/scripts/checks/boot.sh +++ b/scripts/checks/boot.sh @@ -20,7 +20,9 @@ check_lines build/debug.log \ "physical pages free=" \ "physical page allocator selftest ok" \ "kernel page table root active" \ - "page table selftest ok" + "page table selftest ok" \ + "kernel heap initialized" \ + "kernel heap selftest ok" check_lines build/serial.log \ "kernel_main entered" \ @@ -32,6 +34,8 @@ check_lines build/serial.log \ "physical pages free=" \ "physical page allocator selftest ok" \ "kernel page table root active" \ - "page table selftest ok" + "page table selftest ok" \ + "kernel heap initialized" \ + "kernel heap selftest ok" cat build/debug.log diff --git a/scripts/checks/page-fault.sh b/scripts/checks/page-fault.sh index b1a3190..13c7064 100755 --- a/scripts/checks/page-fault.sh +++ b/scripts/checks/page-fault.sh @@ -12,6 +12,7 @@ check_lines build/debug.log \ "traps initialized" \ "kernel page table root active" \ "page table selftest ok" \ + "kernel heap selftest ok" \ "exception: page fault" \ "vector=14 error=0x0000000000000002" \ "page fault: address=0xffffff1000000000 error=0x0000000000000002" \