summaryrefslogtreecommitdiff
path: root/docs/specs
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-04-17 00:54:20 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-04-17 00:54:20 +0800
commit81563cb415d8214f1d1e34f73d49a404424f8f2c (patch)
treece4926b598f8eef00fe2fb2eff62766688c7bad9 /docs/specs
parentab83dfb9a607807a6373f9bd47190848b822cf6e (diff)
downloadlume-81563cb415d8214f1d1e34f73d49a404424f8f2c.tar.gz
lume-81563cb415d8214f1d1e34f73d49a404424f8f2c.tar.xz
docs: add global initialization, linker and entry specifications, and spinlock documentation
- Introduced global initialization sequence documentation outlining the objectives, dependencies, core data structures, public APIs, concurrency contracts, error paths, and state machine for LumeOS. - Created linker and entry documentation defining early memory layout and assembly startup sequence compatible with multiple bootloaders, including API specifications and error handling. - Added spinlock documentation detailing the implementation of spinlocks, concurrency synchronization, memory ordering guarantees, and error handling mechanisms.
Diffstat (limited to 'docs/specs')
-rw-r--r--docs/specs/arch_abstraction.md223
-rw-r--r--docs/specs/global_init.md208
-rw-r--r--docs/specs/linker_and_entry.md208
-rw-r--r--docs/specs/spinlock.md195
4 files changed, 834 insertions, 0 deletions
diff --git a/docs/specs/arch_abstraction.md b/docs/specs/arch_abstraction.md
new file mode 100644
index 0000000..a90137c
--- /dev/null
+++ b/docs/specs/arch_abstraction.md
@@ -0,0 +1,223 @@
+# 架构抽象层 (Architecture Abstraction)
+
+## 1. 目标 (Objective)
+
+为 LumeOS 的平台无关层(MI)提供统一的底层硬件原语接口。本模块负责将 RISC-V 架构特有的控制状态寄存器 (CSR) 操作、汇编级上下文切换 (Context Switch)、陷阱帧 (TrapFrame) 布局、以及 TLB 刷新机制进行封装。**本项目所有内联汇编 (Inline Assembly) 严格限定于本模块内部,上层模块禁止直接操作硬件寄存器。**
+
+## 2. 前置依赖 (Dependencies)
+
+- **全局类型契约:** 依赖 `include/lume/types.h` 提供的 `uint64` 等标准类型。
+
+- **编译器支持:** 依赖 GCC/Clang 的内联汇编能力 (`__asm__ volatile`),必须确保不被过度优化(使用 `memory` clobber 建立编译器屏障)。
+
+- **启动序列契约:** `arch::cpu::id()` 依赖 `tp` 寄存器在 `entry.S` 中被正确初始化为当前 CPU 编号(参见 `linker_and_entry.md`)。在 `tp` 初始化之前,禁止调用本模块任何依赖 CPU ID 的函数。
+
+
+## 3. 核心数据结构 (Data Structures)
+
+### 3.1 上下文切换结构 (`struct Context`)
+
+用于调度器在不同内核流(如不同 Task 或 KThread)之间进行主动切换(Switch)。它只保存 Callee-Saved(被调用者保存)寄存器。
+
+```cpp
+// 定义于 arch/riscv/include/arch/context.h
+namespace arch {
+ struct Context {
+ uint64 ra; // Return Address (返回地址)
+ uint64 sp; // Stack Pointer (栈指针)
+ uint64 s[12]; // s0 ~ s11 (Callee-saved 寄存器)
+ };
+}
+```
+
+### 3.2 陷阱帧结构 (`struct TrapFrame`)
+
+用于在发生中断、异常或系统调用时,由硬件与汇编入口 `trap_vector` 协同保存当前 CPU 的执行快照。此结构同时跨越用户态和内核态,必须精确布局。
+
+```cpp
+// 定义于 arch/riscv/include/arch/trapframe.h
+// 对齐由分配方保证(页对齐或栈对齐),struct 本身不施加 alignas
+namespace arch {
+ struct TrapFrame {
+ /* 内核元数据区 (偏移 0 - 39) */
+ uint64 kernel_satp; // 0: 内核页表地址 (挂载至 satp)
+ uint64 kernel_sp; // 8: 顶层内核栈地址
+ uint64 kernel_trap; // 16: 内核 C++ trap_handler 的绝对地址
+ uint64 kernel_cpu_id; // 24: 当前 CPU 编号 (trap 入口加载至 tp)
+ uint64 epc; // 32: 中断/异常时的指令地址 (sepc)
+
+ /* CSR 备份区 (偏移 40 - 47) */
+ uint64 sstatus; // 40: 中断前的特权状态 (含 SPP, SPIE 位)
+
+ /* 通用寄存器备份区 (偏移 48 - 295) */
+ uint64 ra; // 48
+ uint64 sp; // 56 (用户态的栈指针)
+ uint64 gp; // 64
+ uint64 tp; // 72
+ uint64 t[7]; // 80: t0-t6 (Caller-saved 临时寄存器)
+ uint64 s[12]; // 136: s0-s11 (Callee-saved 寄存器)
+ uint64 a[8]; // 232: a0-a7 (函数参数/系统调用参数与返回值)
+ };
+ static_assert(sizeof(TrapFrame) == 296);
+}
+```
+
+**相比原版的关键修正:**
+- **新增 `sstatus` 字段:** 这是陷阱帧中最容易遗漏但最致命的字段。`sstatus` 包含 `SPP`(之前的特权级)和 `SPIE`(之前的中断使能状态)。若不保存/恢复 `sstatus`,`sret` 返回用户态时将无法正确恢复特权级和中断状态。
+- **将 `kernel_cpu_id` 移入元数据区:** 它不是通用寄存器的备份,而是内核注入的元数据(trap 入口用来设置 `tp`),不应放在"通用寄存器备份区"中。
+- **简化寄存器数组:** 将 `t0_t2[3]` + `t3_t6[4]` 合并为 `t[7]`,将 `s0_s1[2]` + `s2_s11[10]` 合并为 `s[12]`。编译器按偏移访问不受影响,且减少了心智负担。
+
+
+## 4. 公共 API (Public APIs)
+
+所有接口统一在 `arch` 及其子命名空间下,通过静态内联函数或汇编实现,实现**零运行时代价**。这些操作不会引发常规意义的"错误"(如不会返回 `-ENOMEM`),因此不采用错误码范式。
+
+### 4.1 CPU 控制与状态感知 (`namespace arch::cpu`)
+
+```cpp
+namespace arch::cpu {
+ // 获取当前 CPU 编号 (从 tp 寄存器中读取)
+ // 前置条件:tp 必须已在 entry.S 中初始化
+ uint64 id();
+
+ // 开启中断 (设置 sstatus.SIE = 1)
+ void intr_on();
+
+ // 关闭中断 (设置 sstatus.SIE = 0)
+ void intr_off();
+
+ // 获取当前中断状态 (返回 true 表示中断已开启)
+ bool intr_enabled();
+
+ // 使 CPU 进入低功耗睡眠,直到下一个中断到来 (wfi 指令)
+ void halt_until_interrupt();
+
+ // 读取硬件单调时钟计数 (rdtime 指令)
+ // 返回自系统启动以来的 tick 数(频率由 FDT timebase-frequency 决定)
+ uint64 read_time();
+}
+```
+
+### 4.2 内存管理硬件原语 (`namespace arch::mmu`)
+
+```cpp
+namespace arch::mmu {
+ // 读取缺页异常发生时的虚拟地址 (从 stval 寄存器读取)
+ uint64 get_fault_address();
+
+ // 切换顶级页表
+ // 参数:physical_pgdir_addr 为页表的物理地址(非 PPN),
+ // 函数内部负责右移 12 位并组装 SV39 模式字段写入 satp
+ void set_page_table(uint64 physical_pgdir_addr);
+
+ // 读取当前页表物理地址 (从 satp 提取 PPN 并左移还原为物理地址)
+ uint64 get_page_table();
+
+ // 刷新整个 TLB (sfence.vma zero, zero)
+ void flush_tlb_all();
+
+ // 刷新特定虚拟地址的 TLB 条目 (sfence.vma vaddr, zero)
+ void flush_tlb_page(uint64 vaddr);
+}
+```
+
+### 4.3 陷阱向量控制 (`namespace arch::trap`)
+
+```cpp
+namespace arch::trap {
+ // 设置 S-mode 陷阱向量基址 (写入 stvec 寄存器)
+ // 参数:handler_addr 为汇编 trap_vector 入口的虚拟地址
+ // 模式:Direct(所有异常/中断统一入口)
+ void set_vector(uint64 handler_addr);
+
+ // 读取当前陷阱原因 (scause 寄存器)
+ uint64 get_cause();
+
+ // 读取陷阱附加信息 (stval 寄存器)
+ uint64 get_value();
+}
+```
+
+### 4.4 上下文切换原语
+
+```cpp
+namespace arch {
+ // 汇编实现的上下文切换函数。定义于 arch/riscv/swtch.S
+ // 语义:将当前 CPU 寄存器保存到 old_ctx,并将 next_ctx 的内容恢复到 CPU
+ // 本函数由 C++ 调用,汇编实现,绝对不会失败。
+ extern "C" void context_switch(Context* old_ctx, Context* next_ctx);
+}
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+本模块属于内核的绝对最底层,位于 `Spinlock` 模块的下方,因此**不感知、不持有、也不获取任何高级锁。**
+
+- **调用方契约(中断开关):** 上层模块(如 `Spinlock`)需直接依赖 `arch::cpu::intr_off()` 与 `intr_on()` 来防止单核死锁。
+
+- **内存屏障契约(Memory Barriers):** `flush_tlb_all` 与 `flush_tlb_page` 的底层 `sfence.vma` 指令本身即为硬件级内存屏障。内联汇编实现中仍须添加编译器屏障(`"memory"` clobber),防止编译器跨越 TLB 刷新点重排内存访问。
+
+- **跨核(SMP)同步警告:** 当前 `flush_tlb_page` 仅执行**本地** `sfence.vma`。在实现基于 IPI (核间中断) 的 TLB Shootdown 前,VMM 须确保同一时间只有一个 CPU 在修改共享的页表映射。未来将在本命名空间中增加 `flush_tlb_page_remote()` 接口。
+
+
+## 6. 错误路径 (Error Paths)
+
+底层指令级操作无传统错误返回。
+
+- `flush_tlb_page` 对任何地址执行 `sfence.vma` 都不会触发异常(仅使对应 TLB 表项失效)。
+
+- `context_switch` 若传入无效指针(如 nullptr),加载/存储操作将触发 Store/Load Page Fault,由 Phase 2 的 `trap.md` 规定的异常分发层兜底并触发 `Panic`。
+
+- `set_page_table` 传入非法物理地址将导致后续的所有内存访问引发 Page Fault。此类错误属于编程逻辑错误,无法在本层恢复。
+
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### 7.1 上下文切换时序 (`context_switch`)
+
+调度器在选择出新的 `Task` 后,调用此接口的时序如下:
+
+```
+[当前 Task A] (执行 sched() 决定放弃 CPU)
+ │
+ ▼
+[调用 arch::context_switch(&TaskA->context, &TaskB->context)]
+ │
+ ├─> 1. 保存 ra, sp, s0-s11 到 TaskA->context 内存区块中
+ ├─> 2. 从 TaskB->context 内存区块中加载 ra, sp, s0-s11 到物理寄存器
+ └─> 3. ret (返回到被加载的 ra 地址)
+ │
+ ▼
+[恢复至 Task B] (PC 从 Task B 上次挂起的 sched() 处继续执行)
+```
+
+### 7.2 陷阱帧保存/恢复时序 (trap_vector)
+
+```
+[用户态正在运行]
+ │
+ ├─ 中断/异常/ecall 触发
+ ▼
+[trap_vector (汇编入口, arch/riscv/trap/trap_vector.S)]
+ │
+ ├─> 1. 交换 sscratch <-> tp (sscratch 预存 TrapFrame 地址)
+ ├─> 2. 将 31 个通用寄存器写入 TrapFrame
+ ├─> 3. csrr sstatus → TrapFrame.sstatus (保存特权状态)
+ ├─> 4. csrr sepc → TrapFrame.epc (保存中断 PC)
+ ├─> 5. 加载 kernel_sp, kernel_satp, kernel_cpu_id 到 sp, satp, tp
+ ├─> 6. sfence.vma (切换到内核页表后刷新 TLB)
+ └─> 7. jalr TrapFrame.kernel_trap (跳转至 C++ trap_handler)
+ │
+ ▼
+[MI 层 trap_handler()] → 分发至 syscall / page fault / IRQ 处理
+ │
+ ▼
+[usertrapret: 恢复用户态]
+ │
+ ├─> 1. 写回 TrapFrame.epc → sepc
+ ├─> 2. 写回 TrapFrame.sstatus → sstatus
+ ├─> 3. 切换回用户页表 (satp)
+ ├─> 4. 从 TrapFrame 恢复 31 个通用寄存器
+ ├─> 5. 交换 sscratch <-> tp (恢复 TrapFrame 地址到 sscratch)
+ └─> 6. sret (返回用户态)
+``` \ No newline at end of file
diff --git a/docs/specs/global_init.md b/docs/specs/global_init.md
new file mode 100644
index 0000000..cd14dae
--- /dev/null
+++ b/docs/specs/global_init.md
@@ -0,0 +1,208 @@
+# 全局初始化序列 (Global Initialization)
+
+## 1. 目标 (Objective)
+
+统筹 LumeOS 从汇编跃迁至 C++ 后的全局初始化流程。定义子系统间的严格启动顺序、全局对象的生命周期管理范式、以及 BSP/AP 的两阶段同步协议。
+
+## 2. 前置依赖 (Dependencies)
+
+- **汇编环境契约:** 依赖 `linker_and_entry.md` 保证的执行环境:BSS 段已被清零、高半区虚拟内存(`early_pgdir`)已映射、`tp` 寄存器已保存当前 CPU ID、`gp` 寄存器已就绪。注意:`early_cpp_init()` 已在 `entry.S` 中由 BSP 调用完毕(在 `kernel_main` 之前),不在本模块的管辖范围内。
+
+- **全局基础契约:** 依赖 `00_conventions.md` 中禁用了 C++ 异常与 RTTI 的环境。
+
+- **并发同步契约:** 依赖 `spinlock.md` 提供的 `Spinlock`(但在 BSP 独占期不使用)。
+
+
+## 3. 核心数据结构与生命周期范式 (Data Structures & Lifecycle)
+
+在内核早期,物理内存管理 (PMM) 和字节级分配器 (Slab) 尚未就绪,传统的 `new` 和隐式的全局构造函数极易引发缺页崩溃或顺序未定义行为。
+
+### 3.1 显式生命周期模型 (Placement New 范式)
+
+对于所有复杂的内核全局单例(如 `PhysicalMemoryManager`, `Scheduler`),**严禁**直接定义为全局变量依赖 `.init_array` 隐式构造。必须采用以下静态缓冲区分配 + 显式 Placement New 的范式:
+
+```cpp
+// 示例:kernel/mm/pmm_global.cc
+
+// Placement new 声明(freestanding 环境无 <new> 头文件,自行提供)
+// 定义于 include/lume/new.h
+inline void* operator new(size_t, void* ptr) noexcept { return ptr; }
+inline void operator delete(void*, void*) noexcept {}
+
+// 1. 在 .bss 段静态分配对齐的内存区块,不触发任何构造函数
+alignas(PhysicalMemoryManager) uint8 g_pmm_buf[sizeof(PhysicalMemoryManager)];
+
+// 2. 暴露全局指针供外部访问
+PhysicalMemoryManager* g_pmm = nullptr;
+
+// 3. 在 kernel_main 的严格时序中被显式调用
+void pmm_init(uint64 fdt_paddr) {
+ // 使用 Placement New 在预分配的内存上执行构造函数
+ g_pmm = new (g_pmm_buf) PhysicalMemoryManager(fdt_paddr);
+}
+```
+
+**关于 `<new>` 头文件:** 在 `-ffreestanding -nostdlib` 编译环境下,`<new>` 可能不可用或行为不一致。LumeOS 在 `include/lume/new.h` 中自行提供 placement new 声明,不依赖任何标准库头文件。
+
+### 3.2 简单数据结构的按需初始化
+
+对于没有复杂依赖的 Plain Old Data (POD) 或简单锁对象:
+
+- `Spinlock` 等对象直接作为 BSS 段全局变量,由 `entry.S` 清零。随后在所属子系统的 `init()` 方法中调用 `lock.init("name")` 赋予身份。
+
+### 3.3 `.init_array` 的使用边界
+
+`early_cpp_init()` 在 `entry.S` 中遍历 `.init_array`。只有满足以下**全部**条件的全局构造函数才允许出现在此表中:
+
+- 构造函数不依赖任何子系统(PMM、Slab、VMM、Scheduler 均未初始化)。
+- 构造函数不分配动态内存。
+- 构造函数不获取任何锁。
+
+实际上,应该尽量让 `.init_array` 为空。绝大多数全局对象应走 §3.1 的 Placement New 路径。
+
+
+## 4. 公共 API (Public APIs)
+
+### 4.1 内核 C++ 唯一入口
+
+```cpp
+// 定义于 kernel/main.cc
+// 被 entry.S 中的 real_start 在开启 MMU、调用 early_cpp_init 后跳转
+// 所有 CPU 从此入口进入 C++ 世界
+// a0 = cpu_id, a1 = fdt_paddr
+extern "C" void kernel_main(uint64 cpu_id, uint64 fdt_paddr) __attribute__((noreturn));
+```
+
+### 4.2 子系统初始化签名规范
+
+所有子系统的初始化入口必须遵循统一签名,并在 `kernel/main.cc` 内部按图谱依次调用。每个 `xxx_init()` 函数成功时无返回值,失败时直接 `kernel_panic()`:
+
+```cpp
+void console_init(); // 1. UART 就绪,此后 kprintf/kernel_panic 可输出
+void fdt_init(uint64 fdt_paddr); // 2. 解析 FDT,构建设备节点表
+void pmm_init(); // 3. 从 FDT 获取内存布局,建立 Buddy 分配器
+void slab_init(); // 4. 建立 kmem_cache,此后 operator new 可用
+void vmm_init(); // 5. 建立精细化内核页表,废弃 early_pgdir
+void trap_init(); // 6. 设置 stvec,注册 IRQ 分发器
+void plic_init(); // 7. 配置 PLIC 中断控制器(阈值、优先级、使能位)
+void timer_init(); // 8. 配置定时器中断(调度器时钟源)
+void sched_init(); // 9. 构造 BSP 的 Idle Task 和初始就绪队列
+```
+
+### 4.3 AP 初始化签名
+
+AP 在 BSP 完成全局初始化后执行,仅限 Per-CPU 私有数据:
+
+```cpp
+void vmm_init_ap(); // 切换 satp 至 BSP 建好的内核页表
+void trap_init_ap(); // 设置本 CPU 的 stvec
+void plic_init_ap(); // 配置本 CPU 在 PLIC 上的 Context 使能
+void timer_init_ap(); // 启动本 CPU 的定时器中断
+void sched_init_ap(); // 初始化本 CPU 的 O(1) 就绪队列、创建 Idle Task
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+这是整个内核生命周期中**唯一一段不受自旋锁约束的特殊时期**,必须严格遵循以下阶段隔离:
+
+### 5.1 两阶段同步协议
+
+LumeOS 启动过程中存在**两个独立的同步屏障**,不要混淆:
+
+| 同步点 | 位置 | 变量 | 含义 |
+|--------|------|------|------|
+| 第一道 | `entry.S` (汇编层) | `boot_sync_flag` | AP 等待 BSP 建立 `early_pgdir`,完成后所有 CPU 齐步进入虚拟地址空间 |
+| 第二道 | `kernel_main` (C++ 层) | `ap_boot_sync` | AP 等待 BSP 完成全部子系统初始化,完成后 AP 开始 Per-CPU 初始化 |
+
+### 5.2 绝对单核期 (BSP 独占)
+
+- 在 `kernel_main` 启动初期,AP 卡在第二道屏障 `ap_boot_sync` 处自旋。
+
+- BSP 拥有对全局数据结构的**绝对独占权**。在此期间调用 `pmm_init`、`vmm_init` 等函数时,**无需获取该子系统的 `Spinlock`**(也不应获取,因为锁可能尚未初始化)。
+
+### 5.3 多核唤醒与并发期 (AP 启动)
+
+- BSP 完成所有全局初始化后,通过 `__atomic_store_n(&ap_boot_sync, 1, __ATOMIC_RELEASE)` 唤醒 AP。`RELEASE` 语义保证 AP 读到 `ap_boot_sync == 1` 时,所有全局单例对象必然已构造完毕。
+
+- AP 进入后,**只能执行 Per-CPU 的局部初始化**(如设置自身的 `stvec`、初始化 PLIC Context、创建 Per-CPU Idle Task)。
+
+- 一旦 AP 的 Per-CPU 初始化完成并开启中断,所有子系统对全局数据的访问必须严格遵守 `spinlock.md` 契约。
+
+
+## 6. 错误路径 (Error Paths)
+
+初始化阶段的错误处理极度苛刻:
+
+- **console_init 之前的 Panic:** 此时 UART 尚未就绪,`kernel_panic()` 无法输出任何信息。唯一行为是关中断 + `wfi` 死循环(静默死亡)。可通过 JTAG/GDB 附加调试。
+
+- **console_init 之后的 Panic:** 可输出 Panic 消息和调用栈信息后死循环。
+
+- 任何子系统的 `init` 方法如果检测到硬件缺失(如 FDT 解析不到内存节点)或关键资源耗尽(Slab 无法建立早期缓存),必须**立即调用 `kernel_panic()`**。
+
+- 此阶段系统尚未具备任务调度与资源回收能力,无后路可退。
+
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### 7.1 全局初始化依赖 DAG 图
+
+`kernel_main` 必须严格按照自顶向下的单向依赖顺序执行,任何逆序调用将导致 `kernel_panic` 或缺页崩溃。
+
+```
+ entry.S (汇编世界)
+ │
+ early_cpp_init() ← 遍历 .init_array (应尽量为空)
+ │
+ ════════════════
+ kernel_main()
+ ════════════════
+ │
+ ┌─────────────┴──────────────┐
+ │ │
+ [BSP (cpu_id == 0)] [AP (cpu_id != 0)]
+ │ │
+ 1. console_init() 自旋等待
+ └─ UART 就绪 ap_boot_sync == 1
+ └─ 此后 Panic 可输出 │
+ │ │
+ 2. fdt_init(fdt_paddr) │
+ └─ 解析 FDT 设备树 │
+ │ │
+ 3. pmm_init() │
+ └─ Buddy 分配器就绪 │
+ │ │
+ 4. slab_init() │
+ └─ operator new 可用 │
+ │ │
+ 5. vmm_init() │
+ └─ 完整内核页表就绪 │
+ └─ 废弃 early_pgdir 恒等映射 │
+ │ │
+ 6. trap_init() │
+ └─ stvec 挂载 trap_vector │
+ │ │
+ 7. plic_init() │
+ └─ PLIC 中断控制器就绪 │
+ │ │
+ 8. timer_init() │
+ └─ 定时器中断就绪 │
+ │ │
+ 9. sched_init() │
+ └─ Idle Task (Task 0) 创建 │
+ └─ Per-CPU 就绪队列初始化 │
+ │ │
+ ──[RELEASE 屏障]──────────────────> 读到 1
+ ap_boot_sync = 1 │
+ │ 1. vmm_init_ap()
+ │ 2. trap_init_ap()
+ │ 3. plic_init_ap()
+ │ 4. timer_init_ap()
+ │ 5. sched_init_ap()
+ │ │
+ ▼ ▼
+ arch::cpu::intr_on() arch::cpu::intr_on()
+ BSP 变为 Idle Task AP 变为 Idle Task
+ 进入调度循环 进入调度循环
+```
+
+**BSP 进入调度的方式:** BSP 在 `sched_init()` 中将自身的执行上下文注册为 CPU 0 的 Idle Task (Task 0)。开启中断后,第一个定时器中断触发调度器接管执行流。不是通过 `sched_yield()` 主动让出,而是被定时器中断被动抢占进入调度循环。 \ No newline at end of file
diff --git a/docs/specs/linker_and_entry.md b/docs/specs/linker_and_entry.md
new file mode 100644
index 0000000..a59d28d
--- /dev/null
+++ b/docs/specs/linker_and_entry.md
@@ -0,0 +1,208 @@
+# 链接器与引导入口 (Linker & Entry)
+
+## 1. 目标 (Objective)
+
+定义 LumeOS 在 RISC-V 64 架构上的早期内存布局与汇编启动序列。负责兼容多种 Bootloader(OpenSBI、GRUB、U-Boot),建立高半区内核(Higher-Half Kernel)映射,初始化早期 C++ 运行环境(BSS、GP 寄存器、全局构造函数),并安全地将各个 CPU 同步导入虚拟地址空间的 `kernel_main`。
+
+## 2. 前置依赖 (Dependencies)
+
+- **引导协议契约 (Boot Protocol):** 为了同时支持 OpenSBI(直接跳转偏移 0)和 GRUB/U-Boot(解析元数据后引导),LumeOS 采用 **RISC-V Linux Boot Protocol**。内核文件起始处必须包含标准的 64 字节 Image Header。
+
+- **寄存器传参契约:** 无论哪种引导器,在跳转至内核执行的第一条指令时,必须保证 `a0` = 当前 CPU ID (Hart ID),`a1` = FDT (Device Tree) 的物理基址。
+
+- **编译模型契约:** 内核必须使用 `-mcmodel=medany` 编译,以支持基于 PC 的相对寻址,确保在 MMU 开启前后代码均能正确执行。
+
+- **硬件 MMU 契约:** 目标硬件必须支持 RISC-V SV39 分页模式。
+
+
+## 3. 核心数据结构与内存布局 (Data Structures & Memory Map)
+
+### 3.1 虚拟与物理地址基址
+
+- **物理加载基址 (Physical Base):** `0x80200000` (跳过低地址的 OpenSBI 固件区,对齐 2MB 以支持大页映射)。
+
+- **内核虚拟基址 (Virtual Base):** `0xFFFFFFC080200000` (SV39 地址空间顶部,硬编码偏移量 `PAGE_OFFSET = 0xFFFFFFC000000000`)。
+
+- **虚拟-物理偏移常量:** `KERNEL_VA_OFFSET = 0xFFFFFFC000000000`,用于虚拟/物理地址互转:`VA = PA + KERNEL_VA_OFFSET`,`PA = VA - KERNEL_VA_OFFSET`。
+
+
+### 3.2 链接脚本段分布 (`linker.ld`)
+
+必须在所有代码之前放置 `.head.text` 段,以确保 Image Header 绝对位于二进制文件的第 0 字节处。所有段强制 4KB 对齐:
+
+```
++-----------------------+ 0xFFFFFFC080200000 (Virtual) / 0x80200000 (Physical)
+| .head.text (镜像头) | -> 严格位于内核入口 0 字节处,包含 Linux Boot Header
++-----------------------+
+| .text (执行代码) | -> ALIGN(4096), 权限: RX
++-----------------------+
+| .rodata (只读数据) | -> ALIGN(4096), 权限: R-
+| (含 __init_array) | -> 全局构造函数指针表
++-----------------------+
+| .data (已初始化数据) | -> ALIGN(4096), 权限: RW
+| (含 __global_pointer$)| -> 位于 .sdata 附近,用于优化全局变量访问
+| (含 early_pgdir) | -> 4KB 静态早期页表
++-----------------------+
+| .bss (未初始化数据) | -> ALIGN(4096), 权限: RW
++-----------------------+
+| boot_stack (早期栈) | -> ALIGN(4096), 为每个 CPU 预留 8KB 栈空间
++-----------------------+
+```
+
+**链接脚本必须导出的符号:**
+
+| 符号名 | 含义 |
+|--------|------|
+| `_start` | 内核入口点(Image Header 首字节) |
+| `_stext` / `_etext` | .text 段边界 |
+| `_srodata` / `_erodata` | .rodata 段边界 |
+| `_sdata` / `_edata` | .data 段边界 |
+| `_sbss` / `_ebss` | .bss 段边界(用于启动清零) |
+| `__global_pointer$` | GP 寄存器锚点 |
+| `__init_array_start` / `__init_array_end` | C++ 全局构造函数表边界 |
+| `_kernel_end` | 内核镜像结束地址(PMM 初始化起点) |
+
+### 3.3 早期页表 (Early Page Table)
+
+- **`early_pgdir`:** 一块静态分配的 4KB `.data` 内存,用作初始顶级页表 (Root Page Table)。
+
+- 内含两个 1GB 的超级大页(Giga Page)映射:
+
+ 1. **恒等映射 (Identity Map):** `0x80000000 -> 0x80000000`,保证开启 MMU 瞬间下一条指令能正确取指。
+
+ 2. **高半区映射 (Higher-Half Map):** `0xFFFFFFC080000000 -> 0x80000000`,供后续 C++ 代码正常运行。
+
+- **生命周期:** 此恒等映射为临时性过渡,在 VMM 建立完整页表后必须被撤销。高半区映射将被 VMM 接管并细化为 4KB 粒度的精确权限映射。
+
+
+## 4. 公共 API 与核心符号 (Public APIs & Symbols)
+
+### 4.1 RISC-V OS Image Header 规范
+
+在 `entry.S` 的最开始(`.head.text` 段),必须按照以下结构手写 64 字节的元数据。
+定义参照 Linux `arch/riscv/include/asm/image.h` 中的 `struct riscv_image_header`:
+
+```asm
+ .section .head.text, "ax"
+ .globl _start
+_start:
+ /* code0 (u32, offset 0): 可执行跳转指令,OpenSBI 裸启入口 */
+ j real_start
+ /* code1 (u32, offset 4): 保留 */
+ .word 0
+ /* text_offset (u64, offset 8): Image 加载偏移 */
+ .dword 0x200000
+ /* image_size (u64, offset 16): 镜像大小,链接后由脚本填充,初期可写 0 */
+ .dword 0
+ /* flags (u64, offset 24): 内核标志位 (bit 0: 0=LE, 1=BE) */
+ .dword 0
+ /* version (u32, offset 32): Header 版本 (major << 16 | minor),当前 0.2 */
+ .word 0x0002
+ /* res1 (u32, offset 36): 保留 */
+ .word 0
+ /* res2 (u64, offset 40): 保留 */
+ .dword 0
+ /* magic (u64, offset 48): RISCV_IMAGE_MAGIC = "RISCV\0\0\0" (LE) */
+ .dword 0x5643534952
+ /* magic2 (u32, offset 56): RISCV_IMAGE_MAGIC2 = "RSC\x05" (LE) */
+ .word 0x05435352
+ /* res3 (u32, offset 60): 预留给 PE/COFF 偏移 (UEFI),目前填 0 */
+ .word 0
+```
+
+### 4.2 汇编函数签名
+
+```cpp
+// 定义于 arch/riscv/boot/entry.S
+// _start 中的 `j real_start` 跳转目标,执行早期硬件初始化。
+void real_start() __attribute__((noreturn)); // 汇编实现,无 C++ 调用约定
+
+// 定义于 arch/riscv/boot/entry.S 或 kernel/main.cc
+// 仅由 CPU 0 调用,遍历 .init_array 执行全局构造函数。
+void early_cpp_init();
+
+// 定义于 kernel/main.cc
+// C++ 内核总入口。各 CPU 在开启 MMU 后汇聚于此。
+// 参数:cpu_id = 当前 CPU 编号,fdt_paddr = FDT 物理基址。
+void kernel_main(uint64 cpu_id, uint64 fdt_paddr) __attribute__((noreturn));
+```
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+此时 `Spinlock` 尚未初始化,必须使用汇编级的极简同步机制来避免多核访问冲突。
+
+- **本模块持有的锁:** 无高级锁。
+
+- **本模块可能获取的外部锁:** 无。
+
+- **原子同步契约 (SMP Sync):**
+
+ 1. 在 `.data` 段定义一个全局标志 `uint32 boot_sync_flag = 0;`。
+
+ 2. 只有 **CPU 0** (或 `a0` 读取到的第一个 CPU) 作为主核心 (BSP),获准执行 BSS 清零、早期页表填充等破坏性操作。
+
+ 3. **副核心 (AP, Application Processors)** 必须使用原子加载指令在汇编层盲等 `boot_sync_flag` 变为 1。具体实现为 `lw` + 循环比较(不需要 `amoswap`,因为只有 BSP 写入,AP 只读)。
+
+ 4. BSP 完成 `early_pgdir` 写入并唤醒 AP 前,必须执行 `fence rw, rw` 以保证所有先前的内存写入对其他 CPU 可见,随后使用 `fence.i` 刷新指令缓存(如有需要),最后将 `boot_sync_flag` 设为 1。
+
+
+## 6. 错误路径 (Error Paths)
+
+在早期的裸机启动阶段,内核没有任何错误恢复能力。
+
+- **检测点:** 所有 CPU 在 `real_start` 入口处可选地检查 `misa` 或 `marchid` 判断基本硬件兼容性(如 64 位检查)。
+
+- **失败行为:** 立即关闭本 CPU 的中断使能(`csrc sstatus, SSTATUS_SIE`),进入 `wfi` 死循环。无串口输出(此时 UART 尚未初始化)。
+
+- **无回滚:** 早期启动无任何资源可回收,失败即死。
+
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+以下描述了从底层固件直到进入 `kernel_main` 的全流程:
+
+```
+[引导分流]
+ ├──> OpenSBI 裸启: 直接跳转到内存基址 (0x80200000)
+ │ └─ 命中 _start 处的第一条指令 `j real_start`
+ │
+ └──> GRUB / U-Boot 引导: 解析位于 0x80200000 处的 64 字节 Header
+ ├─ 验证 magic ("RISCV\0\0\0") 与 magic2 ("RSC\x05")
+ ├─ 验证 text_offset
+ └─ 跳转到 0x80200000 (此时 a0=cpu_id, a1=fdt_paddr)
+ └─ 同样命中 _start 处的第一条指令 `j real_start`
+
+ │ (汇聚点)
+ ▼
+[real_start]
+ │
+ ├─> 1. 设置 gp 寄存器 (la gp, __global_pointer$)
+ │ // 极其关键:medany 模型下访问全局变量依赖此寄存器
+ │
+ ├─> 2. 设置早期内核栈 (la sp, boot_stack + (cpu_id + 1) * 8192)
+ │
+ ├─> 3. 判断是否为主核心 (CPU 0)?
+ │ ├── [是 (BSP)]
+ │ │ ├─ 3.1 循环清理 BSS 段 (_sbss 至 _ebss 填 0)
+ │ │ ├─ 3.2 填充 early_pgdir (建立恒等映射与高半区映射)
+ │ │ ├─ 3.3 fence rw, rw (确保内存修改对其他 CPU 可见)
+ │ │ └─ 3.4 释放副核心 (boot_sync_flag = 1)
+ │ │
+ │ └── [否 (AP)]
+ │ └─ 3.1 汇编级自旋等待,直到 boot_sync_flag == 1
+ │
+ ├─> 4. 高半区蹦床 (Higher-Half Trampoline - 所有 CPU 执行)
+ │ ├─ 4.1 将 early_pgdir 物理地址写入 satp 寄存器 (MODE=SV39)
+ │ ├─ 4.2 执行 sfence.vma 刷新 TLB
+ │ ├─ 4.3 计算 kernel_main 的高半区虚拟地址
+ │ └─ 4.4 jr 绝对跳转至虚拟地址,彻底抛弃低位物理 PC
+ │
+ ▼
+[进入虚拟世界: call early_cpp_init] (仅 BSP 执行)
+ │
+ └─> 遍历 __init_array_start 到 __init_array_end
+ 依次调用全局构造函数,完成 C++ early runtime setup
+ │
+ ▼
+[跳转至 C++ 逻辑: call kernel_main(a0, a1)] (所有 CPU 汇合)
+``` \ No newline at end of file
diff --git a/docs/specs/spinlock.md b/docs/specs/spinlock.md
new file mode 100644
index 0000000..586ee43
--- /dev/null
+++ b/docs/specs/spinlock.md
@@ -0,0 +1,195 @@
+# 自旋锁与并发同步 (Spinlock & Synchronization)
+
+## 1. 目标 (Objective)
+
+为 LumeOS 提供多核环境下的底层互斥机制。本模块实现基于原子操作的自旋锁(Spinlock)和基于 RAII 范式的局部锁守卫(LockGuard)。同时,严格定义锁操作前后的硬件中断屏蔽策略(避免单核中断死锁)以及底层内存序保证(Memory Ordering)。
+
+## 2. 前置依赖 (Dependencies)
+
+- **全局类型契约:** `include/lume/types.h`。
+
+- **架构原语契约:** 依赖 `arch_abstraction.md` 中的 `arch::cpu::id()`, `arch::cpu::intr_on()`, `arch::cpu::intr_off()`, `arch::cpu::intr_enabled()`。
+
+- **Panic 机制:** 依赖底层的内核崩溃函数 `kernel_panic(const char* msg)`。
+
+- **全局初始化契约:** `g_cpu_sync_states[]` 为 BSS 段全局数组,由 `entry.S` 的 BSS 清零保证初始值为零。`Spinlock` 作为全局对象时,必须通过 `init()` 方法显式初始化(参见 `global_init.md`),不得依赖构造函数。
+
+
+## 3. 核心数据结构 (Data Structures)
+
+### 3.1 CPU 中断嵌套跟踪器
+
+为了支持锁的嵌套(如持有 `A` 锁时去获取 `B` 锁),必须在每 CPU 维度跟踪锁的深度,确保只有释放**最后一把锁**时,才恢复真实的中断状态。
+
+```cpp
+// 定义于 kernel/sync/cpu_state.h
+namespace sync {
+ struct CpuSyncState {
+ int lock_depth; // 当前 CPU 持有的自旋锁数量 (嵌套深度)
+ bool intr_was_on; // 在获取第一把锁之前,中断是否处于开启状态
+ };
+ // BSS 段全局数组,由 entry.S 清零初始化
+ inline constexpr uint64 kInvalidCpuId = ~0ULL;
+ extern CpuSyncState g_cpu_sync_states[kMaxCPUs];
+}
+```
+
+### 3.2 自旋锁类 (`Spinlock`)
+
+```cpp
+// 定义于 kernel/sync/spinlock.h
+class Spinlock {
+public:
+ // 默认构造:locked_ = 0, name_ = "uninit", cpu_id_ = kInvalidCpuId
+ // 全局 Spinlock 在 BSS 清零后通过 init() 显式初始化
+ Spinlock() = default;
+
+ // 显式初始化(用于全局对象或 Placement New 场景)
+ void init(const char* name);
+
+ // 禁用拷贝与移动 (锁对象具有固定的内存地址身份)
+ Spinlock(const Spinlock&) = delete;
+ Spinlock& operator=(const Spinlock&) = delete;
+
+ void acquire();
+ void release();
+ bool is_held_by_current_cpu() const;
+
+private:
+ uint32 locked_ = 0; // 锁状态:0=空闲, 1=占用
+ const char* name_ = "uninit"; // 锁名称 (用于 Panic 输出)
+ uint64 cpu_id_ = sync::kInvalidCpuId; // 持有者 CPU 编号
+};
+```
+
+### 3.3 RAII 锁守卫 (`LockGuard`)
+
+```cpp
+// 定义于 kernel/sync/spinlock.h
+class LockGuard {
+public:
+ explicit LockGuard(Spinlock& lock) : lock_(lock) {
+ lock_.acquire();
+ }
+
+ ~LockGuard() {
+ lock_.release();
+ }
+
+ // 禁用拷贝与移动
+ LockGuard(const LockGuard&) = delete;
+ LockGuard& operator=(const LockGuard&) = delete;
+
+private:
+ Spinlock& lock_;
+};
+```
+
+## 4. 公共 API 与中断管理协议 (Public APIs)
+
+### 4.1 中断压栈/出栈协议 (Internal API)
+
+必须在 `Spinlock::acquire()` 和 `release()` 内部隐式调用,严禁外部业务层手动调用。
+
+```cpp
+namespace sync {
+ // 关闭中断,并增加当前 CPU 的 lock_depth。
+ // 如果是第一把锁 (depth 从 0 变 1),记录当前 sstatus.SIE 状态到 intr_was_on。
+ void push_intr();
+
+ // 减少当前 CPU 的 lock_depth。
+ // 如果 depth 降为 0 且入栈前中断是开启的 (intr_was_on == true),则恢复中断。
+ void pop_intr();
+}
+```
+
+### 4.2 锁语义约定
+
+本模块属于内核生命线,发生任何违反并发契约的行为(如重复加锁、越权解锁)均属于致命代码逻辑错误。 **此类函数无返回值,一旦检测到契约被破坏,直接触发 `kernel_panic()` 宕机。**
+
+## 5. 并发与锁契约 (Concurrency & Locks)
+
+### 5.1 内存序保证 (Memory Order Guarantee)
+
+为了确保临界区内的读写操作不会被编译器或 CPU 的乱序执行(Out-of-Order Execution)重排到临界区外,必须使用带有内存屏障语义的原子操作。
+
+- **Acquire 语义:** `Spinlock::acquire()` 内部的原子交换必须使用 `__ATOMIC_ACQUIRE`。在 RISC-V 层面,这将生成带有 `.aq` (Acquire) 后缀的原子指令(如 `amoswap.w.aq`),确保锁获取之后的任何内存读写,都不会被重排到拿锁之前。
+
+- **Release 语义:** `Spinlock::release()` 内部的原子写零必须使用 `__ATOMIC_RELEASE`。使用 `__atomic_store_n(&locked_, 0, __ATOMIC_RELEASE)`,在 RISC-V 层面生成 `fence rw, w` + `sw`。注意:release 不需要原子 RMW 指令(`amoswap`),普通的 release-store 即可——因为此时本 CPU 已经是唯一的锁持有者,不存在竞争写。
+
+### 5.2 自旋优化 (TTAS: Test-and-Test-and-Set)
+
+朴素的 `while(__atomic_exchange_n(...))` 会在每次迭代都发出总线级原子操作,导致缓存一致性协议风暴。必须使用 TTAS 模式:
+
+```cpp
+void Spinlock::acquire() {
+ sync::push_intr();
+ // 外层:非原子读,利用本地缓存副本空转,不产生总线流量
+ while (true) {
+ while (__atomic_load_n(&locked_, __ATOMIC_RELAXED))
+ ; // 在 L1 cache 上旋转,等待释放信号via cache invalidation
+ // 内层:看到可能空闲,才发起真正的原子交换
+ if (__atomic_exchange_n(&locked_, 1, __ATOMIC_ACQUIRE) == 0)
+ break; // 成功获取
+ }
+ cpu_id_ = arch::cpu::id();
+}
+```
+
+### 5.3 业务层使用契约 (Caller Contract)
+
+1. **禁止睡眠:** 在持有 `Spinlock`(或处于 `LockGuard` 作用域内)的任何期间,**绝对禁止**调用可能导致当前 Task 休眠或主动放弃 CPU 的函数(如 `sleep()`, `yield()`, 或由于分配内存触发了等待)。
+
+2. **优先 RAII:** 常规业务逻辑中应通过 `LockGuard` 对象管理锁的生命周期,利用 C++ 析构确保锁在异常或提前 return 时必定释放。`acquire()/release()` 公开暴露仅供**调度器内部**等必须跨函数边界持有锁的特殊场景使用(如 `sleep()` 中释放锁 + 上下文切换 + 醒来后重新获取)。
+
+3. **锁序遵守:** 嵌套获取多把 Spinlock 时,必须严格遵循全局锁序 DAG(将在 Phase 2 完成后汇总为 `lock_ordering.md`)。任何违反锁序的代码在 Code Review 中必须被驳回。
+
+
+## 6. 错误路径与 Panic 触发点 (Error Paths)
+
+在以下情况下,自旋锁必须立即执行 `kernel_panic()` 阻断系统:
+
+1. **死锁防御 (Double Lock):** 在 `acquire()` 中,调用 `push_intr()` 关中断后、原子操作前,检查 `is_held_by_current_cpu()`。若为 `true`,说明当前 CPU 试图获取自己持有的锁,必然死锁,立即 Panic。打印锁名称与 CPU ID。
+
+2. **越权释放 (Invalid Release):** 在 `release()` 中,若 `is_held_by_current_cpu()` 为 `false`,说明代码逻辑严重错乱(锁未被持有、或被其他 CPU 持有),立即 Panic。
+
+3. **中断状态校验:** 在 `acquire()` 入口(`push_intr()` 之前),若中断已被开启但 `lock_depth > 0`,说明存在中断状态泄漏(某处代码在持有锁的同时非法开启了中断),可选 Panic。此检查在 `#ifdef DEBUG` 下启用以避免正式构建的性能损失。
+
+
+## 7. 状态机 / 时序图 (State Machine / Sequence)
+
+### 7.1 Spinlock Acquire / Release 硬件级时序
+
+```
+[当前 CPU (CPU X) 请求 LockGuard]
+ │
+ ├─> 1. push_intr()
+ │ ├─ 读取当前中断状态: was_on = arch::cpu::intr_enabled()
+ │ ├─ 关中断: arch::cpu::intr_off()
+ │ └─ if (lock_depth == 0) intr_was_on = was_on; lock_depth++
+ │
+ ├─> 2. 断言: is_held_by_current_cpu() == false (否则 Panic: double lock)
+ │
+ ├─> 3. TTAS 自旋获取
+ │ ├─ 外层 while: __atomic_load_n(&locked_, RELAXED) 非原子读空转
+ │ └─ 内层 CAS: __atomic_exchange_n(&locked_, 1, ACQUIRE) == 0 → 成功
+ │
+ ├─> 4. 记录属主: cpu_id_ = arch::cpu::id()
+ │
+ ▼
+[执行临界区 C++ 业务逻辑 (关中断、无休眠)]
+ │
+ ▼
+[LockGuard 析构,触发 release()]
+ │
+ ├─> 1. 断言: is_held_by_current_cpu() == true (否则 Panic: invalid release)
+ │
+ ├─> 2. 擦除属主: cpu_id_ = kInvalidCpuId
+ │
+ ├─> 3. __atomic_store_n(&locked_, 0, __ATOMIC_RELEASE)
+ │ └─ RISC-V: fence rw, w + sw zero → 临界区写入全局可见后才释放锁
+ │
+ └─> 4. pop_intr()
+ ├─ lock_depth--
+ └─ if (depth == 0 && intr_was_on) arch::cpu::intr_on()
+``` \ No newline at end of file