diff --git a/Makefile b/Makefile index a26e56d..df225f6 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ KERNEL_ELF := $(BUILD_DIR)/image/kernel.elf DEBUG_LOG := $(BUILD_DIR)/debug.log SERIAL_LOG := $(BUILD_DIR)/serial.log KERNEL_TEST_TRAP ?= 0 +KERNEL_TEST_PAGE_FAULT ?= 0 ifeq ($(ARCH),x86_64) ARCH_MAKEFILE := arch/x86/Makefile diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 9a6d048..98168c8 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -30,6 +30,7 @@ KERNEL_CFLAGS := \ -fno-pic \ -mno-red-zone \ -DKERNEL_TEST_TRAP=$(KERNEL_TEST_TRAP) \ + -DKERNEL_TEST_PAGE_FAULT=$(KERNEL_TEST_PAGE_FAULT) \ -Wall \ -Wextra \ -Werror \ diff --git a/arch/x86/include/arch/traps.h b/arch/x86/include/arch/traps.h new file mode 100644 index 0000000..76f8736 --- /dev/null +++ b/arch/x86/include/arch/traps.h @@ -0,0 +1,32 @@ +#ifndef ARCH_X86_TRAPS_H +#define ARCH_X86_TRAPS_H + +#include + +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 trap_dispatch(struct trap_frame *frame); +void handle_page_fault(struct trap_frame *frame); + +#endif diff --git a/arch/x86/kernel/cpu.h b/arch/x86/kernel/cpu.h index 68b83b1..b92d837 100644 --- a/arch/x86/kernel/cpu.h +++ b/arch/x86/kernel/cpu.h @@ -7,33 +7,11 @@ #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; -}; +#include 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); diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index bfa40a3..e102b3e 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -40,39 +40,11 @@ static const char *const exception_names[32] = { "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); } -static uint64_t read_cr2(void) -{ - uint64_t cr2; - - __asm__ volatile("movq %%cr2, %0" : "=r"(cr2)); - return cr2; -} - void arch_traps_init(void) { gdt_init(); @@ -95,20 +67,18 @@ void trap_dispatch(struct trap_frame *frame) early_log_puts("vector="); early_log_u64_decimal(vector); early_log_puts(" error="); - log_u64_hex(frame->error_code); + early_log_u64_hex(frame->error_code); early_log_puts("\n"); early_log_puts("rip="); - log_u64_hex(frame->rip); + early_log_u64_hex(frame->rip); early_log_puts(" rsp="); - log_u64_hex(interrupted_rsp(frame)); + early_log_u64_hex(interrupted_rsp(frame)); early_log_puts(" rflags="); - log_u64_hex(frame->rflags); + early_log_u64_hex(frame->rflags); early_log_puts("\n"); if (vector == 14) { - early_log_puts("fault_address="); - log_u64_hex(read_cr2()); - early_log_puts("\n"); + handle_page_fault(frame); } panic("unhandled CPU exception"); diff --git a/arch/x86/mm/Makefile b/arch/x86/mm/Makefile index 1d614b0..be05387 100644 --- a/arch/x86/mm/Makefile +++ b/arch/x86/mm/Makefile @@ -1,4 +1,5 @@ ARCH_MM_SRCS := \ + $(ARCH_DIR)/mm/fault.c \ $(ARCH_DIR)/mm/page_table.c ARCH_MM_OBJS := \ diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c new file mode 100644 index 0000000..8300c36 --- /dev/null +++ b/arch/x86/mm/fault.c @@ -0,0 +1,53 @@ +#include + +#include + +#define PF_PRESENT (1ull << 0) +#define PF_WRITE (1ull << 1) +#define PF_USER (1ull << 2) +#define PF_RESERVED (1ull << 3) +#define PF_INSTRUCTION_FETCH (1ull << 4) + +static uint64_t read_cr2(void) +{ + uint64_t cr2; + + __asm__ volatile("movq %%cr2, %0" : "=r"(cr2)); + return cr2; +} + +static void log_fault_access(uint64_t error_code) +{ + early_log_puts("access="); + + if ((error_code & PF_INSTRUCTION_FETCH) != 0) { + early_log_puts("execute"); + } else if ((error_code & PF_WRITE) != 0) { + early_log_puts("write"); + } else { + early_log_puts("read"); + } + + early_log_puts(" mode="); + early_log_puts((error_code & PF_USER) != 0 ? "user" : "kernel"); + + early_log_puts(" reason="); + early_log_puts( + (error_code & PF_PRESENT) != 0 ? "protection" : "not-present"); + + if ((error_code & PF_RESERVED) != 0) { + early_log_puts(" reserved-bit"); + } + + early_log_puts("\n"); +} + +void handle_page_fault(struct trap_frame *frame) +{ + early_log_puts("page fault: address="); + early_log_u64_hex(read_cr2()); + early_log_puts(" error="); + early_log_u64_hex(frame->error_code); + early_log_puts("\n"); + log_fault_access(frame->error_code); +} diff --git a/docs/agents/tasks/03-memory.md b/docs/agents/tasks/03-memory.md index 36990a1..cd8d640 100644 --- a/docs/agents/tasks/03-memory.md +++ b/docs/agents/tasks/03-memory.md @@ -55,14 +55,14 @@ - 已切换到内核自有 PML4,不再直接修改固件页表。 - 已提供最小 `map_page()`、`unmap_page()` 和 `virt_to_phys()` 接口。 - 已加入页表 map/unmap/query selftest。 -- `scripts/check.sh` 已验证 `physical pages free=`、物理页 selftest、页表根切换和页表 selftest 日志。 +- 已拆出 x86 page fault 诊断路径,能输出 fault address、错误码、访问类型和权限来源。 +- `scripts/check.sh` 已验证 `physical pages free=`、物理页 selftest、页表根切换、页表 selftest 和 page fault 日志。 后续扩展: - 长期内存区域模型,不直接依赖 UEFI memory type。 - page metadata。 - buddy allocator。 -- page fault 专门处理。 - 最小内核堆。 - slab/slub 或等价小对象缓存。 @@ -72,7 +72,8 @@ - 正常启动日志包含 `physical page allocator selftest ok`。 - 正常启动日志包含 `kernel page table root active`。 - 正常启动日志包含 `page table selftest ok`。 +- page fault 测试日志包含 `page fault: address=` 和 `access=write mode=kernel reason=not-present`。 下一阶段: -- 继续在 `03-memory.md` 内推进 page fault 专门处理和最小内核堆。 +- 继续在 `03-memory.md` 内推进最小内核堆。 diff --git a/docs/agents/tasks/README.md b/docs/agents/tasks/README.md index 1e65da1..c544d9f 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` 内:先补 page fault 专门诊断,再接最小内核堆。 +当前最合适的下一步仍在 `03-memory.md` 内:继续做最小内核堆。 ## Linux 级能力缺口路由 diff --git a/docs/roadmap.md b/docs/roadmap.md index 3c2a45e..ec363e4 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -21,6 +21,7 @@ - 物理页验证:`alloc_page/free_page` selftest 已接入启动检查。 - 页表基础:已切换到内核自有 PML4,并提供最小 `map_page/unmap_page/virt_to_phys`。 - 页表验证:页表 map/unmap/query selftest 已接入启动检查。 +- page fault 基础:已能输出 fault address、错误码、访问类型和权限来源。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 - `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。 - `.clang-format` 已用于强制当前 C 代码风格。 @@ -29,7 +30,7 @@ - 完整日志体系:printk、log level、ring buffer、oops、符号化、crash dump。 - 完整中断体系:PIC/APIC、外部 IRQ、timer interrupt。 -- 内存管理扩展:长期内存区域模型、page fault 策略、内核堆。 +- 内存管理扩展:长期内存区域模型、内核堆、后续完整缺页策略。 - 后续主线:调度、进程、文件系统、用户态和 shell。 ## 当前下一步 @@ -41,7 +42,6 @@ 目标: - 建立最小内核堆。 -- 让 page fault 能进入现有异常路径并输出有效诊断。 ## 任务路由 diff --git a/include/tianole/early_log.h b/include/tianole/early_log.h index 59bc5b2..c16b137 100644 --- a/include/tianole/early_log.h +++ b/include/tianole/early_log.h @@ -6,6 +6,7 @@ void early_log_init(void); void early_log_putc(char ch); void early_log_puts(const char *text); +void early_log_u64_hex(uint64_t value); void early_log_u64_decimal(uint64_t value); void panic(const char *message) __attribute__((noreturn)); diff --git a/kernel/early_log.c b/kernel/early_log.c index e354f9d..b4edf37 100644 --- a/kernel/early_log.c +++ b/kernel/early_log.c @@ -38,6 +38,26 @@ void early_log_puts(const char *text) } } +static void early_log_hex_digit(uint8_t digit) +{ + if (digit < 10) { + early_log_putc((char)('0' + digit)); + return; + } + + early_log_putc((char)('a' + digit - 10)); +} + +void early_log_u64_hex(uint64_t value) +{ + int shift; + + early_log_puts("0x"); + for (shift = 60; shift >= 0; shift -= 4) { + early_log_hex_digit((uint8_t)((value >> shift) & 0x0f)); + } +} + void early_log_u64_decimal(uint64_t value) { char digits[20]; diff --git a/kernel/main.c b/kernel/main.c index 522b62d..f1745f8 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -15,6 +17,10 @@ void kernel_main(const boot_info_t *boot_info) __asm__ volatile("ud2"); #endif +#if KERNEL_TEST_PAGE_FAULT + *(volatile uint64_t *)(uintptr_t)0xffffff1000000000ull = 1; +#endif + for (;;) { __asm__ volatile("hlt"); } diff --git a/scripts/check.sh b/scripts/check.sh index 4a63bad..b78580b 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -6,4 +6,5 @@ cd "$(dirname "$0")/.." ./scripts/checks/format.sh ./scripts/checks/boot.sh ./scripts/checks/trap.sh +./scripts/checks/page-fault.sh ./scripts/checks/default-build.sh diff --git a/scripts/checks/page-fault.sh b/scripts/checks/page-fault.sh new file mode 100755 index 0000000..b1a3190 --- /dev/null +++ b/scripts/checks/page-fault.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/../.." + +. ./scripts/lib/check-common.sh + +build_kernel KERNEL_TEST_PAGE_FAULT=1 +run_qemu KERNEL_TEST_PAGE_FAULT=1 + +check_lines build/debug.log \ + "traps initialized" \ + "kernel page table root active" \ + "page table selftest ok" \ + "exception: page fault" \ + "vector=14 error=0x0000000000000002" \ + "page fault: address=0xffffff1000000000 error=0x0000000000000002" \ + "access=write mode=kernel reason=not-present" \ + "panic: unhandled CPU exception" + +cat build/debug.log