Ver código fonte

new: 新增具有守卫的自旋锁SpinLock,支持编译期对锁的使用进行检查。 (#148)

login 2 anos atrás
pai
commit
ec53d23ed0

+ 1 - 1
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)

+ 2 - 0
docs/kernel/locking/index.rst

@@ -8,4 +8,6 @@
    :maxdepth: 1
 
    locks
+   spinlock
    lockref
+

+ 7 - 0
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), 将锁及其要保护到的数据绑定在一个结构体内,并能在编译期避免未加锁就访问数据的问题。
 
 &emsp;&emsp;进程在获取自旋锁后,将改变pcb中的锁变量持有计数,从而隐式地禁止了抢占。为了获得更多灵活的操作,spinlock还提供了以下的方法:
 
@@ -32,6 +34,11 @@
 &emsp;&emsp;当您同时需要使用自旋锁以及引用计数时,一个好的方法是:使用`lockref`. 这是一种额外的加速技术,能额外提供“无锁修改引用计数”的功能。详情请见:{ref}`lockref <_lockref>`
 
 ## 详细介绍
+
+### 自旋锁的详细介绍
+
+&emsp;&emsp;关于自旋锁的详细介绍,请见文档:{ref}`自旋锁 <_spinlock_doc>`
+
 ### semaphore信号量
 
 &emsp;&emsp;semaphore信号量是基于计数实现的。

+ 86 - 0
docs/kernel/locking/spinlock.md

