summaryrefslogtreecommitdiff
path: root/docs/specs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/specs')
-rw-r--r--docs/specs/cpp_runtime.md55
-rw-r--r--docs/specs/fdt.md191
-rw-r--r--docs/specs/pmm.md209
-rw-r--r--docs/specs/slab.md233
-rw-r--r--docs/specs/vmm.md158
5 files changed, 846 insertions, 0 deletions
diff --git a/docs/specs/cpp_runtime.md b/docs/specs/cpp_runtime.md
new file mode 100644
index 0000000..e3ebd90
--- /dev/null
+++ b/docs/specs/cpp_runtime.md
@@ -0,0 +1,55 @@
+# C++ 内核运行时 (C++ Kernel Runtime)
+
+## 1. 目标 (Objective)
+
+为 LumeOS 内核提供最小化的 C++ ABI 存根函数。在 `-fno-exceptions -fno-rtti -nostdlib` 环境下,编译器和链接器仍会引用若干 C++ 运行时符号(如纯虚函数陷阱、全局析构注册、DSO handle)。本模块提供这些符号的内核实现,防止链接失败。
+
+**职责边界:**
+- `operator new/delete` 由 Slab 分配器模块 (`mm/slab.cc`) 提供,**不在本模块**。
+- Placement new 由 `include/lume/new.h` 提供,**不在本模块**。
+- 本模块仅提供 ABI 存根和编译器辅助符号。
+
+## 2. 前置依赖 (Dependencies)
+
+- **Panic 机制 (`lib/panic.cc`):** 依赖 `kernel_panic()` 在遇到不可恢复错误时终止系统。
+- **项目约定 (`00_conventions.md`):** 遵循 `-fno-exceptions -fno-rtti` 编译约束。
+
+## 3. 核心数据结构 (Data Structures)
+
+无。本模块仅实现全局函数和符号。
+
+## 4. 公共 API (Public APIs)
+
+这些符号由编译器/链接器隐式引用,内核代码不应直接调用。
+
+```cpp
+// 定义于 lib/cxx_abi.cc
+
+// 当代码通过基类指针调用未实现的纯虚函数时,
+// 编译器生成的 vtable 条目会引用此符号。
+// 这是不可恢复的编程错误。
+extern "C" void __cxa_pure_virtual();
+
+// 全局对象析构注册。内核不支持全局对象析构(全局对象
+// 通过 Placement New 手动管理生命周期)。若编译器生成
+// 对此符号的调用,说明代码中存在违规的全局对象。
+extern "C" int __cxa_atexit(void (*func)(void*), void* arg, void* dso_handle);
+
+// DSO handle — 链接器在某些情况下引用此符号
+// (与 __cxa_atexit 配合标识动态共享对象)。
+// 内核为静态链接,此符号仅需存在即可。
+extern "C" void* __dso_handle;
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+无。所有函数要么立即 panic,要么是静态符号。
+
+## 6. 错误路径 (Error Paths)
+
+- `__cxa_pure_virtual()` → 立即 `kernel_panic("pure virtual function call")`。
+- `__cxa_atexit()` → 立即 `kernel_panic("__cxa_atexit called in kernel")`。
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+无复杂时序。所有路径均为:检测到违规 → panic。
diff --git a/docs/specs/fdt.md b/docs/specs/fdt.md
new file mode 100644
index 0000000..2a1d542
--- /dev/null
+++ b/docs/specs/fdt.md
@@ -0,0 +1,191 @@
+# 设备树 (Flattened Device Tree)
+
+## 1. 目标 (Objective)
+
+为 LumeOS 提供一套标准的硬件发现与配置机制,彻底摆脱硬编码的物理地址。本模块负责解析由 Bootloader 传入的 FDT 二进制数据,并分两个阶段将其转化为内核可用的数据结构:
+
+1. **早期扫描 (Early Scan):** 在内存管理(PMM)初始化之前,以只读、无分配的方式扫描 FDT,提取启动所必需的核心信息(如物理内存布局)。
+2. **对象化展开 (Unflattening):** 在内存管理(PMM/Slab)就绪后,将完整的 FDT 解析为一个常驻内存的 C++ `DeviceNode` 对象树,为后续的驱动探测(Driver Probing)提供面向对象的接口。
+
+## 2. 前置依赖 (Dependencies)
+
+- **引导协议契约 (`linker_and_entry.md`):** 依赖 Bootloader 在跳转至内核时,将 FDT 的物理基址置于 `a1` 寄存器。
+- **全局初始化契约 (`global_init.md`):** `fdt_init()` 在 `console_init()` 之后、`pmm_init()` 之前被 BSP 调用。`pmm_init()` 自身会回调 FDT 的早期扫描接口。
+- **内存管理契约 (`pmm.md`, `slab.md`):** 对象化展开阶段依赖 Slab 分配器(`operator new`)来动态创建 `DeviceNode` 对象。
+- **项目约定 (`00_conventions.md`):** 遵循所有命名、错误处理和 API 设计规范。
+
+## 3. 核心数据结构 (Data Structures)
+
+### 3.1 FDT 属性 (`struct FdtProperty`)
+
+表示设备节点中的一个属性(如 `reg`, `compatible`)。
+
+```cpp
+// 定义于 include/lume/fdt_prop.h
+struct FdtProperty {
+ const char* name; // 属性名
+ const void* value; // 指向 FDT blob 中原始数据的指针
+ int len; // 数据长度(字节)
+ list_node link; // 链接到下一个属性
+};
+```
+
+### 3.2 设备节点 (`class DeviceNode`)
+
+FDT 对象树的核心,代表一个硬件设备或总线。
+
+```cpp
+// 定义于 include/lume/device_node.h
+class DeviceNode {
+public:
+ // 构造/析构
+ DeviceNode(DeviceNode* parent, const char* name);
+ ~DeviceNode();
+
+ // 属性访问
+ const FdtProperty* get_prop(const char* name) const;
+ uint32_t get_prop_u32(const char* name, uint32_t default_val = 0) const;
+ uint64 get_prop_u64(const char* name, uint64 default_val = 0) const;
+ const char* get_prop_string(const char* name) const;
+
+ // 子节点遍历
+ DeviceNode* find_child(const char* name);
+ void for_each_child(void (*callback)(DeviceNode*));
+
+ // 内部接口,供 FdtManager 构建树时使用
+ void add_child(DeviceNode* child);
+ void add_prop(FdtProperty* prop);
+
+private:
+ const char* name_;
+ DeviceNode* parent_;
+ list_node children_list_; // list_head of children
+ list_node props_list_; // list_head of properties
+ list_node sibling_link_; // link in parent's children_list_
+};
+```
+
+### 3.3 FDT 管理器 (`class FdtManager`)
+
+全局单例,负责 FDT 的解析与查询。
+
+```cpp
+// 定义于 include/lume/fdt.h
+class FdtManager {
+public:
+ // 由 fdt_init() 通过 Placement New 调用
+ FdtManager(uint64 fdt_paddr);
+
+ // 阶段二:对象化展开
+ void unflatten();
+
+ // 驱动探测接口
+ DeviceNode* find_compatible(const char* compat_string);
+ DeviceNode* get_node_by_path(const char* path);
+
+ // 阶段一:早期扫描接口 (静态,无状态)
+ static void early_scan_mem(uint64 fdt_paddr, uint64* base, uint64* size);
+ static void early_scan_uart(uint64 fdt_paddr, uint64* uart_addr);
+
+private:
+ uint64 fdt_paddr_;
+ DeviceNode* root_ = nullptr;
+ bool is_unflattened_ = false;
+};
+
+// 全局 FDT 管理器实例指针
+extern FdtManager* g_fdt;
+```
+
+## 4. 公共 API (Public APIs)
+
+```cpp
+// === 初始化 API (由 kernel_main 调用) ===
+
+// 阶段一和阶段二的入口。仅保存 FDT 地址,真正的解析被推迟。
+void fdt_init(uint64 fdt_paddr);
+
+// 在 Slab 就绪后调用,执行对象化展开。
+void fdt_unflatten();
+
+
+// === 驱动探测 API (供各驱动 init 函数调用) ===
+
+// 根据 "compatible" 字符串查找第一个匹配的设备节点。
+// 返回值:成功则为 DeviceNode 指针,失败为 nullptr。
+DeviceNode* fdt_find_compatible(const char* compat_string);
+
+// 根据完整路径查找设备节点。
+DeviceNode* fdt_get_node_by_path(const char* path);
+
+
+// === 早期扫描 API (供 PMM 和早期控制台调用) ===
+
+// 扫描 FDT blob 获取 /memory 节点的 base 和 size。
+// 失败则 panic。
+void fdt_early_get_mem_info(uint64* base, uint64* size);
+
+// 扫描 FDT blob 获取 /chosen/stdout-path 对应的 UART 物理地址。
+void fdt_early_get_uart_info(uint64* uart_addr);
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+- **无锁设计:** FDT 的所有初始化和解析操作(`fdt_init`, `fdt_unflatten`)均由 BSP 在多核启动前的**绝对单核期**完成。
+
+- **只读安全:** 一旦 `fdt_unflatten()` 完成,`DeviceNode` 对象树即被视为**全局只读**数据。任何 CPU 都可以随时无锁访问它进行驱动探测和属性查询。
+
+- **禁止修改:** 任何模块在初始化之后都**不得**修改 `DeviceNode` 树的内容。
+
+## 6. 错误路径 (Error Paths)
+
+- **FDT 损坏:** 在 `fdt_init()` 或 `fdt_unflatten()` 中,如果检测到 FDT 的 magic number 无效或结构损坏,必须立即调用 `kernel_panic()`。
+
+- **关键节点缺失:** `fdt_early_get_mem_info()` 如果找不到 `/memory` 节点,说明无法确定物理内存大小,属于致命错误,必须 `kernel_panic()`。
+
+- **内存分配失败:** `fdt_unflatten()` 在使用 `new` 创建 `DeviceNode` 时如果 Slab 返回 `nullptr`,说明内存耗尽,必须 `kernel_panic()`。
+
+- **查询失败:** `fdt_find_compatible()` 或 `fdt_get_node_by_path()` 在未找到匹配节点时,安全地返回 `nullptr`。调用方(驱动)负责处理此情况(通常意味着该硬件不存在,驱动不进行初始化)。
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### FDT 二阶段解析时序
+
+```
+[entry.S] -> a1 = fdt_paddr
+ │
+ ▼
+[kernel_main on BSP]
+ │
+ ├─> 1. fdt_init(fdt_paddr)
+ │ └─ g_fdt = new FdtManager(fdt_paddr); // Placement New
+ │ (仅保存地址,不进行任何解析)
+ │
+ ├─> 2. pmm_init()
+ │ └─ fdt_early_get_mem_info(&base, &size);
+ │ └─ FdtManager::early_scan_mem(fdt_paddr, ...);
+ │ ├─ 遍历原始 FDT blob (无内存分配)
+ │ ├─ 找到 /memory 节点
+ │ └─ 返回 reg 属性值
+ │
+ ├─> 3. slab_init()
+ │ └─ operator new 可用
+ │
+ ├─> 4. (稍后) fdt_unflatten()
+ │ └─ g_fdt->unflatten();
+ │ ├─ 再次遍历原始 FDT blob
+ │ ├─ for each node: new DeviceNode(...)
+ │ ├─ for each prop: new FdtProperty(...)
+ │ └─ 构建完整的 C++ 对象树
+ │
+ ▼
+[驱动初始化阶段, e.g., plic_init()]
+ │
+ ├─> node = fdt_find_compatible("riscv,plic0");
+ │ └─ g_fdt->find_compatible(...);
+ │ └─ 遍历已构建的 DeviceNode C++ 对象树 (只读, 无锁)
+ │
+ ├─> reg_prop = node->get_prop("reg");
+ │
+ └─> (使用 reg_prop->value 初始化 PLIC 驱动)
+``` \ No newline at end of file
diff --git a/docs/specs/pmm.md b/docs/specs/pmm.md
new file mode 100644
index 0000000..5703e7a
--- /dev/null
+++ b/docs/specs/pmm.md
@@ -0,0 +1,209 @@
+# 物理内存管理器 (Physical Memory Manager)
+
+## 1. 目标 (Objective)
+
+为 LumeOS 提供对物理内存页帧(Frame)的分配与回收服务。本模块基于伙伴系统(Buddy System)算法管理所有可用物理内存,并引入 Per-CPU 缓存(PCP)机制以降低多核场景下的锁竞争。PMM 是 Slab 分配器和 VMM 的底层基石。
+
+## 2. 前置依赖 (Dependencies)
+
+- **全局初始化契约 (`global_init.md`):** `pmm_init()` 在 `fdt_init()` 之后、`slab_init()` 之前被 BSP 调用。
+- **设备树契约 (`fdt.md`):** 依赖 `fdt_early_get_mem_info()` 获取物理内存的基址和大小。
+- **并发同步契约 (`spinlock.md`):** 依赖 `Spinlock` 实现对全局伙伴系统的互斥访问。
+- **架构抽象契约 (`arch_abstraction.md`):** 依赖 `arch::cpu::id()` 获取当前 CPU 编号以访问 Per-CPU 缓存;依赖 `arch::cpu::intr_off()/intr_on()` 保护 PCP 操作免受中断重入。
+- **项目约定 (`00_conventions.md`):** 遵循所有命名、错误处理和 API 设计规范。
+
+## 3. 核心数据结构 (Data Structures)
+
+### 3.1 物理页帧描述符 (`struct Frame`)
+
+为了极致地压缩元数据内存占用,`struct Frame` 采用 `union` 设计,并严格保证 `refcount` 独立于 `union` 之外,以支持 COW。
+
+```cpp
+// 定义于 include/lume/frame.h
+#include <lume/types.h>
+#include <lume/list.h>
+
+class KmemCache; // 前向声明
+
+enum class FrameState : uint8 {
+ Free, // 空闲,位于 Buddy 或 PCP 链表中
+ Allocated, // 已分配给上层(Slab, VMM)
+ Slab, // 作为 Slab 页使用
+};
+
+struct Frame {
+ uint32 refcount; // 原子引用计数(独立于 union)
+ FrameState state; // 页帧当前状态
+ uint8 order; // 伙伴系统中的阶数 (0-10 for 4KB-4MB)
+
+ union {
+ // 当 state == FrameState::Free 时
+ struct {
+ list_node free_link; // 用于挂载到 Buddy 或 PCP 的空闲链表
+ } free;
+
+ // 当 state == FrameState::Slab 时
+ struct {
+ KmemCache* cache; // 指向所属的 Slab 缓存
+ uint32 obj_count; // 已分配对象数量
+ } slab;
+ };
+};
+```
+
+### 3.2 Per-CPU 单页缓存 (`struct PerCpuCache`)
+
+每个 CPU 私有的单页帧空闲链表。**访问 PCP 时必须关闭中断**,防止定时器中断处理函数在同一 CPU 上重入 PCP 操作导致链表损坏。
+
+```cpp
+// 定义于 mm/pmm.cc (模块私有)
+inline constexpr int kPcpHighWatermark = 64; // PCP 缓存上限
+inline constexpr int kPcpBatchSize = 32; // 每次 refill/drain 的批量大小
+
+struct PerCpuCache {
+ list_node free_list; // 单页帧空闲链表头
+ uint32 count; // 当前缓存的页帧数量
+};
+```
+
+### 3.3 伙伴系统常量
+
+```cpp
+// 定义于 include/lume/pmm.h
+inline constexpr int kMaxOrder = 11; // 阶数 0..10 → 4KB..4MB
+```
+
+## 4. 公共 API (Public APIs)
+
+所有分配函数在失败时返回 `nullptr`。
+
+```cpp
+// === 初始化 ===
+void pmm_init(); // BSP 调用,从 FDT 发现内存并初始化伙伴系统
+
+// === 单页帧操作 (Fast Path via PCP) ===
+Frame* pmm_alloc_frame(); // 分配一个 4KB 页帧
+void pmm_free_frame(Frame* f); // 释放一个 4KB 页帧
+
+// === 多页帧操作 (Slow Path, 直接伙伴系统) ===
+Frame* pmm_alloc_frames(uint8 order); // 分配 2^order 个连续页帧
+void pmm_free_frames(Frame* f, uint8 order); // 释放 2^order 个连续页帧
+
+// === 引用计数操作 (原子, 无锁) ===
+void frame_incref(Frame* f); // 原子递增引用计数
+void frame_decref(Frame* f); // 原子递减,降至 0 时自动释放
+
+// === 地址转换 ===
+uint64 frame_to_pa(const Frame* f); // Frame* → 物理地址
+Frame* pa_to_frame(uint64 pa); // 物理地址 → Frame*
+uint64 frame_to_va(const Frame* f); // Frame* → 内核虚拟地址
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+- **持有锁:** 模块内部持有一把 `Spinlock` (`g_pmm_lock`)。
+
+- **锁的作用域:** `g_pmm_lock` **仅保护**全局伙伴系统 (`g_free_areas[]`)。
+
+- **PCP 保护机制:** PCP 操作**必须在中断关闭状态下**执行。这不是"无锁"——而是通过关中断防止同一 CPU 上的重入(定时器中断 → IRQ handler → pmm_alloc_frame → 损坏 PCP 链表)。关中断由 `pmm_alloc_frame()`/`pmm_free_frame()` 内部自动管理。
+
+- **加锁慢速路径:**
+ - 当 PCP 为空时,`refill_pcp()` 获取 `g_pmm_lock` 从伙伴系统批量取页。
+ - 当 PCP 达到 `kPcpHighWatermark` 时,`drain_pcp()` 获取 `g_pmm_lock` 归还半数页帧。
+ - `pmm_alloc_frames()` 和 `pmm_free_frames()` 因直接操作伙伴系统,**始终需要**获取 `g_pmm_lock`。
+
+- **原子操作:** `frame_incref()` / `frame_decref()` 使用 `__atomic_fetch_add/sub` + `__ATOMIC_ACQ_REL`。
+
+## 6. 错误路径 (Error Paths)
+
+- **初始化失败:** `pmm_init()` 若无法从 FDT 找到可用内存,或无法为 `frame_map` 分配空间,则 `kernel_panic()`。
+
+- **分配失败:** `pmm_alloc_*()` 在物理内存耗尽时返回 `nullptr`。上层负责处理。
+
+- **释放错误:** `pmm_free_*()` 若接收到 `nullptr`,立即 `kernel_panic()`。
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### 7.1 单页分配时序 (Fast & Slow Path)
+
+```
+[pmm_alloc_frame() on CPU X]
+ │
+ ├─> 1. 关闭中断 (防止同 CPU 重入)
+ │
+ ├─> 2. 获取 pcp = &g_pcp[cpu_id]
+ │
+ ├─> 3. if (pcp->count > 0) ?
+ │ ├── [是 (Fast Path)]
+ │ │ ├─ 3.1 从 pcp->free_list 弹出一个 Frame
+ │ │ ├─ 3.2 设置 state=Allocated, refcount=1
+ │ │ └─ 3.3 恢复中断, return Frame
+ │ │
+ │ └── [否 (Slow Path)]
+ │ ├─ 4.1 LockGuard guard(g_pmm_lock);
+ │ ├─ 4.2 refill_pcp(pcp); // 批量从 Buddy 取 kPcpBatchSize 页
+ │ ├─ 4.3 释放锁
+ │ └─ 4.4 回到步骤 3 重试
+ │
+ ▼
+[恢复中断, 返回 Frame* 或 nullptr]
+```
+
+### 7.2 初始化时序
+
+```
+[pmm_init() — BSP 单核期]
+ │
+ ├─> 1. fdt_early_get_mem_info(&base, &size)
+ │ └─ 获取物理内存基址和大小
+ │
+ ├─> 2. 计算 frame_map 位置 (紧跟 _kernel_end 之后, 页对齐)
+ │ └─ frame_map 大小 = num_frames * sizeof(Frame)
+ │
+ ├─> 3. 清零 frame_map
+ │
+ ├─> 4. 初始化 Spinlock 和 Buddy 空闲链表头
+ │
+ ├─> 5. 初始化所有 PCP 链表头
+ │
+ └─> 6. 将所有空闲页帧 (frame_map 之后) 按最大对齐阶数插入 Buddy
+```
+
+### 7.3 写时复制 (COW) 引用计数流程
+
+```
+[父进程 fork() 创建子进程]
+ │
+ ├─> 1. 遍历父进程所有可写 VMA
+ │
+ ├─> 2. for each VMA:
+ │ ├─ 2.1 复制父进程的 PTE 到子进程页表
+ │ ├─ 2.2 清除父、子 PTE 的 'Write' 权限位
+ │ ├─ 2.3 设置父、子 PTE 的 'COW' 软件标志位
+ │ └─ 2.4 frame_incref(pa_to_frame(PTE->paddr));
+ │
+ ▼
+[子进程尝试写入共享页面]
+ │
+ ├─> 1. 触发 Store Page Fault
+ │
+ ├─> 2. VMM 陷阱处理:
+ │ ├─ 2.1 检查 PTE,确认是合法的 COW Fault
+ │ ├─ 2.2 old_frame = pa_to_frame(PTE->paddr)
+ │ ├─ 2.3 if (old_frame->refcount == 1) ?
+ │ │ ├── [是] 直接恢复 Write 权限,无需拷贝
+ │ │ └── [否] 分配新页, 拷贝, 修改 PTE, frame_decref(old)
+ │
+ ▼
+[返回用户态,写操作成功]
+```
+
+## 8. 已知缺陷与未来挑战 (Known Weaknesses & Future Challenges)
+
+### 8.1 内存回收
+
+当前 API 在物理内存耗尽时仅返回 `nullptr`,缺乏主动回收机制。未来需引入内存回收器。
+
+### 8.2 frame_map 存储
+
+当前设计假设单一连续 `frame_map` 数组,适用于 QEMU 线性内存。对于多内存区域的真实硬件,需演进为稀疏内存模型。
diff --git a/docs/specs/slab.md b/docs/specs/slab.md
new file mode 100644
index 0000000..dc6953a
--- /dev/null
+++ b/docs/specs/slab.md
@@ -0,0 +1,233 @@
+# SLUB 分配器 (SLUB Allocator)
+
+## 1. 目标 (Objective)
+
+在物理内存管理器 (PMM) 之上,为内核提供高效的字节级内存分配器。采用 **SLUB** 设计(Simple List of Unfull Blocks),通过复用 PMM 的 `Frame` 描述符存储 per-page 元数据、将空闲链表嵌入对象体内、以及 Per-CPU 活跃页机制,实现低碎片、低锁竞争的小对象分配。本模块是内核 `operator new` 的直接后端。
+
+**为何选择 SLUB 而非经典 Slab:**
+- `Frame` 结构已包含 `cache` 指针和 `obj_count` 字段,无需额外的 per-page `struct Slab` 头部。
+- 省去 full/partial/empty 三链表管理,仅需一个 partial 链表。
+- Per-CPU 缓存仅需一个"当前活跃页"指针,无需动态分配对象数组(避免 slab_init 的鸡生蛋问题)。
+- Linux 自 2.6.23 起默认 SLUB,6.8 已完全移除经典 Slab。
+
+## 2. 前置依赖 (Dependencies)
+
+- **全局初始化契约 (`global_init.md`):** `slab_init()` 在 `pmm_init()` 之后、`vmm_init()` 之前被 BSP 调用。
+- **物理内存契约 (`pmm.md`):** 依赖 `pmm_alloc_frame()` / `pmm_free_frame()` 以及 `Frame` 描述符中的 `slab.cache` 和 `slab.obj_count` 字段。还依赖 `pa_to_frame()` / `frame_to_pa()` 进行地址转换。
+- **并发同步契约 (`spinlock.md`):** 依赖 `Spinlock` 保护全局 partial 链表。
+- **架构抽象契约 (`arch_abstraction.md`):** 依赖 `arch::cpu::id()` 访问 Per-CPU 缓存;依赖 `arch::cpu::intr_off()/intr_on()` 保护 Per-CPU 操作免受中断重入。
+- **项目约定 (`00_conventions.md`):** 遵循所有命名和类型规范。
+
+## 3. 核心数据结构 (Data Structures)
+
+### 3.1 空闲对象链表(嵌入式)
+
+空闲对象的前 `sizeof(void*)` 字节存储下一个空闲对象的指针。对象被分配后,该区域归用户使用。因此**最小对象尺寸 = `sizeof(void*)` = 8 字节**。
+
+```
+[ Free Object ] [ Free Object ] [ Free Object ]
++-------------+ +-------------+ +-------------+
+| next ---------.------->| next ---------.------->| next = NULL |
+| (unused) | | (unused) | | (unused) |
++-------------+ +-------------+ +-------------+
+```
+
+### 3.2 Frame 描述符中的 Slab 元数据
+
+复用 `include/lume/frame.h` 中已有的字段(无需新增结构体):
+
+```cpp
+// 当 Frame.state == FrameState::Slab 时有效
+struct {
+ KmemCache* cache; // 此页属于哪个 KmemCache
+ uint32 obj_count; // 此页上已分配的对象数量
+} slab;
+```
+
+- 此外,需要在 `Frame` 的 `free` union 成员中增加一个 `void* freelist` 字段,或者直接将 freelist 头指针嵌入 union。为保持 Frame 结构简洁,**freelist 头指针存储在页面固定偏移处(页起始的前 8 字节)**,不侵入 Frame 结构。
+
+**修正方案:** 在 `Frame` 的 `slab` union 成员中增加 `void* freelist` 字段:
+
+```cpp
+struct {
+ KmemCache* cache; // 所属缓存
+ uint32 obj_count; // 已分配对象数
+ void* freelist; // 页内空闲对象链表头
+} slab;
+```
+
+### 3.3 Per-CPU 活跃页
+
+每个 CPU 对每个 KmemCache 维护一个"当前活跃页"(cpu_slab)。分配时优先从此页的 freelist 弹出对象,无需获取全局锁。
+
+```cpp
+// 定义于 mm/slab.h (模块私有)
+struct PerCpuSlab {
+ Frame* active; // 当前 CPU 正在使用的 slab 页(可能为 nullptr)
+};
+```
+
+### 3.4 内核内存缓存 (`class KmemCache`)
+
+```cpp
+// 定义于 mm/slab.h
+class KmemCache {
+public:
+ KmemCache() = default;
+ void init(const char* name, uint32 obj_size, uint32 obj_align);
+
+ void* alloc();
+ void free(void* obj);
+
+private:
+ Frame* get_partial(); // 从 partial 链表取一个页
+ void put_partial(Frame* f); // 将页放入 partial 链表
+ Frame* new_slab(); // 从 PMM 申请新页并格式化
+
+ Spinlock lock_; // 保护 partial 链表
+ list_node partial_; // 部分填充的 slab 页链表
+
+ PerCpuSlab cpu_slab_[kMaxCPUs]; // Per-CPU 活跃页
+
+ const char* name_ = "uninit";
+ uint32 obj_size_ = 0; // 对齐后的对象尺寸
+ uint32 obj_align_ = 0; // 对齐要求
+ uint32 objs_per_slab_ = 0; // 每页可容纳的对象数
+};
+```
+
+### 3.5 通用尺寸缓存
+
+```cpp
+// 预创建的 2^N 尺寸缓存: 8, 16, 32, 64, 128, 256, 512, 1024, 2048
+inline constexpr int kNumSizeClasses = 9;
+inline constexpr uint32 kSizeClasses[kNumSizeClasses] = {
+ 8, 16, 32, 64, 128, 256, 512, 1024, 2048
+};
+
+extern KmemCache g_size_caches[kNumSizeClasses];
+```
+
+## 4. 公共 API (Public APIs)
+
+```cpp
+// === 初始化 ===
+void slab_init(); // 初始化所有通用尺寸缓存
+
+// === 对象缓存 API ===
+void kmem_cache_init(KmemCache* cache, const char* name,
+ uint32 size, uint32 align);
+void* kmem_cache_alloc(KmemCache* cache);
+void kmem_cache_free(KmemCache* cache, void* obj);
+
+// === 通用分配 API ===
+void* kmalloc(uint32 size); // 自动选择尺寸缓存
+void kfree(void* ptr); // 通过 Frame 反查 cache,自动归还
+
+// === C++ Runtime ===
+void* operator new(size_t size);
+void* operator new[](size_t size);
+void operator delete(void* ptr) noexcept;
+void operator delete[](void* ptr) noexcept;
+void operator delete(void* ptr, size_t) noexcept;
+void operator delete[](void* ptr, size_t) noexcept;
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+- **持有锁:** 每个 `KmemCache` 持有一把 `Spinlock lock_`。
+
+- **锁的作用域:** `lock_` **仅保护** `partial_` 链表。
+
+- **Per-CPU 保护:** 访问 `cpu_slab_[cpu_id]` 时必须关闭中断(同 PMM 的 PCP 策略),防止中断重入。
+
+- **快速路径(关中断,无锁):** 从当前 CPU 的 `active` 页的 `freelist` 弹出/压入对象。
+
+- **慢速路径(需要锁):**
+ - alloc:当 `active` 页无空闲对象时,获取 `lock_` 从 `partial_` 取页(或调用 PMM 新建)。
+ - free:当归还对象使一个页变为完全空闲时,获取 `lock_` 归还 PMM。
+
+- **与 PMM 交互:** `new_slab()` 调用 `pmm_alloc_frame()` 时**不持有** `lock_`(先释放锁,申请页,再获取锁挂入链表),避免 Slab lock → PMM lock 嵌套。
+
+## 6. 错误路径 (Error Paths)
+
+- **初始化失败:** `slab_init()` 若 PMM 无法提供页面,`kernel_panic()`。
+
+- **分配失败:** `kmalloc()` / `kmem_cache_alloc()` 在 OOM 时返回 `nullptr`。
+
+- **释放错误:** `kfree(nullptr)` 为 no-op。传入非法指针(不属于任何 slab 页)时 `kernel_panic()`。
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### 7.1 `kmem_cache_alloc` 时序
+
+```
+[kmem_cache_alloc(cache) on CPU X]
+ │
+ ├─> 1. 关中断
+ ├─> 2. active = cache->cpu_slab_[X].active
+ │
+ ├─> 3. if (active && active->slab.freelist != NULL) ?
+ │ ├── [是 (Fast Path)]
+ │ │ ├─ obj = active->slab.freelist
+ │ │ ├─ active->slab.freelist = *(void**)obj
+ │ │ ├─ active->slab.obj_count++
+ │ │ ├─ 恢复中断
+ │ │ └─ return obj
+ │ │
+ │ └── [否 (Slow Path)]
+ │ ├─ 4.1 将 active (若满) 从 cpu_slab 移出
+ │ ├─ (满页不需要跟踪,但其 Frame 中 cache 指针仍有效)
+ │ ├─ 4.2 恢复中断
+ │ ├─ 4.3 LockGuard guard(cache->lock_)
+ │ ├─ 4.4 new_active = get_partial() // 从 partial 链表取
+ │ │ └─ 若无,释放锁 → new_slab() → 重新获取锁
+ │ ├─ 4.5 释放锁
+ │ ├─ 4.6 关中断
+ │ ├─ 4.7 cache->cpu_slab_[X].active = new_active
+ │ ├─ 4.8 从 new_active->slab.freelist 弹出 obj
+ │ ├─ 4.9 恢复中断
+ │ └─ return obj
+ │
+ ▼
+[返回 obj* 或 nullptr]
+```
+
+### 7.2 `kfree` 时序
+
+```
+[kfree(ptr)]
+ │
+ ├─> 1. pa = ptr 的物理地址 (对于 identity/higher-half,减去偏移)
+ ├─> 2. frame = pa_to_frame(pa & ~(kPageSize-1))
+ ├─> 3. cache = frame->slab.cache
+ │
+ ├─> 4. 关中断
+ ├─> 5. 将 obj 压入 frame->slab.freelist
+ ├─> 6. frame->slab.obj_count--
+ │
+ ├─> 7. if (obj_count == 0) ?
+ │ ├── [是:页完全空闲]
+ │ │ ├─ 若此页是当前 CPU 的 active,清除 active
+ │ │ ├─ 恢复中断
+ │ │ ├─ LockGuard: 从 partial_ 链表移除 (如果在链表中)
+ │ │ └─ pmm_free_frame(frame) // 归还 PMM
+ │ │
+ │ └── [否]
+ │ ├─ 若此页不是任何 CPU 的 active 且不在 partial,
+ │ │ 则需获取锁放入 partial (满→部分满的过渡)
+ │ └─ 恢复中断
+ │
+ ▼
+[完成]
+```
+
+## 8. 已知缺陷与未来挑战
+
+### 8.1 缓存收缩
+
+当前不主动收缩 partial 链表。未来可增加 `kmem_cache_shrink()` 供内存回收线程调用。
+
+### 8.2 大对象分配
+
+大于 2048 字节的分配直接退化为 `pmm_alloc_frames()`。当前由 `kmalloc` 透明处理。
diff --git a/docs/specs/vmm.md b/docs/specs/vmm.md
new file mode 100644
index 0000000..7492a02
--- /dev/null
+++ b/docs/specs/vmm.md
@@ -0,0 +1,158 @@
+# 虚拟内存管理器 (Virtual Memory Manager)
+
+## 1. 目标 (Objective)
+
+为 LumeOS 管理虚拟地址空间。本模块分两个阶段实现:
+
+- **Phase 1(本文档重点):**
+ 1. 在 `vmm_init()` 中建立精细化的内核页表(4KB 粒度、精确权限)。
+ 2. 实现基于 Rbtree 和 Slab 的 VMA (Virtual Memory Area) 区间树以管理用户态内存。
+ 3. 实现架构无关的按需分页 (Demand Paging) 模拟接口。
+- **Phase 2(未来):** 将 VMM 的按需分页逻辑绑定到真实的 Trap 异常中断系统,并引入进程 (Process/Task) 关联和基于硬件触发的缺页异常处理。
+
+采用 pmap(Physical Map)分层模型:MI 层管理地址空间布局逻辑,MD 层(`arch/riscv/mm/pmap.cc`)封装 SV39 页表硬件操作。
+
+## 2. 前置依赖 (Dependencies)
+
+- **全局初始化契约 (`global_init.md`):** `vmm_init()` 在 `slab_init()` 和 `fdt_unflatten()` 之后、`trap_init()` 之前被 BSP 调用。
+- **物理内存契约 (`pmm.md`):** 依赖 `pmm_alloc_frame()` 分配页表页。
+- **Slab 分配器契约 (`slab.md`):** Phase 2 依赖 `kmalloc` 创建 VMA 对象。
+- **架构抽象契约 (`arch_abstraction.md`):** 依赖 `arch::mmu::set_page_table()`, `arch::mmu::flush_tlb_all()` 切换和刷新页表。
+- **链接脚本契约 (`linker_and_entry.md`):** 依赖 `_stext/_etext`, `_srodata/_erodata`, `_sdata/_edata`, `_sbss/_ebss`, `_kernel_end` 确定各段边界。
+
+## 3. 核心数据结构 (Data Structures)
+
+### 3.1 SV39 页表项 (PTE) 格式
+
+```
+63 54 53 28 27 19 18 10 9 8 7 6 5 4 3 2 1 0
++---------+----------+----------+----------+----+-+-+-+-+-+-+-+-+
+| Reserved| PPN[2] | PPN[1] | PPN[0] |RSW |D|A|G|U|X|W|R|V|
+| (10) | (26) | (9) | (9) |(2) |1|1|1|1|1|1|1|1|
++---------+----------+----------+----------+----+-+-+-+-+-+-+-+-+
+```
+
+权限位常量:
+
+```cpp
+namespace pmap {
+ inline constexpr uint64 PTE_V = 1 << 0; // Valid
+ inline constexpr uint64 PTE_R = 1 << 1; // Read
+ inline constexpr uint64 PTE_W = 1 << 2; // Write
+ inline constexpr uint64 PTE_X = 1 << 3; // Execute
+ inline constexpr uint64 PTE_U = 1 << 4; // User-accessible
+ inline constexpr uint64 PTE_G = 1 << 5; // Global
+ inline constexpr uint64 PTE_A = 1 << 6; // Accessed
+ inline constexpr uint64 PTE_D = 1 << 7; // Dirty
+}
+```
+
+### 3.2 pmap 接口 (MD 层, namespace 函数)
+
+遵循 `00_conventions.md` 的多态策略——pmap 为极热路径,使用 namespace 函数 + 编译期链接。
+
+```cpp
+// 定义于 arch/riscv/mm/pmap.h (arch 私有头) 和 mm/pmap.h (MI 接口)
+namespace pmap {
+ // 分配并初始化一个新的根页表,返回其物理地址。
+ // 失败返回 0。
+ uint64 create();
+
+ // 销毁一个页表及其所有子页表页。
+ void destroy(uint64 root_pa);
+
+ // 建立映射:va -> pa,权限为 perm。
+ // 自动分配中间页表页。
+ // 返回 0 成功,负错误码失败。
+ int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm);
+
+ // 解除 va 处的映射。
+ void unmap(uint64 root_pa, uint64 va);
+
+ // 查询 va 对应的 pa。成功返回 true 并写入 *pa_out。
+ bool lookup(uint64 root_pa, uint64 va, uint64* pa_out);
+
+ // 将 root_pa 写入 satp 并刷新 TLB。
+ void activate(uint64 root_pa);
+}
+```
+
+### 3.3 内核地址空间布局
+
+`vmm_init()` 建立的精细内核映射:
+
+```
+Virtual Address Physical Address Perm Description
+─────────────────────────────────────────────────────────────────────
+arch::kVAOffset + 0x80200000 0x80200000 R-X .text (代码段)
+arch::kVAOffset + _srodata _srodata_pa R-- .rodata (只读数据)
+arch::kVAOffset + _sdata _sdata_pa RW- .data + .bss
+arch::kVAOffset + frame_map_pa frame_map_pa RW- PMM frame_map 数组
+arch::kVAOffset + 0x10000000 0x10000000 RW- UART MMIO
+arch::kVAOffset + 0x0C000000 0x0C000000 RW- PLIC MMIO
+arch::kVAOffset + mem_end ... RW- 剩余物理内存 (到 mem_end)
+```
+
+## 4. 公共 API (Public APIs)
+
+### 4.1 Phase 1 API
+
+```cpp
+// 初始化内核页表,替代 early_pgdir。
+// 1. 分配新根页表
+// 2. 按段权限映射内核各段
+// 3. 映射 MMIO 区域
+// 4. 激活新页表
+// 5. 旧的 early_pgdir 恒等映射自动失效
+void vmm_init();
+
+// AP 切换到 BSP 建好的内核页表
+void vmm_init_ap();
+```
+
+### 4.2 Phase 2 API (未来)
+
+```cpp
+// 暂为 stub,Phase 2 实现
+int vmm_map_user(uint64 root_pa, uint64 va, uint64 len, uint64 perm);
+int vmm_unmap_user(uint64 root_pa, uint64 va, uint64 len);
+int vmm_handle_page_fault(uint64 root_pa, uint64 fault_addr, uint64 cause);
+uint64 vmm_clone(uint64 parent_root_pa);
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+- **Phase 1:** `vmm_init()` 在 BSP 单核期执行,无需锁。
+- **pmap 锁:** 每个根页表关联一把 Spinlock(Phase 2 在 AddressSpace 结构中)。多核操作同一页表必须持有此锁。
+- **TLB Shootdown:** Phase 1 仅有内核页表且在单核期建立,无需 shootdown。Phase 2 需实现。
+
+## 6. 错误路径 (Error Paths)
+
+- `pmap::create()` 或 `pmap::map()` 中 `pmm_alloc_frame()` 失败 → `kernel_panic()`(内核页表映射失败不可恢复)。
+- `vmm_init()` 中任何映射步骤失败 → `kernel_panic()`。
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### 7.1 vmm_init 时序
+
+```
+[kernel_main on BSP, after slab_init + fdt_unflatten]
+ │
+ ├─> 1. root_pa = pmap::create() // 分配新根页表
+ │
+ ├─> 2. 映射内核 .text 段 (RX)
+ │ for (pa = _stext_pa; pa < _etext_pa; pa += PAGE_SIZE)
+ │ pmap::map(root_pa, pa + arch::kVAOffset, pa, PTE_R|PTE_X|PTE_G)
+ │
+ ├─> 3. 映射 .rodata 段 (R-)
+ ├─> 4. 映射 .data + .bss 段 (RW)
+ ├─> 5. 映射 frame_map 区域 (RW)
+ ├─> 6. 映射剩余物理内存 (RW)
+ ├─> 7. 映射 MMIO: UART 0x10000000, PLIC 0x0C000000 (RW)
+ │
+ ├─> 8. pmap::activate(root_pa) // 写 satp, sfence.vma
+ │ └─ 此刻 early_pgdir 的恒等映射失效
+ │ └─ 代码继续执行(因为高半区映射已建立)
+ │
+ └─> 9. 保存 root_pa 到全局变量 g_kernel_pgtbl
+```