# DragonOS RCU 架构

## 1. RCU 解决什么问题

RCU（Read-Copy Update）的目标是在读多写少的共享数据上，让读者以很低的成本访问旧版本或新版本，同时保证旧版本只会在所有既有读者都不再使用它之后被回收。

一次典型更新分为三个阶段：

1. 更新者构造新版本，并以原子方式发布；
2. 系统等待一个宽限期，确保发布前已经存在的读侧临界区全部结束；
3. 宽限期结束后执行回调，回收旧版本或完成延迟操作。

```mermaid
flowchart LR
    A[发布新版本] --> B[等待宽限期]
    B --> C[确认既有读者均已离开]
    C --> D[执行回调并回收旧版本]
```

RCU 不保证读者一定看到最新版本。它保证读者看到的版本在整个读侧临界区内仍然有效，并为更新者提供安全回收旧版本的时机。

## 2. DragonOS RCU 的整体结构

DragonOS 当前采用适合非抢占式内核读侧临界区的轻量设计。系统由读侧保护、CPU 上下文跟踪、宽限期协调和回调处理四部分组成。

```mermaid
flowchart TB
    Reader[RCU 读侧临界区]
    Arch[架构入口与退出边界]
    Context[每 CPU 上下文跟踪]
    Scheduler[调度器静止状态报告]
    Coordinator[宽限期协调器]
    GP[宽限期状态]
    Callback[回调队列与执行器]

    Reader -->|限制临界区内调度| Scheduler
    Arch -->|User / Idle / IRQ / NMI 边界| Context
    Context -->|当前状态与静止状态进展| Coordinator
    Scheduler -->|上下文切换静止状态| Coordinator
    Coordinator --> GP
    GP -->|宽限期完成| Callback
```

各层职责如下：

- **读侧保护**：标识读侧临界区，并保证普通非抢占式读者不会在临界区中被调度走。
- **上下文跟踪**：维护每个 CPU 当前是否可能运行普通 RCU 读者，以及该 CPU 是否穿过了静止状态。
- **调度器集成**：把可靠的上下文切换作为静止状态来源之一。
- **宽限期协调**：在宽限期开始时确定需要等待的 CPU，并收集它们后续的静止状态证据。
- **回调处理**：按照宽限期世代组织回调，只在对应宽限期完成后执行。

上下文跟踪不管理回调，宽限期状态不理解架构入口细节，架构代码也不参与宽限期判定。这样的分层避免同一状态在多个子系统中重复维护。

## 3. Watching 与扩展静止状态

RCU 判断 CPU 是否需要被某个宽限期等待，关键不是 CPU 是否正在执行指令，而是它是否可能正在运行普通 RCU 读侧临界区。

DragonOS 将 CPU 上下文分为三种基础状态：

- **Kernel**：CPU 可能运行普通 RCU 读者，称为 RCU watching；
- **User**：CPU 正在用户态，不能运行内核 RCU 读者，属于扩展静止状态；
- **Idle**：CPU 正在空闲等待，不能运行普通 RCU 读者，属于扩展静止状态。

IRQ 和 NMI 是覆盖在基础状态上的临时执行层。它们执行内核代码，因此覆盖期间 CPU 必须视为 watching；只有最外层中断退出后，CPU 才能恢复被打断的 User 或 Idle 扩展静止状态。

```mermaid
stateDiagram-v2
    [*] --> Kernel
    Kernel --> User: 返回用户态
    User --> Kernel: 进入内核
    Kernel --> Idle: 进入空闲等待
    Idle --> Kernel: 离开空闲等待

    state "IRQ / NMI 覆盖层（watching）" as Overlay
    Kernel --> Overlay: 中断进入
    User --> Overlay: 中断进入
    Idle --> Overlay: 中断进入
    Overlay --> Overlay: 嵌套中断
    Overlay --> Kernel: 返回内核执行
    Overlay --> User: 恢复用户态
    Overlay --> Idle: 恢复空闲等待
```

需要长期保持的状态不变量是：

