Browse Source

new: Rust版本的Mutex (#157)

login 2 years ago
parent
commit
935f40ec17

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

@@ -10,4 +10,5 @@
    locks
    spinlock
    lockref
+   mutex
 

+ 1 - 57
docs/kernel/locking/locks.md

@@ -47,60 +47,4 @@
 
 ### mutex互斥量
 
-  mutex是一种轻量级的同步原语,只有0和1两种状态。
-
-  当mutex被占用时,尝试对mutex进行加锁操作的进程将会被休眠,直到资源可用。
-
-#### 特性
-
-- 同一时间只有1个任务可以持有mutex
-- 不允许递归地加锁、解锁
-- 只允许通过mutex的api来操作mutex
-- 在硬中断、软中断中不能使用mutex
-
-#### 数据结构
-
-  mutex定义在`common/mutex.h`中。其数据类型如下所示:
-
-```c
-typedef struct
-{
-
-    atomic_t count; // 锁计数。1->已解锁。 0->已上锁,且有可能存在等待者
-    spinlock_t wait_lock;   // mutex操作锁,用于对mutex的list的操作进行加锁
-    struct List wait_list;  // Mutex的等待队列
-} mutex_t;
-```
-
-#### API
-
-##### mutex_init
-
-**`void mutex_init(mutex_t *lock)`**
-
-  初始化一个mutex对象。
-
-##### mutex_lock
-
-**`void mutex_lock(mutex_t *lock)`**
-
-  对一个mutex对象加锁。若mutex当前被其他进程持有,则当前进程进入休眠状态。
-
-##### mutex_unlock
-
-**`void mutex_unlock(mutex_t *lock)`**
-
-  对一个mutex对象解锁。若mutex的等待队列中有其他的进程,则唤醒下一个进程。
-
-##### mutex_trylock
-
-**`void mutex_trylock(mutex_t *lock)`**
-
-  尝试对一个mutex对象加锁。若mutex当前被其他进程持有,则返回0.否则,加锁成功,返回1.
-
-##### mutex_is_locked
-
-**`void mutex_is_locked(mutex_t *lock)`**
-
-  判断mutex是否已被加锁。若给定的mutex已处于上锁状态,则返回1,否则返回0。
-
+&emsp;&emsp;请见{ref}`Mutex文档 <_mutex_doc>`

+ 171 - 0
docs/kernel/locking/mutex.md

@@ -0,0 +1,171 @@
+(_mutex_doc)=
+
+:::{note}
+作者:龙进 <[email protected]>
+:::
+
+# mutex互斥量
+
+&emsp;&emsp;mutex是一种轻量级的同步原语,只有被加锁、空闲两种状态。
+
+&emsp;&emsp;当mutex被占用时,尝试对mutex进行加锁操作的进程将会被休眠,直到资源可用。
+
+## 1. 特性
+
+- 同一时间只有1个任务可以持有mutex
+- 不允许递归地加锁、解锁
+- 只允许通过mutex的api来操作mutex
+- 在硬中断、软中断中不能使用mutex
+
+## 2. 定义
+
+&emsp;&emsp;mutex定义在`lib/mutex.rs`中,定义如下所示:
+
+```rust
+/// @brief Mutex互斥量结构体
+/// 请注意!由于Mutex属于休眠锁,因此,如果您的代码可能在中断上下文内执行,请勿采用Mutex!
+#[derive(Debug)]
+pub struct Mutex<T> {
+    /// 该Mutex保护的数据
+    data: UnsafeCell<T>,
+    /// Mutex内部的信息
+    inner: SpinLock<MutexInner>,
+}
+
+#[derive(Debug)]
+struct MutexInner {
+    /// 当前Mutex是否已经被上锁(上锁时,为true)
+    is_locked: bool,
+    /// 等待获得这个锁的进程的链表
+    wait_list: LinkedList<&'static mut process_control_block>,
+}
+
+```
+
+## 3. 使用
+
+&emsp;&emsp;与SpinLock类似,Rust版本的Mutex具有一个守卫。使用的时候,需要将要被保护的数据的所有权移交Mutex。并且,守卫只能在加锁成功后产生,因此,每个时刻,每个Mutex最多存在1个守卫。
+
+&emsp;&emsp;当需要读取、修改Mutex保护的数据时,请先使用Mutex的`lock()`方法。该方法会返回一个`MutexGuard`。您可以使用被保护的数据的成员函数来进行一些操作。或者是直接读取、写入被保护的数据。(相当于您获得了被保护的数据的可变引用)
+
+&emsp;&emsp;完整示例如下方代码所示:
+
+```rust
+let x :Mutex<Vec<i32>>= Mutex::new(Vec::new());
+    {
+        let mut g :MutexGuard<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]);
+        // 在此处,Mutex是加锁的状态
+        kdebug!("x={:?}", x);
+    }
+    // 由于上方的变量`g`,也就是Mutex守卫的生命周期结束,自动释放了Mutex。因此,在此处,Mutex是放锁的状态
+    kdebug!("x={:?}", x);
+```
+
+&emsp;&emsp;对于结构体内部的变量,我们可以使用Mutex进行细粒度的加锁,也就是使用Mutex包裹需要细致加锁的成员变量,比如这样:
+
+```rust
+pub struct a {
+  pub data: Mutex<data_struct>,
+}
+```
+
+&emsp;&emsp;当然,我们也可以对整个结构体进行加锁:
+
+```rust
+struct MyStruct {
+  pub data: data_struct,
+}
+/// 被全局加锁的结构体
+pub struct LockedMyStruct(Mutex<MyStruct>);
+```
+
+## 4. API
+
+### 4.1. new - 初始化Mutex
+
+#### 原型
+
+```rust
+pub const fn new(value: T) -> Self
+```
+
+#### 说明
+
+&emsp;&emsp;`new()`方法用于初始化一个Mutex。该方法需要一个被保护的数据作为参数。并且,该方法会返回一个Mutex。
+
+
+### 4.2. lock - 加锁
+
+#### 原型
+
+```rust
+pub fn lock(&self) -> MutexGuard<T>
+```
+
+#### 说明
+
+&emsp;&emsp;对Mutex加锁,返回Mutex的守卫,您可以使用这个守卫来操作被保护的数据。
+
+&emsp;&emsp;如果Mutex已经被加锁,那么,该方法会阻塞当前进程,直到Mutex被释放。
+
+### 4.3. try_lock - 尝试加锁
+
+#### 原型
+
+```rust
+pub fn try_lock(&self) -> Result<MutexGuard<T>, i32>
+```
+
+#### 说明
+
+&emsp;&emsp;尝试对Mutex加锁。如果加锁失败,不会将当前进程加入等待队列。如果加锁成功,返回Mutex的守卫;如果当前Mutex已经被加锁,返回`Err(错误码)`。
+
+## 5. C版本的Mutex(在将来会被废弃)
+
+&emsp;&emsp;mutex定义在`common/mutex.h`中。其数据类型如下所示:
+
+```c
+typedef struct
+{
+
+    atomic_t count; // 锁计数。1->已解锁。 0->已上锁,且有可能存在等待者
+    spinlock_t wait_lock;   // mutex操作锁,用于对mutex的list的操作进行加锁
+    struct List wait_list;  // Mutex的等待队列
+} mutex_t;
+```
+
+### 5.1. API
+
+#### mutex_init
+
+**`void mutex_init(mutex_t *lock)`**
+
+&emsp;&emsp;初始化一个mutex对象。
+
+#### mutex_lock
+
+**`void mutex_lock(mutex_t *lock)`**
+
+&emsp;&emsp;对一个mutex对象加锁。若mutex当前被其他进程持有,则当前进程进入休眠状态。
+
+#### mutex_unlock
+
+**`void mutex_unlock(mutex_t *lock)`**
+
+&emsp;&emsp;对一个mutex对象解锁。若mutex的等待队列中有其他的进程,则唤醒下一个进程。
+
+#### mutex_trylock
+
+**`void mutex_trylock(mutex_t *lock)`**
+
+&emsp;&emsp;尝试对一个mutex对象加锁。若mutex当前被其他进程持有,则返回0.否则,加锁成功,返回1.
+
+#### mutex_is_locked
+
+**`void mutex_is_locked(mutex_t *lock)`**
+
+&emsp;&emsp;判断mutex是否已被加锁。若给定的mutex已处于上锁状态,则返回1,否则返回0。

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

@@ -71,6 +71,24 @@ let x :SpinLock<Vec<i32>>= SpinLock::new(Vec::new());
     kdebug!("x={:?}", x);
 ```
 
+&emsp;&emsp;对于结构体内部的变量,我们可以使用SpinLock进行细粒度的加锁,也就是使用SpinLock包裹需要细致加锁的成员变量,比如这样:
+
+```rust
+pub struct a {
+  pub data: SpinLock<data_struct>,
+}
+```
+
+&emsp;&emsp;当然,我们也可以对整个结构体进行加锁:
+
+```rust
+struct MyStruct {
+  pub data: data_struct,
+}
+/// 被全局加锁的结构体
+pub struct LockedMyStruct(SpinLock<MyStruct>);
+```
+
 ### 3.2. 原理
 
 &emsp;&emsp;`SpinLock`之所以能够实现编译期检查,是因为它引入了一个`SpinLockGuard`作为守卫。我们在编写代码的时候,保证只有调用`SpinLock`的`lock()`方法加锁后,才能生成一个`SpinLockGuard`。 并且,当我们想要访问受保护的数据的时候,都必须获得一个守卫。然后,我们为`SpinLockGuard`实现了`Drop` trait,当守卫的生命周期结束时,将会自动释放锁。除此以外,没有别的方法能够释放锁。因此我们能够得知,一个上下文中,只要`SpinLockGuard`的生命周期没有结束,那么它就拥有临界区数据的访问权,数据访问就是安全的。

+ 2 - 1
kernel/src/include/bindings/wrapper.h

@@ -29,4 +29,5 @@
 #include <mm/mm.h>
 #include <mm/slab.h>
 #include <process/process.h>
-#include <sched/sched.h>
+#include <sched/sched.h>
+#include <time/sleep.h>

+ 1 - 0
kernel/src/lib.rs

@@ -1,5 +1,6 @@
 #![no_std] // <1>
 #![no_main] // <1>
+#![feature(const_mut_refs)]
 #![feature(core_intrinsics)] // <2>
 #![feature(alloc_error_handler)]
 #![feature(panic_info_message)]

+ 1 - 0
kernel/src/libs/mod.rs

@@ -6,4 +6,5 @@ pub mod refcount;
 pub mod atomic;
 pub mod list;
 pub mod lockref;
+pub mod mutex;
 pub mod wait_queue;

+ 169 - 0
kernel/src/libs/mutex.rs

@@ -0,0 +1,169 @@
+use core::{
+    cell::UnsafeCell,
+    ops::{Deref, DerefMut},
+};
+
+use alloc::collections::LinkedList;
+
+use crate::{
+    arch::{asm::current::current_pcb, sched::sched},
+    include::bindings::bindings::{
+        pid_t, process_control_block, process_wakeup, EBUSY, PROC_INTERRUPTIBLE, PROC_RUNNING,
+    },
+    libs::spinlock::SpinLockGuard,
+};
+
+use super::spinlock::SpinLock;
+
+#[derive(Debug)]
+struct MutexInner {
+    /// 当前Mutex是否已经被上锁(上锁时,为true)
+    is_locked: bool,
+    /// 等待获得这个锁的进程的链表
+    wait_list: LinkedList<&'static mut process_control_block>,
+}
+
+/// @brief Mutex互斥量结构体
+/// 请注意!由于Mutex属于休眠锁,因此,如果您的代码可能在中断上下文内执行,请勿采用Mutex!
+#[derive(Debug)]
+pub struct Mutex<T> {
+    /// 该Mutex保护的数据
+    data: UnsafeCell<T>,
+    /// Mutex内部的信息
+    inner: SpinLock<MutexInner>,
+}
+
+/// @brief Mutex的守卫
+#[derive(Debug)]
+pub struct MutexGuard<'a, T: 'a> {
+    lock: &'a Mutex<T>,
+}
+
+unsafe impl<T> Sync for Mutex<T> where T: Send {}
+
+impl<T> Mutex<T> {
+    /// @brief 初始化一个新的Mutex对象
+    #[allow(dead_code)]
+    pub const fn new(value: T) -> Self {
+        return Self {
+            data: UnsafeCell::new(value),
+            inner: SpinLock::new(MutexInner {
+                is_locked: false,
+                wait_list: LinkedList::<&'static mut process_control_block>::new(),
+            }),
+        };
+    }
+
+    /// @brief 对Mutex加锁
+    /// @return MutexGuard<T> 返回Mutex的守卫,您可以使用这个守卫来操作被保护的数据
+    #[inline(always)]
+    #[allow(dead_code)]
+    pub fn lock(&self) -> MutexGuard<T> {
+        loop {
+            let mut inner: SpinLockGuard<MutexInner> = self.inner.lock();
+            // 当前mutex已经上锁
+            if inner.is_locked {
+                // 检查当前进程是否处于等待队列中,如果不在,就加到等待队列内
+                if self.check_pid_in_wait_list(&inner, current_pcb().pid) == false {
+                    inner.wait_list.push_back(current_pcb());
+                }
+
+                // 加到等待唤醒的队列,然后睡眠
+                drop(inner);
+                self.__sleep();
+            } else {
+                // 加锁成功
+                inner.is_locked = true;
+                drop(inner);
+                break;
+            }
+        }
+
+        // 加锁成功,返回一个守卫
+        return MutexGuard { lock: self };
+    }
+
+    /// @brief 尝试对Mutex加锁。如果加锁失败,不会将当前进程加入等待队列。
+    /// @return Ok 加锁成功,返回Mutex的守卫
+    /// @return Err 如果Mutex当前已经上锁,则返回Err.
+    #[inline(always)]
+    #[allow(dead_code)]
+    pub fn try_lock(&self) -> Result<MutexGuard<T>, i32> {
+        let mut inner = self.inner.lock();
+
+        // 如果当前mutex已经上锁,则失败
+        if inner.is_locked {
+            return Err(-(EBUSY as i32));
+        } else {
+            // 加锁成功
+            inner.is_locked = true;
+            return Ok(MutexGuard { lock: self });
+        }
+    }
+
+    /// @brief Mutex内部的睡眠函数
+    fn __sleep(&self) {
+        current_pcb().state &= !(PROC_RUNNING as u64);
+        current_pcb().state |= PROC_INTERRUPTIBLE as u64;
+        sched();
+    }
+
+    /// @brief 放锁。
+    ///
+    /// 本函数只能是私有的,且只能被守卫的drop方法调用,否则将无法保证并发安全。
+    fn unlock(&self) {
+        let mut inner: SpinLockGuard<MutexInner> = self.inner.lock();
+        // 当前mutex一定是已经加锁的状态
+        assert!(inner.is_locked);
+        // 标记mutex已经解锁
+        inner.is_locked = false;
+        if inner.wait_list.is_empty() {
+            return;
+        }
+
+        // wait_list不为空,则获取下一个要被唤醒的进程的pcb
+        let to_wakeup: &mut process_control_block = inner.wait_list.pop_front().unwrap();
+        drop(inner);
+
+        unsafe {
+            process_wakeup(to_wakeup);
+        }
+    }
+
+    /// @brief 检查进程是否在该mutex的等待队列内
+    #[inline]
+    fn check_pid_in_wait_list(&self, inner: &MutexInner, pid: pid_t) -> bool {
+        for p in inner.wait_list.iter() {
+            if p.pid == pid {
+                // 在等待队列内
+                return true;
+            }
+        }
+
+        // 不在等待队列内
+        return false;
+    }
+}
+
+/// 实现Deref trait,支持通过获取MutexGuard来获取临界区数据的不可变引用
+impl<T> Deref for MutexGuard<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        return unsafe { &*self.lock.data.get() };
+    }
+}
+
+/// 实现DerefMut trait,支持通过获取MutexGuard来获取临界区数据的可变引用
+impl<T> DerefMut for MutexGuard<'_, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        return unsafe { &mut *self.lock.data.get() };
+    }
+}
+
+/// @brief 为MutexGuard实现Drop方法,那么,一旦守卫的生命周期结束,就会自动释放自旋锁,避免了忘记放锁的情况
+impl<T> Drop for MutexGuard<'_, T> {
+    fn drop(&mut self) {
+        self.lock.unlock();
+    }
+}