@@ -0,0 +1,86 @@
+(_spinlock_doc)=
+
+:::{note}
+作者:龙进 <longjin@RinGoTek.cn>
+:::
+
+# 自旋锁
+
+## 1.简介
+
+&emsp;&emsp;自旋锁是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持运行的状态,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。
+
+&emsp;&emsp;DragonOS在`kernel/src/lib/spinlock.rs`文件中,实现了自旋锁。根据功能特性的略微差异,分别提供了`RawSpinLock`和`SpinLock`两种自旋锁。
+
+(_spinlock_doc_rawspinlock)=
+## 2. RawSpinLock - 原始自旋锁
+
+&emsp;&emsp;`RawSpinLock`是原始的自旋锁,其数据部分包含一个AtomicBool, 实现了自旋锁的基本功能。其加锁、放锁需要手动确定对应的时机,也就是说,和我们在其他语言中使用的自旋锁一样,
+需要先调用`lock()`方法,然后当离开临界区时,手动调用`unlock()`方法。我们并没有向编译器显式地指定该自旋锁到底保护的是哪些数据。
+
+&emsp;&emsp;RawSpinLock为程序员提供了非常自由的加锁、放锁控制。但是,正是由于它过于自由,因此在使用它的时候,我们很容易出错。很容易出现“未加锁就访问临界区的数据”、“忘记放锁”、“双重释放”等问题。当使用RawSpinLock时,编译器并不能对这些情况进行检查,这些问题只能在运行时被发现。
+
+:::{warning}
+`RawSpinLock`与C版本的`spinlock_t`不具有二进制兼容性。如果由于暂时的兼容性的需求,要操作C版本的`spinlock_t`,请使用`spinlock.rs`中提供的C版本的spinlock_t的操作函数。
+
+但是,对于新开发的功能,请不要使用C版本的`spinlock_t`,因为随着代码重构的进行,我们将会移除它。
+:::
+
+(_spinlock_doc_spinlock)=
+## 3. SpinLock - 具备守卫的自旋锁
+
+&emsp;&emsp;`SpinLock`在`RawSpinLock`的基础上,进行了封装,能够在编译期检查出“未加锁就访问临界区的数据”、“忘记放锁”、“双重释放”等问题;并且,支持数据的内部可变性。
+
+&emsp;&emsp;其结构体原型如下:
+
+```rust
+#[derive(Debug)]
+pub struct SpinLock<T> {
+    lock: RawSpinlock,
+    /// 自旋锁保护的数据
+    data: UnsafeCell<T>,
+}
+```
+
+### 3.1. 使用方法
+
+&emsp;&emsp;您可以这样初始化一个SpinLock:
+
+```rust
+let x = SpinLock::new(Vec::new());
+```
+
+&emsp;&emsp;在初始化这个SpinLock时,必须把要保护的数据传入SpinLock,由SpinLock进行管理。
+
+&emsp;&emsp;当需要读取、修改SpinLock保护的数据时,请先使用SpinLock的`lock()`方法。该方法会返回一个`SpinLockGuard`。您可以使用被保护的数据的成员函数来进行一些操作。或者是直接读取、写入被保护的数据。(相当于您获得了被保护的数据的可变引用)
+
+&emsp;&emsp;完整示例如下方代码所示:
+
+```rust
+let x :SpinLock<Vec<i32>>= SpinLock::new(Vec::new());
+    {
+        let mut g :SpinLockGuard<Vec<i32>>= 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. 原理
+
+&emsp;&emsp;`SpinLock`之所以能够实现编译期检查,是因为它引入了一个`SpinLockGuard`作为守卫。我们在编写代码的时候,保证只有调用`SpinLock`的`lock()`方法加锁后,才能生成一个`SpinLockGuard`。 并且,当我们想要访问受保护的数据的时候,都必须获得一个守卫。然后,我们为`SpinLockGuard`实现了`Drop` trait,当守卫的生命周期结束时,将会自动释放锁。除此以外,没有别的方法能够释放锁。因此我们能够得知,一个上下文中,只要`SpinLockGuard`的生命周期没有结束,那么它就拥有临界区数据的访问权,数据访问就是安全的。
+
+### 3.3. 存在的问题
+
+#### 3.3.1. 双重加锁
+
+&emsp;&emsp;请注意,`SpinLock`支持的编译期检查并不是万能的。它目前无法在编译期检查出“双重加锁”问题。试看这样一个场景:函数A中,获得了锁。然后函数B中继续尝试加锁,那么就造成了“双重加锁”问题。这样在编译期是无法检测出来的。
+
+&emsp;&emsp;针对这个问题,我们建议采用这样的编程方法:
+
+- 如果函数B需要访问临界区内的数据,那么,函数B应当接收一个类型为`&SpinLockGuard`的参数,这个守卫由函数A获得。这样一来,函数B就能访问临界区内的数据。

+ 1 - 1
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;
 

+ 71 - 11
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<T> {
+    lock: RawSpinlock,
+    /// 自旋锁保护的数据
+    data: UnsafeCell<T>,
+}
+
+/// SpinLock的守卫
+/// 该守卫没有构造器,并且其信息均为私有的。我们只能通过SpinLock的lock()方法获得一个守卫。
+/// 因此我们可以认为,只要能够获得一个守卫,那么数据就在自旋锁的保护之下。
+#[derive(Debug)]
+pub struct SpinLockGuard<'a, T: 'a> {
+    lock: &'a SpinLock<T>,
+}
+
+/// 向编译器保证,SpinLock在线程之间是安全的.
+/// 其中要求类型T实现了Send这个Trait
+unsafe impl<T> Sync for SpinLock<T> where T: Send {}
+
+impl<T> SpinLock<T> {
+    pub const fn new(value: T) -> Self {
+        return Self {
+            lock: RawSpinlock::INIT,
+            data: UnsafeCell::new(value),
+        };
+    }
 
+    #[inline(always)]
+    pub fn lock(&self) -> SpinLockGuard<T> {
+        self.lock.lock();
+        // 加锁成功,返回一个守卫
+        return SpinLockGuard { lock: self };
+    }
+}
+
+/// 实现Deref trait,支持通过获取SpinLockGuard来获取临界区数据的不可变引用
+impl<T> Deref for SpinLockGuard<'_, T> {
+    type Target = T;
+    
+    fn deref(&self) -> &Self::Target {
+        return unsafe { &*self.lock.data.get() };
+    }
+}
+
+/// 实现DerefMut trait,支持通过获取SpinLockGuard来获取临界区数据的可变引用
+impl<T> DerefMut for SpinLockGuard<'_, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        return unsafe { &mut *self.lock.data.get() };
+    }
+}
+
+/// @brief 为SpinLockGuard实现Drop方法,那么,一旦守卫的生命周期结束,就会自动释放自旋锁,避免了忘记放锁的情况
+impl<T> Drop for SpinLockGuard<'_, T> {
+    fn drop(&mut self) {
+        self.lock.lock.unlock();
+    }
 }