- Kernel 或任意 IRQ/NMI 覆盖层中，CPU 必须是 watching；
- User 或 Idle 只有在不存在 IRQ/NMI 覆盖层时才属于扩展静止状态；
- 进入和退出必须严格配对，只有最外层覆盖退出才允许改变 watching 状态；
- 进入内核后必须先恢复 watching，之后才能运行普通内核逻辑；
- 离开内核前必须先完成所有普通内核工作，最后才能停止 watching。

## 4. 静止状态与进展世代

静止状态（Quiescent State，QS）表示某个 CPU 在该时刻不可能仍处于更早开始的普通 RCU 读侧临界区中。上下文切换、进入 User 和进入 Idle 都能提供这种证据。

每个 CPU 维护自己的静止状态进展世代。CPU 每次从 watching 穿过边界进入扩展静止状态时，世代都会向前推进。宽限期协调器只关心以下两个问题：

1. 宽限期开始时，CPU 是否已经处于扩展静止状态；
2. 如果当时仍然 watching，它的进展世代之后是否发生变化。

这使宽限期不需要持续观察 CPU 的每次活动，也不需要把高频上下文转换都写入全局共享状态。

## 5. 宽限期如何判断完成

宽限期开始时，协调器对在线 CPU 做一次一致性快照：

- 已经处于 User 或 Idle 的 CPU 不加入等待集合；
- 仍然 watching 的 CPU 加入等待集合，同时记录其静止状态世代；
- CPU 后续报告可靠的调度静止状态，或其上下文世代发生变化时，从等待集合移除；
- 等待集合为空时，宽限期完成。

```mermaid
flowchart TD
    Start[开始宽限期] --> Snapshot[快照在线 CPU]
    Snapshot --> Check{CPU 当前是否处于扩展静止状态}
    Check -->|是| Skip[无需等待该 CPU]
    Check -->|否| Wait[记录世代并加入等待集合]
    Wait --> Evidence{获得静止状态证据}
    Evidence -->|上下文切换| Credit[移出等待集合]
    Evidence -->|世代已推进| Credit
    Evidence -->|当前已在扩展静止状态| Credit
    Skip --> Empty{等待集合为空}
    Credit --> Empty
    Empty -->|否| Evidence
    Empty -->|是| Done[完成宽限期]
```

宽限期的正确性依赖两类证明：

- **当前状态证明**：快照时 CPU 已在扩展静止状态，因此它不可能持有更早的普通 RCU 读侧临界区；
- **状态进展证明**：快照时 CPU 仍然 watching，但之后穿过了静止状态，因此快照前存在的读侧临界区已经结束。

两类证明缺一不可。只记录“返回过用户态”无法识别宽限期开始前已经处于用户态的 CPU；只观察当前状态又会遗漏已经穿过静止状态并重新进入内核的 CPU。

## 6. 快照与状态转换的并发闭环

CPU 进入扩展静止状态与宽限期开始可能同时发生。设计必须保证无论两者的先后顺序如何，协调器最终都能获得静止状态证据。

```mermaid
sequenceDiagram
    participant CPU as 目标 CPU
    participant CT as 每 CPU 上下文状态
    participant GP as 宽限期协调器

    GP->>GP: 发布“宽限期活动”
    GP->>CT: 获取状态与进展世代快照
    alt 快照看到扩展静止状态
        GP->>GP: 不等待该 CPU
    else 快照看到 watching
        GP->>GP: 记录世代并等待
    end
    CPU->>CT: 进入扩展静止状态并推进世代
    CPU->>GP: 活动宽限期存在时报告进展
    GP->>CT: 复查状态或世代
    GP->>GP: 确认证据后移出等待集合
```

并发协议满足以下闭环：

- CPU 先进入扩展静止状态时，后续宽限期快照必须能观察到新状态或新世代；
- 宽限期先发布并记录 watching CPU 时，CPU 后续进入扩展静止状态必须触发进展检查；
- 快照与转换重叠时，协调器只能看到转换前或转换后的完整状态，并通过复查消除临界窗口；
- 用于减少无效报告的提示信息只能作为性能过滤，宽限期等待集合始终是正确性的唯一依据。

