From ec53d23ed03347854189d92b7e175f309779321b Mon Sep 17 00:00:00 2001 From: login Date: Sat, 14 Jan 2023 10:35:49 +0800 Subject: [PATCH] =?UTF-8?q?new:=20=E6=96=B0=E5=A2=9E=E5=85=B7=E6=9C=89?= =?UTF-8?q?=E5=AE=88=E5=8D=AB=E7=9A=84=E8=87=AA=E6=97=8B=E9=94=81SpinLock?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E7=BC=96=E8=AF=91=E6=9C=9F=E5=AF=B9?= =?UTF-8?q?=E9=94=81=E7=9A=84=E4=BD=BF=E7=94=A8=E8=BF=9B=E8=A1=8C=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E3=80=82=20(#148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/introduction/mirrors.md | 2 +- docs/kernel/locking/index.rst | 2 + docs/kernel/locking/locks.md | 7 +++ docs/kernel/locking/spinlock.md | 86 +++++++++++++++++++++++++++++++++ kernel/src/lib.rs | 2 +- kernel/src/libs/spinlock.rs | 84 +++++++++++++++++++++++++++----- 6 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 docs/kernel/locking/spinlock.md diff --git a/docs/introduction/mirrors.md b/docs/introduction/mirrors.md index 1ae39257..1b4ccf97 100644 --- a/docs/introduction/mirrors.md +++ b/docs/introduction/mirrors.md @@ -3,4 +3,4 @@ 您可以从以下镜像站下载到DragonOS的源代码和其他文件: - [DragonOS镜像站](https://mirrors.dragonos.org/) -- [DragonOS国内镜像站 (RingoTek)](https://mirrors.RinGoTek.cn) +- [DragonOS国内镜像站 (RinGoTek)](https://mirrors.RinGoTek.cn) diff --git a/docs/kernel/locking/index.rst b/docs/kernel/locking/index.rst index 9a8b2d6d..bce4d52c 100644 --- a/docs/kernel/locking/index.rst +++ b/docs/kernel/locking/index.rst @@ -8,4 +8,6 @@ :maxdepth: 1 locks + spinlock lockref + diff --git a/docs/kernel/locking/locks.md b/docs/kernel/locking/locks.md index 36c4602d..1529440a 100644 --- a/docs/kernel/locking/locks.md +++ b/docs/kernel/locking/locks.md @@ -21,6 +21,8 @@ ### 自旋锁 - spinlock_t +- {ref}`RawSpinLock <_spinlock_doc_rawspinlock>`(Rust版本的spinlock_t,但与spinlock_t不兼容) +- {ref}`SpinLock <_spinlock_doc_spinlock>` —— 在RawSpinLock的基础上,封装了一层守卫(Guard), 将锁及其要保护到的数据绑定在一个结构体内,并能在编译期避免未加锁就访问数据的问题。   进程在获取自旋锁后,将改变pcb中的锁变量持有计数,从而隐式地禁止了抢占。为了获得更多灵活的操作,spinlock还提供了以下的方法: @@ -32,6 +34,11 @@   当您同时需要使用自旋锁以及引用计数时,一个好的方法是:使用`lockref`. 这是一种额外的加速技术,能额外提供“无锁修改引用计数”的功能。详情请见:{ref}`lockref <_lockref>` ## 详细介绍 + +### 自旋锁的详细介绍 + +  关于自旋锁的详细介绍,请见文档:{ref}`自旋锁 <_spinlock_doc>` + ### semaphore信号量   semaphore信号量是基于计数实现的。 diff --git a/docs/kernel/locking/spinlock.md b/docs/kernel/locking/spinlock.md new file mode 100644 index 00000000..1d4305cb --- /dev/null +++ b/docs/kernel/locking/spinlock.md @@ -0,0 +1,86 @@ +(_spinlock_doc)= + +:::{note} +作者:龙进 +::: + +# 自旋锁 + +## 1.简介 + +  自旋锁是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持运行的状态,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 + +  DragonOS在`kernel/src/lib/spinlock.rs`文件中,实现了自旋锁。根据功能特性的略微差异,分别提供了`RawSpinLock`和`SpinLock`两种自旋锁。 + +(_spinlock_doc_rawspinlock)= +## 2. RawSpinLock - 原始自旋锁 + +  `RawSpinLock`是原始的自旋锁,其数据部分包含一个AtomicBool, 实现了自旋锁的基本功能。其加锁、放锁需要手动确定对应的时机,也就是说,和我们在其他语言中使用的自旋锁一样, +需要先调用`lock()`方法,然后当离开临界区时,手动调用`unlock()`方法。我们并没有向编译器显式地指定该自旋锁到底保护的是哪些数据。 + +  RawSpinLock为程序员提供了非常自由的加锁、放锁控制。但是,正是由于它过于自由,因此在使用它的时候,我们很容易出错。很容易出现“未加锁就访问临界区的数据”、“忘记放锁”、“双重释放”等问题。当使用RawSpinLock时,编译器并不能对这些情况进行检查,这些问题只能在运行时被发现。 + +:::{warning} +`RawSpinLock`与C版本的`spinlock_t`不具有二进制兼容性。如果由于暂时的兼容性的需求,要操作C版本的`spinlock_t`,请使用`spinlock.rs`中提供的C版本的spinlock_t的操作函数。 + +但是,对于新开发的功能,请不要使用C版本的`spinlock_t`,因为随着代码重构的进行,我们将会移除它。 +::: + +(_spinlock_doc_spinlock)= +## 3. SpinLock - 具备守卫的自旋锁 + +  `SpinLock`在`RawSpinLock`的基础上,进行了封装,能够在编译期检查出“未加锁就访问临界区的数据”、“忘记放锁”、“双重释放”等问题;并且,支持数据的内部可变性。 + +  其结构体原型如下: + +```rust +#[derive(Debug)] +pub struct SpinLock { + lock: RawSpinlock, + /// 自旋锁保护的数据 + data: UnsafeCell, +} +``` + +### 3.1. 使用方法 + +  您可以这样初始化一个SpinLock: + +```rust +let x = SpinLock::new(Vec::new()); +``` + +  在初始化这个SpinLock时,必须把要保护的数据传入SpinLock,由SpinLock进行管理。 + +  当需要读取、修改SpinLock保护的数据时,请先使用SpinLock的`lock()`方法。该方法会返回一个`SpinLockGuard`。您可以使用被保护的数据的成员函数来进行一些操作。或者是直接读取、写入被保护的数据。(相当于您获得了被保护的数据的可变引用) + +  完整示例如下方代码所示: + +```rust +let x :SpinLock>= SpinLock::new(Vec::new()); + { + let mut g :SpinLockGuard>= x.lock(); + g.push(1); + g.push(2); + g.push(2); + assert!(g.as_slice() == [1, 2, 2] || g.as_slice() == [2, 2, 1]); + // 在此处,SpinLock是加锁的状态 + kdebug!("x={:?}", x); + } + // 由于上方的变量`g`,也就是SpinLock守卫的生命周期结束,自动释放了SpinLock。因此,在此处,SpinLock是放锁的状态 + kdebug!("x={:?}", x); +``` + +### 3.2. 原理 + +  `SpinLock`之所以能够实现编译期检查,是因为它引入了一个`SpinLockGuard`作为守卫。我们在编写代码的时候,保证只有调用`SpinLock`的`lock()`方法加锁后,才能生成一个`SpinLockGuard`。 并且,当我们想要访问受保护的数据的时候,都必须获得一个守卫。然后,我们为`SpinLockGuard`实现了`Drop` trait,当守卫的生命周期结束时,将会自动释放锁。除此以外,没有别的方法能够释放锁。因此我们能够得知,一个上下文中,只要`SpinLockGuard`的生命周期没有结束,那么它就拥有临界区数据的访问权,数据访问就是安全的。 + +### 3.3. 存在的问题 + +#### 3.3.1. 双重加锁 + +  请注意,`SpinLock`支持的编译期检查并不是万能的。它目前无法在编译期检查出“双重加锁”问题。试看这样一个场景:函数A中,获得了锁。然后函数B中继续尝试加锁,那么就造成了“双重加锁”问题。这样在编译期是无法检测出来的。 + +  针对这个问题,我们建议采用这样的编程方法: + +- 如果函数B需要访问临界区内的数据,那么,函数B应当接收一个类型为`&SpinLockGuard`的参数,这个守卫由函数A获得。这样一来,函数B就能访问临界区内的数据。 diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 99e6af1b..3b2802a4 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -23,12 +23,12 @@ mod include; mod ipc; #[macro_use] mod libs; +mod exception; mod mm; mod process; mod sched; mod smp; mod time; -mod exception; extern crate alloc; diff --git a/kernel/src/libs/spinlock.rs b/kernel/src/libs/spinlock.rs index db523cfe..5eac2af1 100644 --- a/kernel/src/libs/spinlock.rs +++ b/kernel/src/libs/spinlock.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +use core::cell::UnsafeCell; +use core::ops::{Deref, DerefMut}; use core::ptr::read_volatile; use core::sync::atomic::{AtomicBool, Ordering}; @@ -59,7 +61,7 @@ pub fn spin_unlock_irq(lock: *mut spinlock_t) { /// 原始的Spinlock(自旋锁) /// 请注意,这个自旋锁和C的不兼容。 -/// +/// /// @param self.0 这个AtomicBool的值为false时,表示没有被加锁。当它为true时,表示自旋锁已经被上锁。 #[derive(Debug)] pub struct RawSpinlock(AtomicBool); @@ -69,12 +71,12 @@ impl RawSpinlock { pub const INIT: RawSpinlock = RawSpinlock(AtomicBool::new(false)); /// @brief 加锁 - pub fn lock(&mut self) { + pub fn lock(&self) { while !self.try_lock() {} } /// @brief 关中断并加锁 - pub fn lock_irq(&mut self){ + pub fn lock_irq(&self) { cli(); self.lock(); } @@ -82,7 +84,7 @@ impl RawSpinlock { /// @brief 尝试加锁 /// @return 加锁成功->true /// 加锁失败->false - pub fn try_lock(&mut self) -> bool { + pub fn try_lock(&self) -> bool { // 先增加自旋锁持有计数 preempt_disable(); @@ -90,7 +92,7 @@ impl RawSpinlock { .0 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) .is_ok(); - + // 如果加锁失败恢复自旋锁持有计数 if res == false { preempt_enable(); @@ -99,34 +101,92 @@ impl RawSpinlock { } /// @brief 解锁 - pub fn unlock(&mut self){ + pub fn unlock(&self) { // 减少自旋锁持有计数 preempt_enable(); self.0.store(false, Ordering::Release); } /// @brief 放锁并开中断 - pub fn unlock_irq(&mut self){ + pub fn unlock_irq(&self) { self.unlock(); sti(); } /// @brief 判断自旋锁是否被上锁 - /// + /// /// @return true 自旋锁被上锁 /// @return false 自旋锁处于解锁状态 - pub fn is_locked(&self)->bool - { + pub fn is_locked(&self) -> bool { return self.0.load(Ordering::Relaxed).into(); } /// @brief 强制设置自旋锁的状态 /// 请注意,这样操作可能会带来未知的风险。因此它是unsafe的。(尽管从Rust语言本身来说,它是safe的) - pub unsafe fn set_value(&mut self, value:bool){ + pub unsafe fn set_value(&mut self, value: bool) { self.0.store(value, Ordering::SeqCst); } // todo: spin_lock_irqsave // todo: spin_unlock_irqrestore - +} + +/// 实现了守卫的SpinLock, 能够支持内部可变性 +/// +#[derive(Debug)] +pub struct SpinLock { + lock: RawSpinlock, + /// 自旋锁保护的数据 + data: UnsafeCell, +} + +/// SpinLock的守卫 +/// 该守卫没有构造器,并且其信息均为私有的。我们只能通过SpinLock的lock()方法获得一个守卫。 +/// 因此我们可以认为,只要能够获得一个守卫,那么数据就在自旋锁的保护之下。 +#[derive(Debug)] +pub struct SpinLockGuard<'a, T: 'a> { + lock: &'a SpinLock, +} + +/// 向编译器保证,SpinLock在线程之间是安全的. +/// 其中要求类型T实现了Send这个Trait +unsafe impl Sync for SpinLock where T: Send {} + +impl SpinLock { + pub const fn new(value: T) -> Self { + return Self { + lock: RawSpinlock::INIT, + data: UnsafeCell::new(value), + }; + } + + #[inline(always)] + pub fn lock(&self) -> SpinLockGuard { + self.lock.lock(); + // 加锁成功,返回一个守卫 + return SpinLockGuard { lock: self }; + } +} + +/// 实现Deref trait,支持通过获取SpinLockGuard来获取临界区数据的不可变引用 +impl Deref for SpinLockGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + return unsafe { &*self.lock.data.get() }; + } +} + +/// 实现DerefMut trait,支持通过获取SpinLockGuard来获取临界区数据的可变引用 +impl DerefMut for SpinLockGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + return unsafe { &mut *self.lock.data.get() }; + } +} + +/// @brief 为SpinLockGuard实现Drop方法,那么,一旦守卫的生命周期结束,就会自动释放自旋锁,避免了忘记放锁的情况 +impl Drop for SpinLockGuard<'_, T> { + fn drop(&mut self) { + self.lock.lock.unlock(); + } }