feat(mm): add early page fault diagnostics

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

View File

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

View File

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

View File

@ -0,0 +1,32 @@
#ifndef ARCH_X86_TRAPS_H
#define ARCH_X86_TRAPS_H
#include <stdint.h>
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

View File

@ -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 <arch/traps.h>
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);

View File

@ -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");

View File

@ -1,4 +1,5 @@
ARCH_MM_SRCS := \
$(ARCH_DIR)/mm/fault.c \
$(ARCH_DIR)/mm/page_table.c
ARCH_MM_OBJS := \

53
arch/x86/mm/fault.c Normal file
View File

@ -0,0 +1,53 @@
#include <arch/traps.h>
#include <tianole/early_log.h>
#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);
}

View File

@ -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` 内推进最小内核堆。

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` 内:先补 page fault 专门诊断,再接最小内核堆。
当前最合适的下一步仍在 `03-memory.md` 内:继续做最小内核堆。
## Linux 级能力缺口路由

View File

@ -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 能进入现有异常路径并输出有效诊断。
## 任务路由

View File

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

View File

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

View File

@ -1,3 +1,5 @@
#include <stdint.h>
#include <tianole/arch.h>
#include <tianole/early_log.h>
#include <tianole/kernel_init.h>
@ -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");
}

View File

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

21
scripts/checks/page-fault.sh Executable file
View File

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