feat(mm): add early physical page allocator

This commit is contained in:
Microindole 2026-05-08 23:20:16 +08:00
parent 44bbea1b11
commit 5b69fe1f06
No known key found for this signature in database
GPG Key ID: 22FB34CD2133ACA1
31 changed files with 968 additions and 77 deletions

View File

@ -21,12 +21,15 @@
## 当前约定
- 详细代码风格和文件组织规则见 `docs/agents/code-style.md`
- 当前代码以“先做最小,但不把架构完全写死”为原则推进。
- `arch/` 放架构相关实现。
- `mm/` 放架构无关内存管理实现,和 Linux 一样作为顶层核心子系统维护。
- `include/tianole/` 放尽量与具体架构无关的共享接口。
- 入口文件只负责串联启动流程不承载文件加载、ELF 解析、内存映射统计等功能细节。
- 检查式日志应该放在对应功能模块中,避免把 `kernel_main()` 变成临时测试脚本。
- 内部 C API 不加 `tianole_` 前缀;这是单一内核代码库,不把项目名重复进函数名和类型名。
- 架构目录内部不重复架构名前缀;跨通用层暴露的架构入口使用 `arch_` 前缀。
- 避免 `temp` / `tmp` 这类临时语义进入函数名、类型名和长期变量名;一次性局部变量可以用具体含义命名。
- C 代码风格参考 Linuxtabs 缩进,函数左花括号另起一行,`if/for/while` 左花括号留在行尾。
- 公共头文件使用 `<...>` include例如 `<tianole/boot_info.h>`;同目录私有头文件使用 `"..."` include例如 `"file.h"`
@ -37,4 +40,5 @@
- 文本文件使用 LF 行尾。
- `.clang-format` 是当前格式化约定;如果环境有 `clang-format`,新增或大改 C/H 文件后应按它格式化。
- 根 `Makefile` 只做总控和通用规则;架构配置放在 `arch/<arch>/Makefile`,目录自己的源文件列表放在对应目录的 `Makefile` 中。
- 本地维护入口是 `scripts/check.sh`GitHub Actions 应调用同一个脚本,避免 CI 逻辑和本地逻辑分叉。
- 本地维护入口是 `scripts/check.sh`;具体检查放在 `scripts/checks/`,公共脚本函数放在 `scripts/lib/`
- GitHub Actions 应调用 `scripts/check.sh`,避免 CI 逻辑和本地逻辑分叉。

View File

@ -10,6 +10,7 @@ BUILD_DIR := build
KERNEL_ELF := $(BUILD_DIR)/image/kernel.elf
DEBUG_LOG := $(BUILD_DIR)/debug.log
SERIAL_LOG := $(BUILD_DIR)/serial.log
KERNEL_TEST_TRAP ?= 0
ifeq ($(ARCH),x86_64)
ARCH_MAKEFILE := arch/x86/Makefile
@ -20,6 +21,7 @@ endif
include $(ARCH_MAKEFILE)
include $(ARCH_DIR)/boot/Makefile
include $(ARCH_DIR)/kernel/Makefile
include mm/Makefile
include kernel/Makefile
.PHONY: all clean dirs run run-headless
@ -27,7 +29,7 @@ include kernel/Makefile
all: $(BOOT_EFI) $(KERNEL_ELF)
dirs:
mkdir -p $(BUILD_DIR)/arch/boot $(BUILD_DIR)/arch/kernel $(BUILD_DIR)/kernel $(EFI_DIR)
mkdir -p $(BUILD_DIR)/arch/boot $(BUILD_DIR)/arch/kernel $(BUILD_DIR)/kernel $(BUILD_DIR)/mm $(EFI_DIR)
$(BUILD_DIR)/arch/boot/%.obj: $(ARCH_DIR)/boot/%.c $(ARCH_DIR)/include/efi.h $(ARCH_DIR)/boot/debug_log.h include/tianole/boot_info.h include/tianole/elf.h | dirs
$(CC) $(CFLAGS) -c $< -o $@
@ -38,10 +40,18 @@ $(BOOT_EFI): $(BOOT_OBJS) | dirs
$(BUILD_DIR)/arch/kernel/entry.o: $(ARCH_DIR)/kernel/entry.S | dirs
$(CC) $(KERNEL_ASFLAGS) -c $< -o $@
$(BUILD_DIR)/arch/kernel/%.o: $(ARCH_DIR)/kernel/%.S | dirs
$(CC) $(KERNEL_ASFLAGS) -c $< -o $@
$(BUILD_DIR)/arch/kernel/%.o: $(ARCH_DIR)/kernel/%.c include/tianole/arch.h | dirs
$(CC) $(KERNEL_CFLAGS) -c $< -o $@
$(BUILD_DIR)/kernel/%.o: kernel/%.c include/tianole/boot_info.h include/tianole/kernel_init.h include/tianole/arch.h include/tianole/early_log.h | dirs
$(BUILD_DIR)/kernel/%.o: kernel/%.c include/tianole/boot_info.h include/tianole/kernel_init.h include/tianole/arch.h include/tianole/early_log.h include/tianole/mm.h | dirs
mkdir -p $(@D)
$(CC) $(KERNEL_CFLAGS) -c $< -o $@
$(BUILD_DIR)/mm/%.o: mm/%.c include/tianole/boot_info.h include/tianole/early_log.h include/tianole/mm.h | dirs
mkdir -p $(@D)
$(CC) $(KERNEL_CFLAGS) -c $< -o $@
$(KERNEL_ELF): $(KERNEL_OBJS) $(ARCH_DIR)/kernel/linker.ld | dirs

