diff --git a/core/ktracing/src/lib.rs b/core/ktracing/src/lib.rs index 1d2d1f4ee2c0a2e2b81cccabc01a3b33c54d705f..063c564433555c89b86f176f230b773eeb856fbb 100644 --- a/core/ktracing/src/lib.rs +++ b/core/ktracing/src/lib.rs @@ -32,6 +32,11 @@ static TRACE_MANAGER: Once> = Once::new /// Lock adapter used by `ktracepoint` internals. pub struct TraceRawLock(SpinRaw<()>); +// SAFETY: `TraceRawLock` implements the `lock_api::RawMutex` protocol by +// leaking the RAII guard on successful lock acquisition and pairing it with +// `SpinLock::force_unlock` in `unlock`. The tracepoint lock is raw by design: +// callers must use it through `lock_api`, which guarantees `unlock` is called +// only after a successful `lock` or `try_lock`. unsafe impl lock_api::RawMutex for TraceRawLock { type GuardMarker = lock_api::GuardSend; @@ -53,6 +58,9 @@ unsafe impl lock_api::RawMutex for TraceRawLock { } unsafe fn unlock(&self) { + // SAFETY: required by the `RawMutex::unlock` contract. `lock` and + // successful `try_lock` above intentionally forget the guard, so this + // path still owns the raw spin lock and may release its atomic flag. unsafe { self.0.force_unlock() } } @@ -249,9 +257,11 @@ impl KernelTraceOps for Kops { } } - unsafe { - core::ptr::copy_nonoverlapping(data.as_ptr(), ptr.cast::(), data.len()); - } + // SAFETY: the destination range is the kernel text range selected by + // the static-key patching caller. The page range has been temporarily + // made writable above, and the source/destination ranges do not + // overlap. + unsafe { core::ptr::copy_nonoverlapping(data.as_ptr(), ptr.cast::(), data.len()) }; { let mut layout = memspace::kernel_layout().lock(); diff --git a/task/kspin/docs/design.md b/task/kspin/docs/design.md new file mode 100644 index 0000000000000000000000000000000000000000..9f667a13f9081c7a4687d44f9ffc33427677843c --- /dev/null +++ b/task/kspin/docs/design.md @@ -0,0 +1,280 @@ +# kspin - 设计文档 + +## 定位 + +`kspin` 是 x-kernel 的内核自旋锁 crate。 +它提供带 RAII guard 的 `SpinLock`, +并通过 guard 类型在编译期选择进入临界区时是否关闭本地 IRQ、 +是否关闭内核抢占。 + +目标读者是维护调度、IRQ、驱动和底层同步路径的开发者。 + +## 背景 + +内核运行在 `no_std` 环境, +很多路径不能睡眠或不能依赖阻塞式 mutex。 +自旋锁适合短临界区, +但在内核中单纯的 atomic lock 不够: +同一 CPU 上的抢占或中断处理程序可能重入并访问同一数据, +导致死锁或数据竞争。 + +`kspin` 把“锁状态”和“进入临界区前的 CPU 本地保护”拆开: + +- `SpinLock` 负责跨 CPU 互斥。 +- guard 类型负责本地 IRQ / preemption 状态。 + +## 范围 + +涉及的源文件: + +```text +task/kspin/ +├── src/ +│ ├── lib.rs # crate 文档、公开 re-export 和类型别名 +│ ├── lock.rs # SpinLock 与 SpinLockGuard +│ ├── guard/ +│ │ ├── mod.rs # BaseGuard 与 KernelGuardIf +│ │ ├── types.rs # NoOp/IrqSave/NoPreempt/NoPreemptIrqSave +│ │ └── arch/mod.rs # karch IRQ save/restore 适配 +│ └── tests.rs # unittest 测试 +├── README.md # crate-level rustdoc 内容 +├── Cargo.toml +└── docs/ + ├── design.md + └── security.md +``` + +## 架构 + +```text +caller + │ + │ lock() + v +┌─────────────────────────────────────────────┐ +│ SpinLock │ +│ marker: PhantomData │ +│ flag: AtomicBool (feature = smp) │ +│ storage: UnsafeCell │ +└──────────────────┬──────────────────────────┘ + │ G::acquire() + v +┌─────────────────────────────────────────────┐ +│ BaseGuard implementation │ +│ NoOp / NoPreempt / IrqSave │ +│ NoPreemptIrqSave │ +└──────────────────┬──────────────────────────┘ + │ acquired + v +┌─────────────────────────────────────────────┐ +│ SpinLockGuard<'_, G, T> │ +│ guard_state: G::State │ +│ ptr: *mut T │ +│ flag_ref: &AtomicBool (feature = smp) │ +└─────────────────────────────────────────────┘ +``` + +| 组件 | 职责 | +|------|------| +| `SpinLock` | 保存被保护数据和可选 SMP atomic 状态 | +| `SpinLockGuard` | 持锁期间提供 `Deref` / `DerefMut`,drop 时释放锁并恢复 guard 状态 | +| `BaseGuard` | 抽象进入/退出本地临界区的 acquire/release 协议 | +| `KernelGuardIf` | `preempt` feature 下由调度器提供 enable/disable preempt 接口 | +| `NoOp` | 不执行本地保护,适合调用方已关闭 IRQ/抢占的上下文 | +| `IrqSave` | 保存并关闭本地 IRQ,drop 时恢复 | +| `NoPreempt` | 关闭内核抢占,drop 时恢复 | +| `NoPreemptIrqSave` | 先关闭抢占再保存关闭 IRQ,释放时先恢复 IRQ 再恢复抢占 | + +## 模块边界自审 + +`kspin` 的职责保持在“底层自旋锁与本地临界区 guard”: + +- 不实现调度器逻辑, + 抢占开关只通过 `KernelGuardIf` 由 `ktask` 等上层提供。 +- 不直接操作架构寄存器, + IRQ save/restore 经 `karch` 统一封装。 +- 不封装睡眠等待、条件变量、读写锁或 semaphore, + 这些由 `ksync` 等更高层同步 crate 承担。 +- 不依赖 `ktask`、`kservices`、`ktracing` 或驱动子系统, + 依赖方向是这些上层 crate 使用 `kspin`。 + +公开 API 自审结果: + +- 保留 `SpinLock`、`SpinLockGuard`、`BaseGuard`、`KernelGuardIf` + 和 `SpinRaw` / `SpinNoPreempt` / `SpinNoIrq` 类型别名。 +- `guard::arch` 是私有模块, + IRQ save/restore helper 没有跨 crate 暴露。 +- 未接入模块树的旧重复实现 `src/base.rs` 已删除, + 避免出现第二套 unsafe spinlock API 和重复审计面。 +- `force_unlock` 保持 `unsafe fn`, + 只服务 `lock_api::RawMutex` 这类无法持有 RAII guard 的适配场景。 + +## 状态机 + +### SMP lock 状态 + +```text +Unlocked(flag = false) + │ compare_exchange(false -> true, Acquire) + v +Locked(flag = true) + │ SpinLockGuard::drop() + │ store(false, Release) + v +Unlocked +``` + +| 从 | 到 | 触发条件 | +|----|----|----------| +| Unlocked | Locked | `lock` 或 `try_lock` CAS 成功 | +| Locked | Locked | `lock` CAS 失败后自旋等待 | +| Locked | Unlocked | `SpinLockGuard::drop` 执行 Release store | +| Locked | Unlocked | `unsafe force_unlock` 在调用者证明持锁时强制释放 | + +### 非 SMP lock 状态 + +```text +SingleCpu + │ lock / try_lock + v +GuardedByLocalGuard + │ drop + v +SingleCpu +``` + +未启用 `smp` feature 时, +atomic flag 被编译移除。 +互斥依赖 guard 类型提供的本地不可重入保证, +例如关闭抢占或 IRQ。 + +### `NoPreemptIrqSave` 状态顺序 + +```text +Normal + │ disable_preempt + v +PreemptDisabled + │ save_disable_irq + v +PreemptDisabledIrqDisabled + │ restore_irq + v +PreemptDisabled + │ enable_preempt + v +Normal +``` + +该顺序避免在 IRQ 已恢复但抢占状态尚未一致时被调度出去。 + +## 算法流程 + +### `lock` + +1. 调用 `G::acquire()`,进入本地临界区并保存 guard 状态。 +2. 启用 `smp` 时,通过 `compare_exchange_weak(false, true, Acquire, Relaxed)` 尝试抢锁。 +3. CAS 失败时,在 `is_locked()` 为真期间执行 `spin_loop()`。 +4. 成功后从 `UnsafeCell` 取出指针, + 构造 `SpinLockGuard`。 +5. 调用者通过 guard 的 `Deref` / `DerefMut` 访问数据。 + +### `try_lock` + +1. 先执行 `G::acquire()`。 +2. 启用 `smp` 时用强 CAS 尝试把 flag 从 false 改为 true。 +3. 成功则返回 guard。 +4. 失败则立即执行 `G::release(guard_state)`, + 恢复本地 IRQ / preemption 状态并返回 `None`。 + +### `SpinLockGuard::drop` + +1. 启用 `smp` 时以 `Release` store 把 flag 写回 false。 +2. 调用 `G::release(guard_state)` 恢复本地状态。 + +释放顺序保证其他 CPU 在 Acquire CAS 成功后能看到临界区内的写入。 + +### `get_mut` + +`get_mut(&mut self)` 依赖 Rust 独占借用, +不需要设置 atomic flag。 +因为调用者持有 `&mut SpinLock`, +不可能同时存在任何 guard 或其他引用。 + +### `force_unlock` + +`force_unlock` 只清除 SMP flag, +不会恢复 guard 状态, +也不会 drop 被泄漏的 guard。 +它仅用于 FFI 或测试等 RAII 无法表达的边界, +调用者必须证明当前执行路径确实持有该锁。 + +## 并发模型 + +锁策略: + +- `smp` feature 开启时,跨 CPU 互斥由 `AtomicBool` 提供。 +- `smp` feature 关闭时,不存在跨 CPU 竞争, + 互斥依赖 guard 阻止同 CPU 抢占或 IRQ 重入。 +- `SpinRaw` 不提供本地保护, + 只能在调用方已经保证不可重入的上下文使用。 +- `SpinNoIrq` 是默认最保守选择, + 可覆盖普通任务上下文和 IRQ 上下文访问同一数据的场景。 + +内存序: + +- 成功 acquire 使用 `Ordering::Acquire`。 +- unlock 使用 `Ordering::Release`。 +- 自旋观察 `is_locked` 使用 `Ordering::Relaxed`, + 只作为等待提示,不承载同步。 + +## Cargo Features + +| Feature | 作用 | +|---------|------| +| `smp` | 启用 `AtomicBool` lock flag,实现跨 CPU 互斥 | +| `preempt` | 通过 `KernelGuardIf` 调用调度器的抢占开关 | + +未启用 `preempt` 时, +`NoPreempt` 和 `NoPreemptIrqSave` 的抢占开关部分会被编译为空操作。 + +## 设计决策 + +### 为何 guard 是类型参数 + +guard 类型参数把临界区策略编码进类型: +同一个 `SpinLock` 实例的本地保护方式不能在运行时被误改。 +调用点从类型别名即可看出锁适合的上下文。 + +### 为何单核移除 atomic flag + +在无 `smp` 配置下没有其他 CPU 竞争。 +保留 atomic flag 只会增加指令和存储开销。 +互斥问题退化为本 CPU 是否会被抢占或中断重入, +由 guard 负责。 + +### 为何 `try_lock` 失败也要先 acquire guard + +如果不先关闭本地抢占或 IRQ, +在检查 lock flag 和返回之间可能被本地中断/抢占路径重入。 +先 acquire guard 可以让成功和失败路径都处于一致的本地临界区模型。 +失败时立即 release,避免泄露 IRQ/preemption 状态。 + +### 为何存在 `force_unlock` + +少数 FFI 或测试场景无法让 RAII guard 正常 drop。 +`force_unlock` 保留逃生口, +但以 `unsafe fn` 暴露, +并在安全文档中要求调用者证明当前路径持锁。 + +## Drop / 资源释放 + +`SpinLock` 没有自定义 `Drop`。 +当 lock 被销毁时, +被保护的 `T` 按普通 Rust 所有权规则 drop。 + +`SpinLockGuard::drop` 是核心释放路径: +它释放 SMP flag 并恢复 guard 状态。 +若 guard 被 `mem::forget`, +锁保持 locked 状态, +除非调用者使用 `unsafe force_unlock`。 diff --git a/task/kspin/docs/security.md b/task/kspin/docs/security.md new file mode 100644 index 0000000000000000000000000000000000000000..5907508b668eb2f8266c7c7904e7283208e89635 --- /dev/null +++ b/task/kspin/docs/security.md @@ -0,0 +1,325 @@ +# kspin - 安全与可靠性分析 + +## 概述 + +`kspin` 是底层同步原语, +包含 `UnsafeCell` 解引用、裸指针 guard、`Send`/`Sync` unsafe impl +以及 `force_unlock` 这样的显式 unsafe API。 +如果锁获取、释放、本地 IRQ/抢占保护或内存序错误, +可能导致数据竞争、死锁、IRQ 状态泄露或跨 CPU 可见性错误。 + +## 信任模型 + +```text +kernel subsystem caller + │ + │ safe API: + │ SpinLock::{new,lock,try_lock,is_locked,get_mut,into_inner} + │ SpinLockGuard::{Deref,DerefMut,Drop} + │ + │ unsafe API: + │ SpinLock::force_unlock + v +┌─────────────────────────────────────────────┐ +│ kspin │ +│ │ +│ unsafe boundary │ +│ ├─ UnsafeCell -> *mut T -> &/&mut T │ +│ ├─ unsafe impl Send/Sync │ +│ ├─ AtomicBool Acquire/Release protocol │ +│ └─ force_unlock caller contract │ +└──────────────────┬──────────────────────────┘ + │ BaseGuard::acquire/release + v +karch IRQ state / ktask preemption interface +``` + +- safe API 调用者信任 `kspin` 在 guard 生命周期内提供唯一 mutable access。 +- `kspin` 信任 `BaseGuard` 实现的 acquire/release 成对且可嵌套语义正确。 +- `kspin` 信任 `KernelGuardIf` 的 preemption 开关不会睡眠或破坏当前 CPU 状态。 +- `force_unlock` 调用者必须自行证明当前执行路径确实持有该锁。 + +## unsafe 代码清单 + +### 1. `SpinLock` 的 `Sync` 实现 + +位置:`src/lock.rs` + +```rust +unsafe impl Sync for SpinLock {} +``` + +不变量: + +- 共享访问 `SpinLock` 时, + 内部 `T` 只能通过持锁的 `SpinLockGuard` 被引用。 +- 启用 `smp` 时, + `AtomicBool` flag 对 guard 创建进行跨 CPU 串行化。 +- 未启用 `smp` 时, + 调用者选择的 guard 必须阻止同 CPU 重入。 + +为何安全: + +- `lock` 和 `try_lock` 只有在 acquire 成功后才从 `UnsafeCell` 创建数据指针。 +- `SpinLockGuard::drop` 释放 flag 并恢复 guard 状态。 +- `T: Send` 与 `std::sync::Mutex` 的共享条件一致。 + +### 2. `SpinLock` 的 `Send` 实现 + +位置:`src/lock.rs` + +```rust +unsafe impl Send for SpinLock {} +``` + +不变量: + +- 移动锁时不会同时保留旧位置的有效访问入口。 +- 被保护数据可跨线程转移所有权。 + +为何安全: + +- Rust 所有权移动保证旧 `SpinLock` 位置不可再被 safe code 使用。 +- 移动后访问仍由同一 guard 和 atomic 协议保护。 + +### 3. `lock` 成功后从 `UnsafeCell` 创建指针 + +位置:`src/lock.rs` + +```rust +ptr: unsafe { &mut *self.storage.get() }, +``` + +不变量: + +- 当前 guard 已经执行 `G::acquire()`。 +- 启用 `smp` 时当前 CPU 已成功把 flag 从 false 改为 true。 +- guard drop 前不会创建另一个 mutable reference。 + +为何安全: + +- 成功持锁后, + `SpinLockGuard` 是访问 `T` 的唯一入口。 +- 指针只保存在 guard 中, + 由 guard 生命周期约束引用。 + +### 4. `try_lock` 成功后从 `UnsafeCell` 创建指针 + +位置:`src/lock.rs` + +不变量与 `lock` 相同, +区别是 `try_lock` 使用强 CAS。 +失败路径会调用 `G::release(guard_state)`, +不创建数据指针。 + +### 5. `get_mut` + +位置:`src/lock.rs` + +```rust +unsafe { &mut *self.storage.get() } +``` + +不变量: + +- 调用者持有 `&mut SpinLock`。 +- Rust 独占借用保证不存在其他 guard 或共享引用。 + +为何安全: + +- 不需要 runtime lock; + 编译期独占借用已经证明没有并发访问。 + +### 6. `SpinLockGuard::deref` + +位置:`src/lock.rs` + +```rust +unsafe { &*self.ptr } +``` + +不变量: + +- `ptr` 来自成功持锁后的 `UnsafeCell`。 +- 返回引用不能超过 guard 生命周期。 + +为何安全: + +- `Deref` 借用 `&self`, + 共享引用只在 guard 仍持锁期间存在。 + +### 7. `SpinLockGuard::deref_mut` + +位置:`src/lock.rs` + +```rust +unsafe { &mut *self.ptr } +``` + +不变量: + +- 当前 guard 以 `&mut self` 被独占借用。 +- guard 仍持有锁。 + +为何安全: + +- `&mut self` 防止同一 guard 同时创建多个 mutable reference。 +- 其他 guard 被 lock 协议排除。 + +### 8. `SpinLock::force_unlock` + +位置:`src/lock.rs` + +```rust +pub unsafe fn force_unlock(&self) +``` + +不变量: + +- 当前执行路径已经持有该锁。 +- 调用后不会继续使用被泄漏 guard 创建出的引用。 +- 调用者负责处理 guard 状态, + 因为 `force_unlock` 只清除 SMP flag,不恢复 IRQ/preemption state。 + +为何安全: + +- 该函数本身仅执行 Release store。 +- 安全性完全依赖调用者契约, + 因此必须保持 `unsafe fn`。 + +外部调用者审查: + +- `core/ktracing::TraceRawLock` 实现 `lock_api::RawMutex` 时调用该 API。 + 其 `lock` / `try_lock` 在成功后 `mem::forget` guard, + `unlock` 由 `lock_api` 契约保证只在持锁后调用。 + 调用点已补充 `SAFETY:` 注释。 + +### 9. `tests.rs` fake guard counter + +位置:`src/tests.rs` + +测试中的 `static mut IRQ_CNT` 用于验证 fake guard acquire/release 是否配对。 +这些 unsafe 访问只存在于 `#[cfg(unittest)]` 测试代码, +并已在每个 unsafe 块前注明测试前提。 + +## 内存安全不变量 + +1. **guard 唯一访问**: + 任何 `&T` / `&mut T` 都必须从 `SpinLockGuard` 派生。 +2. **flag 与 pointer 创建绑定**: + 启用 `smp` 时, + 只有成功 CAS 后才能创建 `ptr`。 +3. **unlock 释放写入**: + guard drop 使用 `Release` store, + 下一个持锁者通过 `Acquire` CAS 观察临界区写入。 +4. **失败路径恢复 guard 状态**: + `try_lock` 失败必须调用 `G::release`。 +5. **`force_unlock` 不恢复本地 guard**: + 调用者不得把它当作普通 unlock 使用。 +6. **`SpinRaw` 需要外部不可重入保证**: + `NoOp` 不关闭抢占或 IRQ。 + +## 线程安全 + +| 类型 | Send 条件 | Sync 条件 | +|------|-----------|-----------| +| `SpinLock` | unsafe impl when `T: Send` | unsafe impl when `T: Send`,依赖 guard + atomic 协议 | +| `SpinLockGuard<'_, G, T>` | 不显式实现;由字段决定 | guard 持有裸指针,不应跨线程共享访问 | +| `NoOp` | zero-sized | zero-sized | +| `IrqSave` | 保存 IRQ flags | 只应在当前 CPU 上 acquire/release | +| `NoPreempt` | zero-sized | 依赖 `KernelGuardIf` | +| `NoPreemptIrqSave` | 保存 IRQ flags | 依赖 `KernelGuardIf` + `karch` IRQ restore | + +## 威胁分析 + +| 编号 | 威胁描述 | 影响等级 | 触发条件 | 应对措施 | +|------|----------|----------|----------|----------| +| T-01 | 未持锁创建 `&mut T` 导致数据竞争 | 高 | `UnsafeCell` 解引用在 CAS 前发生 | 代码只在成功获取 guard 后创建指针;`get_mut` 依赖 `&mut self` | +| T-02 | `SpinRaw` 在可抢占或 IRQ 可重入上下文使用 | 高 | 调用方误选 `SpinRaw` 保护 IRQ 共享数据 | 类型别名文档明确限制;默认推荐 `SpinNoIrq` | +| T-03 | `try_lock` 失败泄露 IRQ/preemption 禁用状态 | 高 | 失败路径忘记 `G::release` | 当前失败路径立即 release;单元测试覆盖 fake guard 恢复 | +| T-04 | unlock 内存序过弱导致写入不可见 | 高 | Release/Acquire 协议被降级 | unlock 使用 Release;成功 lock 使用 Acquire | +| T-05 | guard 被 `mem::forget` 后锁永久保持 locked | 中 | 调用方泄漏 guard | RAII 正常路径自动释放;`force_unlock` 仅作为 unsafe 逃生口 | +| T-06 | `force_unlock` 被非持锁者调用 | 高 | FFI、`lock_api` adapter 或测试误用 unsafe API | `force_unlock` 为 unsafe fn;已审查 `TraceRawLock` 和 unittest 调用点 | +| T-07 | `BaseGuard` acquire/release 顺序错误 | 高 | 新增 guard 未正确保存/恢复 IRQ 或抢占 | guard trait 集中抽象;`NoPreemptIrqSave` 固定先关抢占、再关 IRQ,释放反序 | +| T-08 | Debug 输出在持锁时递归尝试 lock | 低 | 对已锁对象格式化 | `Debug` 使用 `try_lock`,失败输出 `` | +| T-09 | 长临界区导致 CPU 自旋占用 | 中 | 持锁执行阻塞、I/O 或复杂循环 | 文档限定自旋锁用于短临界区;调用方审计热点 | +| T-10 | 未启用 `preempt` feature 却依赖 `NoPreempt` | 中 | 配置错误 | feature 表说明;平台 defconfig 需匹配调度模型 | + +影响等级定义: + +- 高:导致 UB、内存破坏、权限提升。 +- 中:导致 panic、服务不可用、数据不一致。 +- 低:导致性能退化、日志丢失、功能降级。 + +## 故障模式与影响分析 + +| 编号 | 故障模式 | 故障原因 | 局部影响 | 系统影响 | 严重度 | 应对措施 | +|------|----------|----------|----------|----------|--------|----------| +| F-01 | 死锁 | 同一上下文重复获取同一 non-reentrant 锁 | 当前 CPU 自旋 | 可能全系统卡死 | 1 | 避免递归锁;`lock` 文档标注可能死锁 | +| F-02 | IRQ 状态未恢复 | guard release 漏调或 guard 泄漏 | 本 CPU IRQ 关闭 | 定时器和设备中断停滞 | 1 | RAII drop;`try_lock` 失败释放;测试覆盖 fake guard | +| F-03 | 抢占状态未恢复 | `KernelGuardIf` 实现错误 | 当前任务不可抢占 | 调度延迟或卡死 | 2 | `preempt` feature 下由调度器集中实现接口 | +| F-04 | 自旋时间过长 | 临界区过大或持锁者卡死 | CPU 占用升高 | 系统延迟上升 | 2 | 仅用于短临界区;避免持锁 I/O | +| F-05 | `force_unlock` 后继续使用泄漏 guard | unsafe 调用者破坏契约 | 两个 guard 同时访问数据 | 数据竞争或内存破坏 | 1 | 保持 unsafe API;审计所有调用点 | +| F-06 | 单核配置下误以为有跨 CPU 互斥 | `smp` feature 未启用但运行在多核 | 无 atomic flag | 数据竞争 | 1 | defconfig 必须与 CPU 拓扑一致 | +| F-07 | Debug 输出隐藏锁内数据 | `try_lock` 失败 | 只能看到 `` | 调试信息降级 | 4 | 避免 Debug 阻塞或递归死锁 | + +严重度定义: + +- 1:致命,系统崩溃、数据丢失。 +- 2:严重,功能不可用,需重启恢复。 +- 3:一般,功能降级,可自动恢复。 +- 4:轻微,影响有限,用户可容忍。 + +## 故障管理 + +- `lock` 不返回错误; + 竞争时持续自旋。 +- `try_lock` 用 `Option` 表示成功或失败, + 失败时已经恢复 guard 状态。 +- `force_unlock` 不做 runtime 检查, + 误用属于 unsafe 契约违反。 +- 本 crate 不记录日志, + 避免在临界区和低层同步路径引入额外依赖。 + +## 隐私分析 + +`kspin` 不直接处理用户数据。 +它保护的 `T` 可能包含任意内核或用户相关数据, +但该 crate 不读取、不复制、不打印 `T`, +除了 `Debug` 实现会在调用者显式格式化且成功取锁时格式化内部数据。 + +## 已知限制 + +1. **非公平锁**: + 当前实现没有队列或优先级继承。 +2. **无死锁检测**: + 重入同一锁会自旋或死锁。 +3. **无抢占 feature 时 `NoPreempt` 为空操作**: + 配置必须与调度模型一致。 +4. **`force_unlock` 只释放 atomic flag**: + 不恢复 IRQ/preemption guard state。 +5. **自旋锁不适合长临界区**: + 长时间持锁会放大中断延迟和 CPU 占用。 + +## 其它说明(模板章节) + +| 章节 | 说明 | +|------|------| +| 基线 | 以本仓库 `docs/templates/module-docs-guide.md` 及 `AGENTS.md` 为准 | +| 冗余设计 | 无 | +| 过载控制 | 无;调用方必须保持临界区短 | +| 人因差错 | 无直接用户交互 | +| 故障预测预防 | 无 | +| 升级不中断业务 | 无 | + +## 审计清单 + +修改 `kspin` 时需验证: + +- [ ] 每个新增 unsafe 块、unsafe impl 或 unsafe API 都有前置 `SAFETY:` 注释。 +- [ ] `try_lock` 失败路径恢复 guard 状态。 +- [ ] lock/unlock 的 Acquire/Release 内存序不被削弱。 +- [ ] 新增 guard 的 acquire/release 顺序成对且可审计。 +- [ ] `SpinRaw` 的新调用点已经有外部 IRQ/抢占不可重入保证。 +- [ ] 不在持有自旋锁期间执行可能睡眠、阻塞 I/O 或长时间循环的操作。 +- [ ] defconfig 的 `smp` / `preempt` feature 与实际平台模型一致。 diff --git a/task/kspin/src/base.rs b/task/kspin/src/base.rs deleted file mode 100644 index 579dd493634a3d7c4e2b38f2c537368dd9b71564..0000000000000000000000000000000000000000 --- a/task/kspin/src/base.rs +++ /dev/null @@ -1,450 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2025 KylinSoft Co., Ltd. -// See LICENSES for license details. - -//! A naïve spinning mutex. -//! -//! Waiting threads hammer an atomic variable until it becomes available. Best-case latency is low, but worst-case -//! latency is theoretically infinite. -//! -//! Based on [`spin::Mutex`](https://docs.rs/spin/latest/src/spin/mutex/spin.rs.html). - -#[cfg(feature = "smp")] -use core::sync::atomic::{AtomicBool, Ordering}; -use core::{ - cell::UnsafeCell, - fmt, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use crate::guard::BaseGuard; - -/// A [spin lock](https://en.m.wikipedia.org/wiki/Spinlock) providing mutually -/// exclusive access to data. -/// -/// This is a base struct, the specific behavior depends on the generic -/// parameter `G` that implements [`BaseGuard`], such as whether to disable -/// local IRQs or kernel preemption before acquiring the lock. -/// -/// For single-core environment (without the "smp" feature), we remove the lock -/// state, CPU can always get the lock if we follow the proper guard in use. -pub struct BaseSpinLock { - guard_kind: PhantomData, - #[cfg(feature = "smp")] - busy_flag: AtomicBool, - storage: UnsafeCell, -} - -/// A guard that provides mutable data access. -/// -/// When the guard falls out of scope it will release the lock. -pub struct BaseSpinLockGuard<'a, G: BaseGuard, T: ?Sized + 'a> { - guard_type: &'a PhantomData, - irq_token: G::State, - slot: *mut T, - #[cfg(feature = "smp")] - flag_ref: &'a AtomicBool, -} - -// Same unsafe impls as `std::sync::Mutex` -unsafe impl Sync for BaseSpinLock {} -unsafe impl Send for BaseSpinLock {} - -impl BaseSpinLock { - /// Creates a new [`BaseSpinLock`] wrapping the supplied data. - #[inline(always)] - pub const fn new(data: T) -> Self { - Self { - guard_kind: PhantomData, - storage: UnsafeCell::new(data), - #[cfg(feature = "smp")] - busy_flag: AtomicBool::new(false), - } - } - - /// Consumes this [`BaseSpinLock`] and unwraps the underlying data. - #[inline(always)] - pub fn into_inner(self) -> T { - // We know statically that there are no outstanding references to - // `self` so there's no need to lock. - let BaseSpinLock { storage, .. } = self; - storage.into_inner() - } -} - -impl BaseSpinLock { - /// Locks the [`BaseSpinLock`] and returns a guard that permits access to the inner data. - /// - /// The returned value may be dereferenced for data access - /// and the lock will be dropped when the guard falls out of scope. - #[inline(always)] - pub fn lock(&self) -> BaseSpinLockGuard<'_, G, T> { - let irq_state = G::acquire(); - #[cfg(feature = "smp")] - { - // Fast path: optimistic attempt; if it fails, spin until the flag - // becomes available again. - loop { - if self - .busy_flag - .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - { - break; - } - while self.is_locked() { - core::hint::spin_loop(); - } - } - } - BaseSpinLockGuard { - guard_type: &PhantomData, - irq_token: irq_state, - slot: unsafe { &mut *self.storage.get() }, - #[cfg(feature = "smp")] - flag_ref: &self.busy_flag, - } - } - - /// Returns `true` if the lock is currently held. - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - #[inline(always)] - pub fn is_locked(&self) -> bool { - cfg_if::cfg_if! { - if #[cfg(feature = "smp")] { - self.busy_flag.load(Ordering::Relaxed) - } else { - false - } - } - } - - /// Try to lock this [`BaseSpinLock`], returning a lock guard if successful. - #[inline(always)] - pub fn try_lock(&self) -> Option> { - let irq_state = G::acquire(); - - cfg_if::cfg_if! { - if #[cfg(feature = "smp")] { - // Strong CAS avoids spurious failures in the contended fast-path. - let is_unlocked = self - .busy_flag - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok(); - } else { - let is_unlocked = true; - } - } - - if is_unlocked { - Some(BaseSpinLockGuard { - guard_type: &PhantomData, - irq_token: irq_state, - slot: unsafe { &mut *self.storage.get() }, - #[cfg(feature = "smp")] - flag_ref: &self.busy_flag, - }) - } else { - G::release(irq_state); - None - } - } - - /// Force unlock this [`BaseSpinLock`]. - /// - /// # Safety - /// - /// This is *extremely* unsafe if the lock is not held by the current - /// thread. However, this can be useful in some instances for exposing the - /// lock to FFI that doesn't know how to deal with RAII. - #[inline(always)] - pub unsafe fn force_unlock(&self) { - #[cfg(feature = "smp")] - self.busy_flag.store(false, Ordering::Release); - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the [`BaseSpinLock`] mutably, and a mutable reference is guaranteed to be exclusive in - /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As - /// such, this is a 'zero-cost' operation. - #[inline(always)] - pub fn get_mut(&mut self) -> &mut T { - // We know statically that there are no other references to `self`, so - // there's no need to lock the inner mutex. - unsafe { &mut *self.storage.get() } - } -} - -impl Default for BaseSpinLock { - #[inline(always)] - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl fmt::Debug for BaseSpinLock { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try_lock() { - Some(guard) => write!(f, "SpinLock {{ data: ") - .and_then(|()| (*guard).fmt(f)) - .and_then(|()| write!(f, "}}")), - None => write!(f, "SpinLock {{ }}"), - } - } -} - -impl Deref for BaseSpinLockGuard<'_, G, T> { - type Target = T; - - #[inline(always)] - fn deref(&self) -> &T { - // We know statically that only we are referencing data - unsafe { &*self.slot } - } -} - -impl DerefMut for BaseSpinLockGuard<'_, G, T> { - #[inline(always)] - fn deref_mut(&mut self) -> &mut T { - // We know statically that only we are referencing data - unsafe { &mut *self.slot } - } -} - -impl fmt::Debug for BaseSpinLockGuard<'_, G, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl Drop for BaseSpinLockGuard<'_, G, T> { - /// The dropping of the [`BaseSpinLockGuard`] will release the lock it was - /// created from. - #[inline(always)] - fn drop(&mut self) { - #[cfg(feature = "smp")] - self.flag_ref.store(false, Ordering::Release); - G::release(self.irq_token); - } -} - -#[cfg(test)] -mod tests { - use std::{ - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - mpsc::channel, - }, - thread, - }; - - use super::*; - - struct TestGuardIrq; - - static mut IRQ_CNT: u32 = 0; - impl BaseGuard for TestGuardIrq { - type State = u32; - - fn acquire() -> Self::State { - unsafe { - IRQ_CNT += 1; - IRQ_CNT - } - } - - fn release(_: Self::State) { - unsafe { - IRQ_CNT -= 1; - } - } - } - - type TestSpinIrq = BaseSpinLock; - type SpinMutex = crate::SpinRaw; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn basic_lock_unlock() { - let simple_lock = SpinMutex::<_>::new(()); - drop(simple_lock.lock()); - drop(simple_lock.lock()); - } - - #[test] - #[cfg(feature = "smp")] - fn lots_and_lots() { - static GLOBAL_MUTEX: SpinMutex<()> = SpinMutex::<_>::new(()); - static mut COUNTER: u32 = 0; - const INNER_ITERS: u32 = 1000; - const THREAD_PAIRS: u32 = 3; - - fn bump_shared_counter() { - for _ in 0..INNER_ITERS { - unsafe { - let guard = GLOBAL_MUTEX.lock(); - COUNTER += 1; - core::mem::drop(guard); - } - } - } - - let (sender, receiver) = channel(); - let mut worker_handles = Vec::new(); - for _ in 0..THREAD_PAIRS { - let notifier1 = sender.clone(); - worker_handles.push(thread::spawn(move || { - bump_shared_counter(); - notifier1.send(()).unwrap(); - })); - let notifier2 = sender.clone(); - worker_handles.push(thread::spawn(move || { - bump_shared_counter(); - notifier2.send(()).unwrap(); - })); - } - - drop(sender); - for _ in 0..(2 * THREAD_PAIRS) { - receiver.recv().unwrap(); - } - assert_eq!(unsafe { COUNTER }, INNER_ITERS * THREAD_PAIRS * 2); - - for handle in worker_handles { - handle.join().unwrap(); - } - } - - #[test] - #[cfg(feature = "smp")] - fn try_lock() { - let guarded_value = SpinMutex::<_>::new(42); - - // First attempt should succeed - let first = guarded_value.try_lock(); - assert_eq!(first.as_ref().map(|r| **r), Some(42)); - - // Second simultaneous attempt must fail - let second = guarded_value.try_lock(); - assert!(second.is_none()); - - // After releasing the first guard, a new attempt should succeed - ::core::mem::drop(first); - let third = guarded_value.try_lock(); - assert_eq!(third.as_ref().map(|r| **r), Some(42)); - } - - #[test] - fn test_irq_lock_restored() { - let irq_lock = TestSpinIrq::new(()); - let guard = irq_lock.lock(); - assert_eq!(unsafe { IRQ_CNT }, 1); - ::core::mem::drop(guard); - assert_eq!(unsafe { IRQ_CNT }, 0); - } - - #[test] - #[cfg(feature = "smp")] - fn test_irq_try_lock_failed() { - let irq_guarded = TestSpinIrq::new(()); - let primary = irq_guarded.lock(); - assert_eq!(unsafe { IRQ_CNT }, 1); - let competing = irq_guarded.try_lock(); - assert!(competing.is_none()); - assert_eq!(unsafe { IRQ_CNT }, 1); - drop(primary); - } - - #[test] - fn test_into_inner() { - let wrapper = SpinMutex::<_>::new(NonCopy(10)); - assert_eq!(wrapper.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct DropCounter(Arc); - impl Drop for DropCounter { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let drops = Arc::new(AtomicUsize::new(0)); - let mutex = SpinMutex::<_>::new(DropCounter(drops.clone())); - assert_eq!(drops.load(Ordering::SeqCst), 0); - { - let inner = mutex.into_inner(); - assert_eq!(drops.load(Ordering::SeqCst), 0); - core::mem::drop(inner); - } - assert_eq!(drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_mutex_arc_nested() { - // Exercise nested spin locks behind `Arc` and validate access to inner data. - let outer = Arc::new(SpinMutex::<_>::new(1)); - let nested = Arc::new(SpinMutex::<_>::new(outer)); - let (sender, receiver) = channel(); - let worker = thread::spawn(move || { - let first_guard = nested.lock(); - let second_guard = first_guard.lock(); - assert_eq!(*second_guard, 1); - sender.send(()).unwrap(); - }); - receiver.recv().unwrap(); - worker.join().unwrap(); - } - - #[test] - fn test_mutex_arc_access_in_unwind() { - let shared = Arc::new(SpinMutex::<_>::new(1)); - let captured = shared.clone(); - let _ = thread::spawn(move || { - struct Unwinder { - handle: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - *self.handle.lock() += 1; - } - } - let _scope = Unwinder { handle: captured }; - panic!(); - }) - .join(); - let final_guard = shared.lock(); - assert_eq!(*final_guard, 2); - } - - #[test] - fn test_mutex_unsized() { - let slice_mutex: &SpinMutex<[i32]> = &SpinMutex::<_>::new([1, 2, 3]); - { - let slice = &mut *slice_mutex.lock(); - slice[0] = 4; - slice[2] = 5; - } - let expected: &[i32] = &[4, 2, 5]; - assert_eq!(&*slice_mutex.lock(), expected); - } - - #[test] - fn test_mutex_force_lock() { - let raw_lock = SpinMutex::<_>::new(()); - ::std::mem::forget(raw_lock.lock()); - unsafe { - raw_lock.force_unlock(); - } - assert!(raw_lock.try_lock().is_some()); - } -} diff --git a/task/kspin/src/lock.rs b/task/kspin/src/lock.rs index 82bd3d97804316a7069704c2da121e1c78c9acc1..f8e7947f3176f25f13db20b93bada6f063511655 100644 --- a/task/kspin/src/lock.rs +++ b/task/kspin/src/lock.rs @@ -62,8 +62,12 @@ pub struct SpinLockGuard<'a, G: BaseGuard, T: ?Sized + 'a> { flag_ref: &'a AtomicBool, } -// Same unsafe impls as `std::sync::Mutex` +// SAFETY: `SpinLock` only exposes shared access to `T` through guards that +// acquire the lock before creating references from the internal `UnsafeCell`. +// Moving the lock to another thread is sound when the protected value is `Send`. unsafe impl Sync for SpinLock {} +// SAFETY: ownership transfer of the lock transfers ownership of the protected +// storage. Access remains mediated by the same guard protocol after the move. unsafe impl Send for SpinLock {} impl SpinLock { @@ -116,6 +120,8 @@ impl SpinLock { SpinLockGuard { _token: &PhantomData, guard_state, + // SAFETY: the guard has acquired the spinlock and will release it + // on drop, so this guard is the unique mutable accessor until then. ptr: unsafe { &mut *self.storage.get() }, #[cfg(feature = "smp")] flag_ref: &self.flag, @@ -160,6 +166,8 @@ impl SpinLock { Some(SpinLockGuard { _token: &PhantomData, guard_state, + // SAFETY: successful acquisition gives this guard exclusive + // access to the protected storage until the guard is dropped. ptr: unsafe { &mut *self.storage.get() }, #[cfg(feature = "smp")] flag_ref: &self.flag, @@ -188,6 +196,8 @@ impl SpinLock { /// no actual locking is needed. #[inline(always)] pub fn get_mut(&mut self) -> &mut T { + // SAFETY: `&mut self` proves no other references to the lock exist, so + // no guard can concurrently access the `UnsafeCell` contents. unsafe { &mut *self.storage.get() } } } @@ -216,6 +226,8 @@ impl Deref for SpinLockGuard<'_, G, T> { #[inline(always)] fn deref(&self) -> &T { + // SAFETY: `ptr` was created only after the lock was acquired. Shared + // references derived from a guard cannot outlive the guard. unsafe { &*self.ptr } } } @@ -223,6 +235,8 @@ impl Deref for SpinLockGuard<'_, G, T> { impl DerefMut for SpinLockGuard<'_, G, T> { #[inline(always)] fn deref_mut(&mut self) -> &mut T { + // SAFETY: `&mut self` proves this guard is borrowed exclusively, and + // the guard owns the lock until drop. unsafe { &mut *self.ptr } } } diff --git a/task/kspin/src/tests.rs b/task/kspin/src/tests.rs index b0e60d304e7f47a0653b9cc4a07aeed7a7479337..667275aed689c272b80fa1cb2adbccebf6e8aef6 100644 --- a/task/kspin/src/tests.rs +++ b/task/kspin/src/tests.rs @@ -21,6 +21,9 @@ impl BaseGuard for TestGuardIrq { type State = u32; fn acquire() -> Self::State { + // SAFETY: unit tests run this guard counter in controlled single-test + // paths; the mutable static is only a lightweight state-restoration + // probe for the fake guard. unsafe { IRQ_CNT += 1; IRQ_CNT @@ -28,6 +31,8 @@ impl BaseGuard for TestGuardIrq { } fn release(_: Self::State) { + // SAFETY: paired with `acquire` in the same fake guard. Tests assert + // that every acquired state is restored before the test exits. unsafe { IRQ_CNT -= 1; } @@ -67,8 +72,12 @@ fn try_lock_works() { fn guard_state_restored() { let m = TestSpinIrq::new(()); let _a = m.lock(); + // SAFETY: the test reads the fake guard counter after the lock operation + // has completed on this execution path. assert_eq!(unsafe { IRQ_CNT }, 1); drop(_a); + // SAFETY: after dropping the only guard, the fake guard counter should be + // restored to zero. assert_eq!(unsafe { IRQ_CNT }, 0); } @@ -77,13 +86,17 @@ fn guard_state_restored() { fn failed_try_lock_restores_state() { let m = TestSpinIrq::new(()); let _a = m.lock(); + // SAFETY: the current test path holds the only fake guard instance. assert_eq!(unsafe { IRQ_CNT }, 1); let b = m.try_lock(); assert!(b.is_none()); + // SAFETY: the failed `try_lock` must have restored its temporary guard + // state, so only the original guard contributes to the counter. assert_eq!(unsafe { IRQ_CNT }, 1); drop(_a); + // SAFETY: after dropping the original guard, no fake guard state remains. assert_eq!(unsafe { IRQ_CNT }, 0); } @@ -142,6 +155,8 @@ fn force_unlock_works() { let lock = TestMutex::new(()); core::mem::forget(lock.lock()); + // SAFETY: this test intentionally leaks the guard, so the current + // execution path still owns the lock and may exercise `force_unlock`. unsafe { lock.force_unlock(); }