diff --git a/AGENTS.md b/AGENTS.md index 162c5c6..7720574 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,12 +21,15 @@ ## 当前约定 +- 详细代码风格和文件组织规则见 `docs/agents/code-style.md`。 - 当前代码以“先做最小,但不把架构完全写死”为原则推进。 - `arch/` 放架构相关实现。 +- `mm/` 放架构无关内存管理实现,和 Linux 一样作为顶层核心子系统维护。 - `include/tianole/` 放尽量与具体架构无关的共享接口。 - 入口文件只负责串联启动流程,不承载文件加载、ELF 解析、内存映射统计等功能细节。 - 检查式日志应该放在对应功能模块中,避免把 `kernel_main()` 变成临时测试脚本。 - 内部 C API 不加 `tianole_` 前缀;这是单一内核代码库,不把项目名重复进函数名和类型名。 +- 架构目录内部不重复架构名前缀;跨通用层暴露的架构入口使用 `arch_` 前缀。 - 避免 `temp` / `tmp` 这类临时语义进入函数名、类型名和长期变量名;一次性局部变量可以用具体含义命名。 - C 代码风格参考 Linux:tabs 缩进,函数左花括号另起一行,`if/for/while` 左花括号留在行尾。 - 公共头文件使用 `<...>` include,例如 ``;同目录私有头文件使用 `"..."` include,例如 `"file.h"`。 @@ -37,4 +40,5 @@ - 文本文件使用 LF 行尾。 - `.clang-format` 是当前格式化约定;如果环境有 `clang-format`,新增或大改 C/H 文件后应按它格式化。 - 根 `Makefile` 只做总控和通用规则;架构配置放在 `arch//Makefile`,目录自己的源文件列表放在对应目录的 `Makefile` 中。 -- 本地维护入口是 `scripts/check.sh`;GitHub Actions 应调用同一个脚本,避免 CI 逻辑和本地逻辑分叉。 +- 本地维护入口是 `scripts/check.sh`;具体检查放在 `scripts/checks/`,公共脚本函数放在 `scripts/lib/`。 +- GitHub Actions 应调用 `scripts/check.sh`,避免 CI 逻辑和本地逻辑分叉。 diff --git a/Makefile b/Makefile index cd676c9..078eeb1 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index aa74909..3e96814 100644 --- a/README.md +++ b/README.md @@ -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/`。 ## 文档 diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 54c80cf..9a6d048 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -29,6 +29,7 @@ KERNEL_CFLAGS := \ -fno-builtin \ -fno-pic \ -mno-red-zone \ + -DKERNEL_TEST_TRAP=$(KERNEL_TEST_TRAP) \ -Wall \ -Wextra \ -Werror \ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 5f277d5..c616105 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -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)) diff --git a/arch/x86/kernel/cpu.h b/arch/x86/kernel/cpu.h new file mode 100644 index 0000000..68b83b1 --- /dev/null +++ b/arch/x86/kernel/cpu.h @@ -0,0 +1,71 @@ +#ifndef ARCH_X86_KERNEL_CPU_H +#define ARCH_X86_KERNEL_CPU_H + +#include + +#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 diff --git a/arch/x86/kernel/entry.S b/arch/x86/kernel/entry.S index 07e87ad..00ceafa 100644 --- a/arch/x86/kernel/entry.S +++ b/arch/x86/kernel/entry.S @@ -1,17 +1,18 @@ .global _start +.global kernel_stack_top .type _start, @function .section .text _start: - lea kernel_stack_top(%rip), %rsp - call kernel_main + lea kernel_stack_top(%rip), %rsp + call kernel_main 1: - hlt - jmp 1b + hlt + jmp 1b .section .bss .balign 16 kernel_stack: - .skip 16384 + .skip 16384 kernel_stack_top: diff --git a/arch/x86/kernel/exception_entry.S b/arch/x86/kernel/exception_entry.S new file mode 100644 index 0000000..5005e29 --- /dev/null +++ b/arch/x86/kernel/exception_entry.S @@ -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 diff --git a/arch/x86/kernel/gdt.c b/arch/x86/kernel/gdt.c new file mode 100644 index 0000000..947bcc9 --- /dev/null +++ b/arch/x86/kernel/gdt.c @@ -0,0 +1,134 @@ +#include + +#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(); +} diff --git a/arch/x86/kernel/idt.c b/arch/x86/kernel/idt.c new file mode 100644 index 0000000..1ce10f4 --- /dev/null +++ b/arch/x86/kernel/idt.c @@ -0,0 +1,85 @@ +#include + +#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); +} diff --git a/arch/x86/kernel/linker.ld b/arch/x86/kernel/linker.ld index 0e462c6..970c24f 100644 --- a/arch/x86/kernel/linker.ld +++ b/arch/x86/kernel/linker.ld @@ -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 = .; } diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c new file mode 100644 index 0000000..6d200b6 --- /dev/null +++ b/arch/x86/kernel/traps.c @@ -0,0 +1,101 @@ +#include + +#include +#include + +#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"); +} diff --git a/docs/agents/README.md b/docs/agents/README.md index c937caf..359fab2 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -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/`: diff --git a/docs/agents/code-style.md b/docs/agents/code-style.md new file mode 100644 index 0000000..8754023 --- /dev/null +++ b/docs/agents/code-style.md @@ -0,0 +1,74 @@ +# Code Style + +这个文档记录 agent 修改 Tianole 代码时必须遵守的代码组织和风格规则。 + +## 总原则 + +- 参考 Linux 的工程风格,但不机械照抄 Linux 的历史实现。 +- 简化实现可以接受,写死架构边界、设备假设、内存布局和调用路径不接受。 +- 文件按职责拆分,不按行数拆分。 +- 入口文件只编排流程,不承载具体功能细节。 + +## Include 风格 + +- 公共头文件使用 `<...>`,例如 ``。 +- 同目录私有头文件使用 `"..."`,例如 `"file.h"`。 +- 不使用 `../foo.h` 这类相对 include。 +- 如果一个头文件需要跨目录使用,先判断它是否应该成为公共接口。 +- `include/tianole/` 放尽量架构无关的共享接口。 +- `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//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`。 diff --git a/docs/agents/tasks/01-early-debug.md b/docs/agents/tasks/01-early-debug.md index 100baeb..7af3f90 100644 --- a/docs/agents/tasks/01-early-debug.md +++ b/docs/agents/tasks/01-early-debug.md @@ -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 测试覆盖。 diff --git a/docs/agents/tasks/02-cpu-interrupts.md b/docs/agents/tasks/02-cpu-interrupts.md index 0fe136b..288b19d 100644 --- a/docs/agents/tasks/02-cpu-interrupts.md +++ b/docs/agents/tasks/02-cpu-interrupts.md @@ -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`,先建立物理页、页表和内核堆。 diff --git a/docs/agents/tasks/03-memory.md b/docs/agents/tasks/03-memory.md index d55d2aa..4320042 100644 --- a/docs/agents/tasks/03-memory.md +++ b/docs/agents/tasks/03-memory.md @@ -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` 内推进页表管理和内核堆。 diff --git a/docs/agents/tasks/06-storage-vfs.md b/docs/agents/tasks/06-storage-vfs.md index a7b8bd7..1b9581c 100644 --- a/docs/agents/tasks/06-storage-vfs.md +++ b/docs/agents/tasks/06-storage-vfs.md @@ -14,7 +14,7 @@ - `drivers/block/`:块设备抽象和具体设备。 - `kernel/fs/`:VFS。 -- `kernel/mm/`:页缓存或块缓存。 +- `mm/`:页缓存或块缓存。 - `fs/`:具体文件系统实现。 ## 实现内容 diff --git a/docs/roadmap.md b/docs/roadmap.md index a712b24..c02c6cc 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -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 能进入现有异常路径并输出有效诊断。 ## 任务路由 diff --git a/include/tianole/arch.h b/include/tianole/arch.h index 757dad1..146b3d9 100644 --- a/include/tianole/arch.h +++ b/include/tianole/arch.h @@ -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 diff --git a/include/tianole/mm.h b/include/tianole/mm.h new file mode 100644 index 0000000..b7b344b --- /dev/null +++ b/include/tianole/mm.h @@ -0,0 +1,16 @@ +#ifndef TIANOLE_MM_H +#define TIANOLE_MM_H + +#include + +#include + +#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 diff --git a/kernel/Makefile b/kernel/Makefile index a567341..fef1332 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -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) diff --git a/kernel/main.c b/kernel/main.c index 5046b25..522b62d 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -1,11 +1,19 @@ +#include #include #include +#include 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"); diff --git a/mm/Makefile b/mm/Makefile new file mode 100644 index 0000000..35a68a2 --- /dev/null +++ b/mm/Makefile @@ -0,0 +1,5 @@ +MM_SRCS := \ + mm/page_alloc.c + +MM_OBJS := \ + $(patsubst mm/%.c,$(BUILD_DIR)/mm/%.o,$(MM_SRCS)) diff --git a/mm/page_alloc.c b/mm/page_alloc.c new file mode 100644 index 0000000..c44b8f5 --- /dev/null +++ b/mm/page_alloc.c @@ -0,0 +1,164 @@ +#include + +#include +#include + +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(); +} diff --git a/scripts/check.sh b/scripts/check.sh index e338b92..4a63bad 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -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 diff --git a/scripts/checks/boot.sh b/scripts/checks/boot.sh new file mode 100755 index 0000000..8899f0a --- /dev/null +++ b/scripts/checks/boot.sh @@ -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 diff --git a/scripts/checks/default-build.sh b/scripts/checks/default-build.sh new file mode 100755 index 0000000..2ceb743 --- /dev/null +++ b/scripts/checks/default-build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/../.." + +. ./scripts/lib/check-common.sh + +build_kernel diff --git a/scripts/checks/format.sh b/scripts/checks/format.sh new file mode 100755 index 0000000..a23e427 --- /dev/null +++ b/scripts/checks/format.sh @@ -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 diff --git a/scripts/checks/trap.sh b/scripts/checks/trap.sh new file mode 100755 index 0000000..569c24b --- /dev/null +++ b/scripts/checks/trap.sh @@ -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 diff --git a/scripts/lib/check-common.sh b/scripts/lib/check-common.sh new file mode 100755 index 0000000..af16793 --- /dev/null +++ b/scripts/lib/check-common.sh @@ -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 +}