View File

@ -21,6 +21,7 @@ tianole/
docs/
include/
kernel/
mm/
scripts/
```
@ -28,6 +29,7 @@ tianole/
- `arch/`:架构相关代码
- `kernel/`:通用内核主体逐步放这里
- `mm/`:架构无关内存管理
- `include/tianole/`:项目自有共享接口
- `docs/`:正式设计和路线图
@ -71,7 +73,7 @@ cat build/debug.log
scripts/check.sh
```
GitHub Actions 会在 push 和 pull request 时运行同一个检查脚本
GitHub Actions 会在 push 和 pull request 时运行同一个检查入口。具体检查拆在 `scripts/checks/`,共享函数放在 `scripts/lib/`
## 文档

View File

@ -29,6 +29,7 @@ KERNEL_CFLAGS := \
-fno-builtin \
-fno-pic \
-mno-red-zone \
-DKERNEL_TEST_TRAP=$(KERNEL_TEST_TRAP) \
-Wall \
-Wextra \
-Werror \

View File

@ -1,6 +1,10 @@
ARCH_KERNEL_SRCS := \
$(ARCH_DIR)/kernel/early_log.c
$(ARCH_DIR)/kernel/early_log.c \
$(ARCH_DIR)/kernel/gdt.c \
$(ARCH_DIR)/kernel/idt.c \
$(ARCH_DIR)/kernel/traps.c
ARCH_KERNEL_OBJS := \
$(BUILD_DIR)/arch/kernel/entry.o \
$(BUILD_DIR)/arch/kernel/exception_entry.o \
$(patsubst $(ARCH_DIR)/kernel/%.c,$(BUILD_DIR)/arch/kernel/%.o,$(ARCH_KERNEL_SRCS))

71
arch/x86/kernel/cpu.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef ARCH_X86_KERNEL_CPU_H
#define ARCH_X86_KERNEL_CPU_H
#include <stdint.h>
#define KERNEL_CODE_SELECTOR 0x08
#define KERNEL_DATA_SELECTOR 0x10
#define TSS_SELECTOR 0x18
struct trap_frame {
uint64_t rax;
uint64_t rbx;
uint64_t rcx;
uint64_t rdx;
uint64_t rsi;
uint64_t rdi;
uint64_t rbp;
uint64_t r8;
uint64_t r9;
uint64_t r10;
uint64_t r11;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
uint64_t vector;
uint64_t error_code;
uint64_t rip;
uint64_t cs;
uint64_t rflags;
};
void gdt_init(void);
void idt_init(void);
void idt_set_gate(uint8_t vector, void (*handler)(void));
void trap_dispatch(struct trap_frame *frame);
void exception_0(void);
void exception_1(void);
void exception_2(void);
void exception_3(void);
void exception_4(void);
void exception_5(void);
void exception_6(void);
void exception_7(void);
void exception_8(void);
void exception_9(void);
void exception_10(void);
void exception_11(void);
void exception_12(void);
void exception_13(void);
void exception_14(void);
void exception_15(void);
void exception_16(void);
void exception_17(void);
void exception_18(void);
void exception_19(void);
void exception_20(void);
void exception_21(void);
void exception_22(void);
void exception_23(void);
void exception_24(void);
void exception_25(void);
void exception_26(void);
void exception_27(void);
void exception_28(void);
void exception_29(void);
void exception_30(void);
void exception_31(void);
#endif

View File

@ -1,4 +1,5 @@
.global _start
.global kernel_stack_top
.type _start, @function
.section .text

View File

