第二节 自旋锁与中断

在真正开启和处理中断之前,这一节先来升级一下我们的自旋锁。

到目前为止,内核始终在单 CPU 上运行,我们也没有启用中断,这就意味着:我们的程序中不存在并发,那么就没有互斥的问题,自然也不需要锁。在第一章中,我们虽然实现了一个自旋锁,但那只是形式上的,是为了适应 Rust 编码规则而设立的一个空实现。

现在一旦启用中断,就会引起两种新的可能性:

  1. 当正常执行程序时,会随时被外部的中断而打断运行,然后就会去执行响应中断的例程。中断是随机不可预测的,这样有可能会把原本处于临界区中的一组操作打断,破坏它们的原子性、事务性。
  2. 虽然已经支持多任务并发,但这种并发是协作式的,即只有一个任务主动让出执行权时,另一个任务才能执行。因此,调度时机是可以协调的,完全可以避免打破临界区。但是下一步我们将基于时钟中断支持抢占式的并发,即任务调度可能随时发生,或许当前任务正好处于临界区中。

为了杜绝上述两种可能性,我们需要重构新的自旋锁。根据上面的分析,其实我们只要在锁期间,关闭中断即可。

定义自旋锁 SpinNoIrq 和对应的 guard 变量:

pub struct SpinNoIrq<T> {
    data: UnsafeCell<T>,
}

pub struct SpinNoIrqGuard<T> {
    irq_state: usize,
    data: *mut T,
}

unsafe impl<T> Sync for SpinNoIrq<T> {}
unsafe impl<T> Send for SpinNoIrq<T> {}

impl<T> SpinNoIrq<T> {
    #[inline(always)]
    pub const fn new(data: T) -> Self {
        Self {
            data: UnsafeCell::new(data),
        }
    }
}

加锁时,关中断和抢占:

impl<T> SpinNoIrq<T> {
    #[inline(always)]
    pub fn lock(&self) -> SpinNoIrqGuard<T> {
        let irq_state = NoPreemptIrqSave::acquire();
        SpinNoIrqGuard {
            irq_state,
            data: unsafe { &mut *self.data.get() },
        }
    }
}

析构时,恢复中断和抢占状态:

impl<T> Drop for SpinNoIrqGuard<T> {
    #[inline(always)]
    fn drop(&mut self) {
        NoPreemptIrqSave::release(self.irq_state);
    }
}

测试一下自旋锁的功能。测试成功!