new: Rust版本的Mutex (#157)

This commit is contained in:
login
2023-01-17 21:30:16 +08:00
committed by GitHub
parent d8a064128a
commit 935f40ec17
8 changed files with 364 additions and 58 deletions

View File

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

View File

@ -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>`

View File

@ -0,0 +1,171 @@
(_mutex_doc)=
:::{note}
作者:龙进 <longjin@RinGoTek.cn>
:::
# 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。

View File

@ -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`的生命周期没有结束,那么它就拥有临界区数据的访问权,数据访问就是安全的。