@ -0,0 +1,77 @@
.macro EXCEPTION_NO_ERROR vector
.global exception_\vector
.type exception_\vector, @function
exception_\vector:
pushq $0
pushq $\vector
jmp exception_common
.endm
.macro EXCEPTION_ERROR vector
.global exception_\vector
.type exception_\vector, @function
exception_\vector:
pushq $\vector
jmp exception_common
.endm
.section .text
EXCEPTION_NO_ERROR 0
EXCEPTION_NO_ERROR 1
EXCEPTION_NO_ERROR 2
EXCEPTION_NO_ERROR 3
EXCEPTION_NO_ERROR 4
EXCEPTION_NO_ERROR 5
EXCEPTION_NO_ERROR 6
EXCEPTION_NO_ERROR 7
EXCEPTION_ERROR 8
EXCEPTION_NO_ERROR 9
EXCEPTION_ERROR 10
EXCEPTION_ERROR 11
EXCEPTION_ERROR 12
EXCEPTION_ERROR 13
EXCEPTION_ERROR 14
EXCEPTION_NO_ERROR 15
EXCEPTION_NO_ERROR 16
EXCEPTION_ERROR 17
EXCEPTION_NO_ERROR 18
EXCEPTION_NO_ERROR 19
EXCEPTION_NO_ERROR 20
EXCEPTION_ERROR 21
EXCEPTION_NO_ERROR 22
EXCEPTION_NO_ERROR 23
EXCEPTION_NO_ERROR 24
EXCEPTION_NO_ERROR 25
EXCEPTION_NO_ERROR 26
EXCEPTION_NO_ERROR 27
EXCEPTION_NO_ERROR 28
EXCEPTION_ERROR 29
EXCEPTION_ERROR 30
EXCEPTION_NO_ERROR 31
.type exception_common, @function
exception_common:
cld
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %r11
pushq %r10
pushq %r9
pushq %r8
pushq %rbp
pushq %rdi
pushq %rsi
pushq %rdx
pushq %rcx
pushq %rbx
pushq %rax
movq %rsp, %rdi
call trap_dispatch
addq $120, %rsp
addq $16, %rsp
iretq

134
arch/x86/kernel/gdt.c Normal file
View File

@ -0,0 +1,134 @@
#include <stdint.h>
#include "cpu.h"
#define GDT_PRESENT 0x80
#define GDT_RING0 0x00
#define GDT_CODE 0x1a
#define GDT_DATA 0x12
#define GDT_TSS_AVAILABLE 0x09
#define GDT_LONG_MODE 0x20
#define GDT_GRANULARITY_4K 0x80
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t limit_flags;
uint8_t base_high;
} __attribute__((packed));
struct tss_entry {
uint32_t reserved0;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint64_t reserved1;
uint64_t ist[7];
uint64_t reserved2;
uint16_t reserved3;
uint16_t io_map_base;
} __attribute__((packed));
struct tss_descriptor {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t limit_flags;
uint8_t base_high;
uint32_t base_upper;
uint32_t reserved;
} __attribute__((packed));
struct gdtr {
uint16_t limit;
uint64_t base;
} __attribute__((packed));
struct gdt_table {
struct gdt_entry null;
struct gdt_entry kernel_code;
struct gdt_entry kernel_data;
struct tss_descriptor tss;
} __attribute__((packed));
extern char kernel_stack_top[];
static struct tss_entry tss;
static struct gdt_table gdt;
static struct gdt_entry make_gdt_entry(uint8_t access, uint8_t flags)
{
struct gdt_entry entry = {
.limit_low = 0xffff,
.base_low = 0,
.base_mid = 0,
.access = (uint8_t)(GDT_PRESENT | GDT_RING0 | access),
.limit_flags = (uint8_t)(0x0f | flags),
.base_high = 0,
};
return entry;
}
static struct tss_descriptor make_tss_descriptor(uint64_t base, uint32_t limit)
{
struct tss_descriptor descriptor = {
.limit_low = (uint16_t)(limit & 0xffff),
.base_low = (uint16_t)(base & 0xffff),
.base_mid = (uint8_t)((base >> 16) & 0xff),
.access = GDT_PRESENT | GDT_TSS_AVAILABLE,
.limit_flags = (uint8_t)((limit >> 16) & 0x0f),
.base_high = (uint8_t)((base >> 24) & 0xff),
.base_upper = (uint32_t)(base >> 32),
.reserved = 0,
};
return descriptor;
}
static void load_gdt(const struct gdtr *gdtr)
{
__asm__ volatile("lgdt (%0)" : : "r"(gdtr) : "memory");
__asm__ volatile("pushq %[code]\n"
"leaq 1f(%%rip), %%rax\n"
"pushq %%rax\n"
"lretq\n"
"1:\n"
"movw %[data], %%ax\n"
"movw %%ax, %%ds\n"
"movw %%ax, %%es\n"
"movw %%ax, %%ss\n"
:
: [code] "i"(KERNEL_CODE_SELECTOR),
[data] "i"(KERNEL_DATA_SELECTOR)
: "rax", "memory");
}
static void load_tss(void)
{
__asm__ volatile("ltr %0" : : "r"((uint16_t)TSS_SELECTOR) : "memory");
}
void gdt_init(void)
{
struct gdtr gdtr = {
.limit = sizeof(gdt) - 1,
.base = (uint64_t)(uintptr_t)&gdt,
};
tss.rsp0 = (uint64_t)(uintptr_t)kernel_stack_top;
tss.io_map_base = sizeof(tss);
gdt.null = (struct gdt_entry){0};
gdt.kernel_code =
make_gdt_entry(GDT_CODE, GDT_LONG_MODE | GDT_GRANULARITY_4K);
gdt.kernel_data = make_gdt_entry(GDT_DATA, GDT_GRANULARITY_4K);
gdt.tss =
make_tss_descriptor((uint64_t)(uintptr_t)&tss, sizeof(tss) - 1);
load_gdt(&gdtr);
load_tss();
}

85
arch/x86/kernel/idt.c Normal file
View File

