summaryrefslogtreecommitdiff
path: root/docs/specs/global_init.md
blob: cd14dae148d8080d3d66516a9864f65fdb7debb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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()` 主动让出,而是被定时器中断被动抢占进入调度循环。