feat(mm): add minimal kernel heap

This commit is contained in:
Microindole 2026-05-09 11:23:46 +08:00
parent 7df720c57b
commit a2e6889932
No known key found for this signature in database
GPG Key ID: 22FB34CD2133ACA1
9 changed files with 262 additions and 10 deletions

View File

@ -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。

View File

@ -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 级能力缺口路由

View File

@ -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 和调度循环。
## 任务路由

View File

@ -1,6 +1,7 @@
#ifndef TIANOLE_MM_H
#define TIANOLE_MM_H
#include <stddef.h>
#include <stdint.h>
#include <tianole/boot_info.h>
@ -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

View File

@ -1,4 +1,5 @@
MM_SRCS := \
mm/heap.c \
mm/page_alloc.c
MM_OBJS := \

235
mm/heap.c Normal file
View File

@ -0,0 +1,235 @@
#include <stddef.h>
#include <stdint.h>
#include <tianole/early_log.h>
#include <tianole/mm.h>
#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();
}

View File

@ -178,4 +178,5 @@ void mm_init(const boot_info_t *boot_info)
page_allocator_selftest();
page_table_selftest();
heap_init();
}

View File

@ -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

View File

@ -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" \