@ -0,0 +1,85 @@
#include <stdint.h>
#include "cpu.h"
#define IDT_PRESENT 0x80
#define IDT_INTERRUPT_GATE 0x0e
struct idt_entry {
uint16_t offset_low;
uint16_t selector;
uint8_t ist;
uint8_t type_attr;
uint16_t offset_mid;
uint32_t offset_high;
uint32_t reserved;
} __attribute__((packed));
struct idtr {
uint16_t limit;
uint64_t base;
} __attribute__((packed));
static struct idt_entry idt[256];
static void load_idt(const struct idtr *idtr)
{
__asm__ volatile("lidt (%0)" : : "r"(idtr) : "memory");
}
void idt_set_gate(uint8_t vector, void (*handler)(void))
{
uint64_t offset = (uint64_t)(uintptr_t)handler;
struct idt_entry *entry = &idt[vector];
entry->offset_low = (uint16_t)(offset & 0xffff);
entry->selector = KERNEL_CODE_SELECTOR;
entry->ist = 0;
entry->type_attr = IDT_PRESENT | IDT_INTERRUPT_GATE;
entry->offset_mid = (uint16_t)((offset >> 16) & 0xffff);
entry->offset_high = (uint32_t)(offset >> 32);
entry->reserved = 0;
}
void idt_init(void)
{
struct idtr idtr = {
.limit = sizeof(idt) - 1,
.base = (uint64_t)(uintptr_t)idt,
};
idt_set_gate(0, exception_0);
idt_set_gate(1, exception_1);
idt_set_gate(2, exception_2);
idt_set_gate(3, exception_3);
idt_set_gate(4, exception_4);
idt_set_gate(5, exception_5);
idt_set_gate(6, exception_6);
idt_set_gate(7, exception_7);
idt_set_gate(8, exception_8);
idt_set_gate(9, exception_9);
idt_set_gate(10, exception_10);
idt_set_gate(11, exception_11);
idt_set_gate(12, exception_12);
idt_set_gate(13, exception_13);
idt_set_gate(14, exception_14);
idt_set_gate(15, exception_15);
idt_set_gate(16, exception_16);
idt_set_gate(17, exception_17);
idt_set_gate(18, exception_18);
idt_set_gate(19, exception_19);
idt_set_gate(20, exception_20);
idt_set_gate(21, exception_21);
idt_set_gate(22, exception_22);
idt_set_gate(23, exception_23);
idt_set_gate(24, exception_24);
idt_set_gate(25, exception_25);
idt_set_gate(26, exception_26);
idt_set_gate(27, exception_27);
idt_set_gate(28, exception_28);
idt_set_gate(29, exception_29);
idt_set_gate(30, exception_30);
idt_set_gate(31, exception_31);
load_idt(&idtr);
}

View File

@ -3,6 +3,7 @@ ENTRY(_start)
SECTIONS
{
. = 0x200000;
__kernel_start = .;
.text : ALIGN(4K) {
*(.text .text.*)
@ -20,4 +21,6 @@ SECTIONS
*(COMMON)
*(.bss .bss.*)
}
__kernel_end = .;
}

101
arch/x86/kernel/traps.c Normal file
View File

@ -0,0 +1,101 @@
#include <stdint.h>
#include <tianole/arch.h>
#include <tianole/early_log.h>
#include "cpu.h"
static const char *const exception_names[32] = {
"divide error",
"debug",
"non-maskable interrupt",
"breakpoint",
"overflow",
"bound range exceeded",
"invalid opcode",
"device not available",
"double fault",
"coprocessor segment overrun",
"invalid tss",
"segment not present",
"stack segment fault",
"general protection fault",
"page fault",
"reserved",
"x87 floating-point exception",
"alignment check",
"machine check",
"simd floating-point exception",
"virtualization exception",
"control protection exception",
"reserved",
"reserved",
"reserved",
"reserved",
"reserved",
"reserved",
"hypervisor injection exception",
"vmm communication exception",
"security exception",
"reserved",
};
static void log_hex_digit(uint8_t digit)
{
if (digit < 10) {
early_log_putc((char)('0' + digit));
return;
}
early_log_putc((char)('a' + digit - 10));
}
static void log_u64_hex(uint64_t value)
{
int shift;
early_log_puts("0x");
for (shift = 60; shift >= 0; shift -= 4) {
log_hex_digit((uint8_t)((value >> shift) & 0x0f));
}
}
static uint64_t interrupted_rsp(const struct trap_frame *frame)
{
return (uint64_t)(uintptr_t)&frame->rflags + sizeof(frame->rflags);
}
void arch_traps_init(void)
{
gdt_init();
idt_init();
early_log_puts("traps initialized\n");
}
void trap_dispatch(struct trap_frame *frame)
{
uint64_t vector = frame->vector;
const char *name = "unknown exception";
if (vector < 32) {
name = exception_names[vector];
}
early_log_puts("exception: ");
early_log_puts(name);
early_log_puts("\n");
early_log_puts("vector=");
early_log_u64_decimal(vector);
early_log_puts(" error=");
log_u64_hex(frame->error_code);
early_log_puts("\n");
early_log_puts("rip=");
log_u64_hex(frame->rip);
early_log_puts(" rsp=");
log_u64_hex(interrupted_rsp(frame));
early_log_puts(" rflags=");
log_u64_hex(frame->rflags);
early_log_puts("\n");
panic("unhandled CPU exception");
}