状态发布、快照和宽限期发布之间必须形成全序可证明的握手。若未来要放宽内存序，必须先给出覆盖所有架构的 happens-before 证明，不能仅凭单次压力测试判断安全。

## 7. 回调生命周期

回调先进入当前 CPU 的局部队列，再由宽限期协调器按段归类。对应的宽限期完成后，回调进入可执行段，由回调执行器分批处理。

```mermaid
flowchart LR
    Admit[本 CPU 提交] --> Next[next: 尚未归类]
    Next --> Wait[wait: 等待当前 GP]
    Next --> NextReady[next-ready: 等待下一 GP]
    NextReady --> Wait
    Wait --> Done[done: GP 已完成]
    Done --> Execute[有界批次执行]
```

这一过程遵循三个原则：

- 回调不得早于目标宽限期完成；
- 每个 CPU 的队列保持先入先出，CPU 之间不保证全局执行顺序；
- 上下文转换只报告宽限期进展，不直接执行回调。

需要等待此前回调全部执行完毕时，应使用 `rcu_barrier()`；`synchronize_rcu()` 只等待宽限期结束。分段模型、屏障和 CPU 迁移的详细设计见 [RCU 分段回调队列](segmented-callback-queues.md)。

## 8. 架构接入原则

各 CPU 架构只负责在可靠的入口和退出边界通知通用 RCU 层：

- 从用户态进入内核时，必须在任何可能使用普通 RCU 的内核逻辑之前恢复 watching；
- 返回用户态时，必须在所有退出工作完成之后停止 watching；
- 进入空闲等待时停止 watching，唤醒并恢复普通内核执行前恢复 watching；
- IRQ/NMI 入口建立 watching 覆盖层，最外层退出按实际返回目标恢复基础状态；
- 嵌套深度和返回目标必须来自真实入口上下文，不能根据当前任务类型或调度策略推测。

架构层不保存第二份 RCU 上下文状态，也不解析通用状态的内部表示。这样可以让 x86_64、RISC-V 及未来架构共享同一套状态语义和宽限期算法。

## 9. 性能与安全约束

RCU 的价值依赖读侧和高频上下文路径足够轻量。DragonOS RCU 遵循以下约束：

- 普通读侧临界区不获取全局锁；
- 每 CPU 上下文转换优先只修改本 CPU 状态；
- 普通 IRQ 嵌套和不产生静止状态的转换不进入宽限期全局慢路径；
- 只有可能推进活动宽限期时才参与全局协调；
- 高频状态按 CPU 隔离，避免不同 CPU 修改同一缓存行；
- 上下文状态转换不分配内存，不执行回调；
- 非法状态转换、嵌套计数错误和非 watching 读侧访问必须可诊断，不能通过静默修正状态掩盖调用错误。

这些约束同时服务于性能和正确性：减少共享写入可以降低缓存争用，职责单一的状态机也更容易审计并发边界。

## 10. 当前设计边界

DragonOS 当前 RCU 面向普通非抢占式内核读者。以下能力不属于这套基础架构：

- 抢占式 RCU 读者跟踪；
- Tree RCU 的层级聚合结构；
- 强制静止状态和 stall detector；
- CPU hotplug 的完整 RCU 生命周期协议；
- Tasks RCU、SRCU 等其他 RCU 类型；
- tick/nohz、虚拟化时间记账等与上下文跟踪相邻但职责不同的功能。

通用模型预留了可嵌套 NMI 的状态语义，但只有具备可返回 NMI 入口并完成延迟进展协议的架构，才能宣称完成 NMI 集成。尚未具备完整入口的架构应保持明确的未支持状态，不能用不可达 hook 代替真实实现。

后续扩展应围绕明确需求增加能力，并继续保持“架构边界、上下文状态、宽限期协调、回调执行”四层职责分离，避免为了接近 Linux Tree RCU 的形态而引入当前没有消费者的复杂度。
