同步与锁 (Synchronization & Locking)

本文档概述了 comix 内核中用于处理并发和防止竞争条件的同步原语。

1. 简介

在多核或可抢占的内核中,当多个执行流(如不同核上的任务,或中断处理程序与被中断的任务)同时访问共享数据时,若不加协调,就会产生竞争条件(Race Condition),导致数据损坏和系统崩溃。

sync 模块提供了一系列同步原语(Synchronization Primitives),通过确保在任何时刻只有一个执行流能够访问临界区(Critical Section),来保证共享数据的完整性。

导航

2. 核心概念

本内核主要采用两种策略来解决并发问题:

  1. 中断屏蔽 (Interrupt Disabling): 在单核处理器上,禁用中断可以防止当前代码被中断处理程序打断,从而避免了任务代码与中断代码之间的竞争。这是实现其他更复杂锁的底层基础。
  2. 原子操作与自旋 (Atomic Operations & Spinning): 在多核处理器上,仅屏蔽本地核心的中断是不够的,因为其他核心仍然可以访问共享数据。自旋锁利用CPU提供的原子操作(如 amoswap)来循环检查并获取锁。如果锁已被占用,它会“自旋”(在一个紧凑循环中等待),直到锁被释放。

3. 同步原语概览

comix 提供了多种同步原语,适用于不同的场景:

原语源码链接核心机制适用场景
SpinLock<T>os/src/sync/spin_lock.rs屏蔽中断 + 原子操作自旋保护访问耗时极短的共享数据,例如修改一个计数器或链表指针。
SleepLockos/src/sync/sleep_lock.rs原子操作 + 任务睡眠 (WaitQueue)保护访问耗时较长的共享数据,例如执行I/O操作或复杂的计算。
IntrGuardos/src/sync/intr_guard.rs屏蔽/恢复中断 (RAII)作为其他锁的底层实现,或在确定为单核且无需锁的场景下临时屏蔽中断。
RawSpinLockos/src/sync/raw_spin_lock.rs纯粹的原子操作自旋SpinLockSleepLock 的内部构件,不推荐直接使用。

4. 设计哲学:RAII 与锁守卫

为了防止因忘记释放锁而导致的死锁,本模块广泛采用了 RAII (Resource Acquisition Is Initialization) 设计模式。

  • 当调用 lock() 方法时,会返回一个锁守卫 (Lock Guard) 对象(例如 SpinLockGuard)。
  • 这个守卫对象在其生命周期内持有锁,并提供对受保护数据的安全访问(通过 DerefDerefMut)。
  • 当守卫对象离开其作用域时,它的 drop() 方法会自动被调用,从而自动释放锁

这种设计极大地提升了锁使用的安全性。

#![allow(unused)]
fn main() {
// 示例:
let data = SpinLock::new(0);

// lock() 返回一个守卫对象 guard
let mut guard = data.lock();

// 通过 guard 安全地访问被保护的数据
*guard += 1;

// 当 guard 离开作用域时,锁会自动释放,无需手动调用 unlock()
}

5. 注意事项:死锁

使用锁时必须警惕死锁 (Deadlock)。一个常见的死锁场景是锁顺序反转:

  • 任务A: lock(L1); lock(L2);
  • 任务B: lock(L2); lock(L1);

如果任务A持有L1并等待L2,而任务B持有L2并等待L1,两个任务将永远等待下去。

规则: 在整个内核中,如果需要同时获取多个锁,必须始终按照相同的顺序获取它们。 详见锁顺序与死锁预防