View File

@ -6,6 +6,7 @@
建议内容:
- `code-style.md`agent 修改代码时必须遵守的代码组织和风格规则。
- `session-notes/`:实现日志和阶段结论。
- `tasks/`:适合 agent 执行的边界清晰任务,按路线阶段拆分。
- `prompts/`:可复用提示词和约束。
@ -15,8 +16,9 @@
当前任务入口:
- `code-style.md`:代码风格和文件组织规则。
- `tasks/README.md`agent 任务索引。
- `tasks/01-early-debug.md`:下一阶段,早期串口和 panic
- `tasks/02-cpu-interrupts.md`下一阶段GDT/TSS/IDT、异常入口和 trap frame
- `../roadmap.md`:总路线和阶段依赖。
当 agent 修改代码且影响下列内容时,应该同步更新这里或 `docs/`

74
docs/agents/code-style.md Normal file
View File

@ -0,0 +1,74 @@
# Code Style
这个文档记录 agent 修改 Tianole 代码时必须遵守的代码组织和风格规则。
## 总原则
- 参考 Linux 的工程风格,但不机械照抄 Linux 的历史实现。
- 简化实现可以接受,写死架构边界、设备假设、内存布局和调用路径不接受。
- 文件按职责拆分,不按行数拆分。
- 入口文件只编排流程,不承载具体功能细节。
## Include 风格
- 公共头文件使用 `<...>`,例如 `<tianole/boot_info.h>`
- 同目录私有头文件使用 `"..."`,例如 `"file.h"`
- 不使用 `../foo.h` 这类相对 include。
- 如果一个头文件需要跨目录使用,先判断它是否应该成为公共接口。
- `include/tianole/` 放尽量架构无关的共享接口。
- `arch/<arch>/include/` 放架构公开接口。
## 文件组织
- 长文件不是问题,职责混乱才是问题。
- 一个文件可以较长,但必须代表一个清晰子系统、算法或驱动边界。
- 不为了降低行数拆出 `utils.c`、`helpers.c` 这类无边界文件。
- 按职责、生命周期、所有权和调用边界拆分。
- `kernel/main.c`、`boot/main.c` 这类入口文件应保持短,只串联阶段。
- `mm/` 是顶层内存管理子系统目录,不放在 `kernel/mm/` 下。
## 命名
- 内部 C API 不加 `tianole_` 前缀。
- 架构目录内部不要重复架构名前缀;例如 `arch/x86/kernel/` 内使用 `gdt_init()`,不要写成 `x86_gdt_init()`
- 跨通用层暴露的架构入口使用 `arch_` 前缀,例如 `arch_traps_init()`
- 描述硬件规格的文档文字可以写 `x86_64`,但代码文件名和内部符号不需要反复带 `x86`
- 避免 `temp`、`tmp` 这类临时语义进入函数名、类型名和长期变量名。
- 一次性局部变量也应尽量使用具体含义命名。
- 名字应表达长期职责,不表达当前实现的临时状态。
## 注释
- 注释优先解释硬件约束、ABI 决策、内存所有权、并发语义和不明显的不变量。
- 不写重复描述简单代码行为的注释。
- 如果代码依赖硬件手册、启动协议、调用顺序或特殊寄存器状态,应写明约束。
- 如果一个实现是临时简化,应写清楚后续替换边界,而不是写成永久接口。
## C 格式
- 使用 `.clang-format` 作为当前格式化规则。
- 使用 tab 缩进。
- 函数左花括号另起一行。
- `if/for/while` 左花括号留在行尾。
- 文本文件使用 LF 行尾。
## 构建文件
- 根 `Makefile` 只做总控和通用规则。
- 架构配置放在 `arch/<arch>/Makefile`
- 目录自己的源文件列表放在对应目录的 `Makefile`
- 顶层子系统目录可以有自己的 `Makefile`,例如 `mm/Makefile`
- 不新增独立 `mk/` 目录,除非后续有明确且无法避免的理由。
## 脚本组织
- `scripts/check.sh` 是本地和 CI 共用的检查入口,只做编排。
- 可独立执行的检查放在 `scripts/checks/`
- 多个检查共享的函数放在 `scripts/lib/`
- 不把所有检查逻辑持续堆进 `scripts/check.sh`
## 验证
- 新增或大改 C/H 文件后运行 `clang-format`
- 提交前至少运行 `scripts/check.sh`
- 如果只是文档修改,至少运行 `git diff --check`

View File

