summaryrefslogtreecommitdiff
path: root/docs/specs/arch_abstraction.md
blob: a90137c8ffa84652b6068e88c1ea18b0cc51fe5a (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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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 (返回用户态)
```