@ -40,15 +40,24 @@
## 当前状态
完成:
基础完成:
- kernel early log 已拆成通用前端和 x86 backend。
- x86 backend 已同时写 QEMU debug port 和 COM1。
- `panic()` 已接入 early log 和 `arch_halt_forever()`
- `scripts/check.sh` 已验证 `build/debug.log``build/serial.log` 中的关键启动行。
后续进入异常阶段时继续补齐
后续扩展
- printk/log level/ring buffer。
- oops 格式。
- 异常栈输出。
- 内核符号化输出。
- framebuffer console。
- crash dump。
验收依据:
- 正常启动日志能进入 QEMU debug log。
- kernel 日志能进入 COM1 serial log。
- `panic()` 已被 `02-cpu-interrupts.md` 的 invalid opcode 测试覆盖。

View File

@ -39,3 +39,37 @@
- 主动触发 invalid opcode 或 divide error。
- 日志输出 vector、rip、rsp、error code。
- 未处理异常进入 panic。
## 当前状态
基础完成:
- x86_64 GDT 已加载。
- 最小 TSS 已建立并通过 `ltr` 加载。
- IDT 已建立,当前覆盖 CPU exception vector 0-31。
- 异常入口汇编已统一保存通用寄存器现场。
- C 层 trap dispatch 已接收统一 `trap_frame`
- 未处理异常进入 `panic("unhandled CPU exception")`
- `KERNEL_TEST_TRAP=1` 会通过 `ud2` 主动触发 invalid opcode。
- `scripts/check.sh` 已自动验证 invalid opcode 日志和 panic 路径。
后续扩展:
- PIC/APIC 初始化。
- 外部 IRQ 分发。
- timer interrupt。
- page fault 的专门处理策略。
- double fault 独立 IST 栈。
- 用户态异常返回。
- 可恢复异常处理。
- oops 格式和符号化输出。
验收依据:
- 正常启动日志包含 `traps initialized`
- invalid opcode 测试能输出 vector、error code、rip、rsp、rflags。
- 未处理异常能进入 `panic("unhandled CPU exception")`
下一阶段:
- `03-memory.md`,先建立物理页、页表和内核堆。

View File

@ -11,7 +11,7 @@
## 建议边界
- `kernel/mm/`:架构无关内存管理。
- `mm/`:架构无关内存管理。
- `arch/x86/mm/`页表格式、地址空间切换、TLB 操作。
- `include/tianole/`:通用内存接口。
@ -42,3 +42,33 @@
- page fault 能输出有效诊断。
- 内核堆能分配小对象。
- 后续能在此基础上加入 COW、`mmap`、换页和内存回收。
## 当前状态
基础完成:
- 已从 boot memory map 扫描 conventional memory。
- 已排除 kernel image、`boot_info` 和 boot memory map 占用页。
- 已建立最小物理页 free-list allocator。
- 已提供 `alloc_page()``free_page()`
- 已加入物理页分配/释放 selftest。
- `scripts/check.sh` 已验证 `physical pages free=` 和 selftest 日志。
后续扩展:
- 长期内存区域模型,不直接依赖 UEFI memory type。
- page metadata。
- buddy allocator。
- 页表 map/unmap。
- page fault 专门处理。
- 最小内核堆。
- slab/slub 或等价小对象缓存。
验收依据:
- 正常启动日志包含 `physical pages free=`
- 正常启动日志包含 `physical page allocator selftest ok`
下一阶段:
- 继续在 `03-memory.md` 内推进页表管理和内核堆。

View File

@ -14,7 +14,7 @@
- `drivers/block/`:块设备抽象和具体设备。
- `kernel/fs/`VFS。
- `kernel/mm/`:页缓存或块缓存。
- `mm/`:页缓存或块缓存。
- `fs/`:具体文件系统实现。
## 实现内容

View File

@ -5,7 +5,7 @@
## 当前状态
已经完成
基础已具备
- 最小 `UEFI -> bootloader -> kernel` 启动链。
- bootloader 与 kernel 拆分。
@ -13,32 +13,34 @@
- 进入 kernel 前调用 `ExitBootServices`
- `memory map` 通过 `boot_info` 传入 kernel。
- kernel 能统计 memory map 描述符数量和 conventional memory 页数。
- kernel early log 已拆成通用前端和 x86 backend。
- x86 early log backend 同时输出到 QEMU debug port 和 COM1 串口。
- 最小 `panic()` 已接入 early log 和架构 halt 路径。
- early log 基础通用前端、x86 backend、QEMU debug port、COM1 串口。
- panic 基础:最小 `panic()` 已接入 early log 和架构 halt 路径。
- CPU exception 基础x86_64 GDT/TSS/IDT、exception vector 0-31、统一 trap frame。
- trap 验证invalid opcode 已能进入 trap dispatch 并最终进入 panic。
- 物理内存基础:已从 boot memory map 建立最小物理页 allocator。
- 物理页验证:`alloc_page/free_page` selftest 已接入启动检查。
- 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。
- `scripts/check.sh` 已验证启动日志和串口日志GitHub Actions 已接入。
- `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径GitHub Actions 已接入。
- `.clang-format` 已用于强制当前 C 代码风格。
还没有完成:
后续未完成:
- oops 早期错误路径
- GDT/IDT/异常/中断
- 物理页分配器、虚拟内存和内核堆。
- 调度、进程、文件系统、用户态和 shell。
- 完整日志体系printk、log level、ring buffer、oops、符号化、crash dump
- 完整中断体系PIC/APIC、外部 IRQ、timer interrupt
- 内存管理扩展长期内存区域模型、页表、page fault 策略、内核堆。
- 后续主线:调度、进程、文件系统、用户态和 shell。
## 当前下一步
下一步执行:
- `docs/agents/tasks/02-cpu-interrupts.md`
- `docs/agents/tasks/03-memory.md`
目标:
- 建立 x86_64 GDT/TSS/IDT。
- 建立异常入口和 trap frame。
- 让未处理异常进入 panic。
- 为后续 IRQ、timer 和 page fault 做准备。
- 建立内核页表管理。
- 建立最小内核堆。
- 让 page fault 能进入现有异常路径并输出有效诊断。
## 任务路由

View File

@ -4,5 +4,6 @@
void arch_early_log_init(void);
void arch_early_log_putc(char ch);
void arch_halt_forever(void) __attribute__((noreturn));
void arch_traps_init(void);
#endif

16
include/tianole/mm.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef TIANOLE_MM_H
#define TIANOLE_MM_H
#include <stdint.h>
#include <tianole/boot_info.h>
#define PAGE_SIZE 4096u
typedef uint64_t phys_addr_t;
void mm_init(const boot_info_t *boot_info);
phys_addr_t alloc_page(void);
void free_page(phys_addr_t page);
#endif

View File

@ -5,4 +5,5 @@ KERNEL_SRCS := \
KERNEL_OBJS := \
$(ARCH_KERNEL_OBJS) \
$(patsubst kernel/%.c,$(BUILD_DIR)/kernel/%.o,$(KERNEL_SRCS))
$(patsubst kernel/%.c,$(BUILD_DIR)/kernel/%.o,$(KERNEL_SRCS)) \
$(MM_OBJS)

View File

@ -1,11 +1,19 @@
#include <tianole/arch.h>
#include <tianole/early_log.h>
#include <tianole/kernel_init.h>
#include <tianole/mm.h>
void kernel_main(const boot_info_t *boot_info)
{
early_log_init();
early_log_puts("kernel_main entered\n");
arch_traps_init();
kernel_report_boot_state(boot_info);
mm_init(boot_info);
#if KERNEL_TEST_TRAP
__asm__ volatile("ud2");
#endif
for (;;) {
__asm__ volatile("hlt");

5
mm/Makefile Normal file
View File

@ -0,0 +1,5 @@
MM_SRCS := \
mm/page_alloc.c
MM_OBJS := \
$(patsubst mm/%.c,$(BUILD_DIR)/mm/%.o,$(MM_SRCS))

164
mm/page_alloc.c Normal file
View File

@ -0,0 +1,164 @@
#include <stdint.h>
#include <tianole/early_log.h>
#include <tianole/mm.h>
struct page_node {
struct page_node *next;
};
extern char __kernel_start[];
extern char __kernel_end[];
static struct page_node *free_pages;
static uint64_t free_page_count;
static uint64_t align_down(uint64_t value, uint64_t alignment)
{
return value & ~(alignment - 1);
}
static uint64_t align_up(uint64_t value, uint64_t alignment)
{
return align_down(value + alignment - 1, alignment);
}
static int ranges_overlap(uint64_t start,
uint64_t end,
uint64_t reserved_start,
uint64_t reserved_end)
{
return start < reserved_end && reserved_start < end;
}
static int page_is_reserved(const boot_info_t *boot_info, uint64_t page)
{
uint64_t page_end = page + PAGE_SIZE;
uint64_t kernel_start =
align_down((uint64_t)(uintptr_t)__kernel_start, PAGE_SIZE);
uint64_t kernel_end =
align_up((uint64_t)(uintptr_t)__kernel_end, PAGE_SIZE);
uint64_t boot_info_start =
align_down((uint64_t)(uintptr_t)boot_info, PAGE_SIZE);
uint64_t boot_info_end = align_up(
(uint64_t)(uintptr_t)boot_info + sizeof(*boot_info), PAGE_SIZE);
uint64_t map_start = align_down(boot_info->memory_map, PAGE_SIZE);
uint64_t map_end = align_up(
boot_info->memory_map + boot_info->memory_map_size, PAGE_SIZE);
if (ranges_overlap(page, page_end, kernel_start, kernel_end)) {
return 1;
}
if (ranges_overlap(page, page_end, boot_info_start, boot_info_end)) {
return 1;
}
if (ranges_overlap(page, page_end, map_start, map_end)) {
return 1;
}
return 0;
}
static void add_free_page(uint64_t page)
{
struct page_node *node = (struct page_node *)(uintptr_t)page;
node->next = free_pages;
free_pages = node;
free_page_count++;
}
static void add_conventional_range(
const boot_info_t *boot_info, uint64_t start, uint64_t pages)
{
uint64_t page;
uint64_t end = start + pages * PAGE_SIZE;
for (page = align_up(start, PAGE_SIZE); page + PAGE_SIZE <= end;
page += PAGE_SIZE) {
if (!page_is_reserved(boot_info, page)) {
add_free_page(page);
}
}
}
static void init_free_pages(const boot_info_t *boot_info)
{
uint64_t offset;
for (offset = 0; offset + sizeof(boot_memory_descriptor_t) <=
boot_info->memory_map_size;
offset += boot_info->memory_descriptor_size) {
const boot_memory_descriptor_t *descriptor =
(const boot_memory_descriptor_t
*)(uintptr_t)(boot_info->memory_map +
offset);
if (descriptor->type != BOOT_MEMORY_TYPE_CONVENTIONAL) {
continue;
}
add_conventional_range(boot_info,
descriptor->physical_start,
descriptor->number_of_pages);
}
}
phys_addr_t alloc_page(void)
{
struct page_node *node = free_pages;
if (node == 0) {
return 0;
}
free_pages = node->next;
free_page_count--;
return (phys_addr_t)(uintptr_t)node;
}
void free_page(phys_addr_t page)
{
if ((page & (PAGE_SIZE - 1)) != 0 || page == 0) {
panic("invalid physical page free");
}
add_free_page(page);
}
static void page_allocator_selftest(void)
{
phys_addr_t first = alloc_page();
phys_addr_t second = alloc_page();
if (first == 0 || second == 0 || first == second) {
panic("physical page allocator selftest failed");
}
free_page(second);
free_page(first);
early_log_puts("physical page allocator selftest ok\n");
}
void mm_init(const boot_info_t *boot_info)
{
if (boot_info == 0 || boot_info->memory_map == 0 ||
boot_info->memory_descriptor_size == 0) {
panic("memory map unavailable");
}
free_pages = 0;
free_page_count = 0;
init_free_pages(boot_info);
early_log_puts("physical pages free=");
early_log_u64_decimal(free_page_count);
early_log_puts("\n");
page_allocator_selftest();
}

View File

@ -3,49 +3,7 @@ set -euo pipefail
cd "$(dirname "$0")/.."
sources=$(find arch include kernel -name '*.c' -o -name '*.h')
clang-format --dry-run --Werror $sources
make clean
make
timeout 30s make run-headless || true
required_lines=(
"Tianole x86 bootloader loaded."
"jumping to kernel entry"
"kernel_main entered"
"boot_info.version ok"
"boot services exited"
"memory map descriptors="
"conventional memory pages="
)
for line in "${required_lines[@]}"; do
if ! grep -F "$line" build/debug.log >/dev/null; then
echo "missing expected boot log line: $line" >&2
echo "--- build/debug.log ---" >&2
cat build/debug.log >&2 || true
exit 1
fi
done
serial_required_lines=(
"kernel_main entered"
"boot_info.version ok"
"boot services exited"
"memory map descriptors="
"conventional memory pages="
)
for line in "${serial_required_lines[@]}"; do
if ! grep -F "$line" build/serial.log >/dev/null; then
echo "missing expected serial log line: $line" >&2
echo "--- build/serial.log ---" >&2
cat build/serial.log >&2 || true
exit 1
fi
done
cat build/debug.log
./scripts/checks/format.sh
./scripts/checks/boot.sh
./scripts/checks/trap.sh
./scripts/checks/default-build.sh

33
scripts/checks/boot.sh Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/../.."
. ./scripts/lib/check-common.sh
build_kernel
run_qemu
check_lines build/debug.log \
"Tianole x86 bootloader loaded." \
"jumping to kernel entry" \
"kernel_main entered" \
"traps initialized" \
"boot_info.version ok" \
"boot services exited" \
"memory map descriptors=" \
"conventional memory pages=" \
"physical pages free=" \
"physical page allocator selftest ok"
check_lines build/serial.log \
"kernel_main entered" \
"traps initialized" \
"boot_info.version ok" \
"boot services exited" \
"memory map descriptors=" \
"conventional memory pages=" \
"physical pages free=" \
"physical page allocator selftest ok"
cat build/debug.log

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/../.."
. ./scripts/lib/check-common.sh
build_kernel

7
scripts/checks/format.sh Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/../.."
sources=$(find arch include kernel mm -name '*.c' -o -name '*.h')
clang-format --dry-run --Werror $sources

19
scripts/checks/trap.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/../.."
. ./scripts/lib/check-common.sh
build_kernel KERNEL_TEST_TRAP=1
run_qemu KERNEL_TEST_TRAP=1
check_lines build/debug.log \
"traps initialized" \
"exception: invalid opcode" \
"vector=6 error=0x0000000000000000" \
"rip=0x" \
"rsp=0x" \
"panic: unhandled CPU exception"
cat build/debug.log

27
scripts/lib/check-common.sh Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
check_lines()
{
local log_file=$1
shift
for line in "$@"; do
if ! grep -F "$line" "$log_file" >/dev/null; then
echo "missing expected log line: $line" >&2
echo "--- $log_file ---" >&2
cat "$log_file" >&2 || true
exit 1
fi
done
}
build_kernel()
{
make clean
make "$@"
}
run_qemu()
{
timeout 30s make run-headless "$@" || true
}