mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-15 21:26:47 +00:00
new: Rust版本的Mutex (#157)
This commit is contained in:
parent
d8a064128a
commit
935f40ec17
@ -10,4 +10,5 @@
|
||||
locks
|
||||
spinlock
|
||||
lockref
|
||||
mutex
|
||||
|
||||
|
@ -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。
|
||||
|
||||
  请见{ref}`Mutex文档 <_mutex_doc>`
|
171
docs/kernel/locking/mutex.md
Normal file
171
docs/kernel/locking/mutex.md
Normal file
@ -0,0 +1,171 @@
|
||||
(_mutex_doc)=
|
||||
|
||||
:::{note}
|
||||
作者:龙进 <longjin@RinGoTek.cn>
|
||||
:::
|
||||
|
||||
# mutex互斥量
|
||||
|
||||
  mutex是一种轻量级的同步原语,只有被加锁、空闲两种状态。
|
||||
|
||||
  当mutex被占用时,尝试对mutex进行加锁操作的进程将会被休眠,直到资源可用。
|
||||
|
||||
## 1. 特性
|
||||
|
||||
- 同一时间只有1个任务可以持有mutex
|
||||
- 不允许递归地加锁、解锁
|
||||
- 只允许通过mutex的api来操作mutex
|
||||
- 在硬中断、软中断中不能使用mutex
|
||||
|
||||
## 2. 定义
|
||||
|
||||
  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. 使用
|
||||
|
||||
  与SpinLock类似,Rust版本的Mutex具有一个守卫。使用的时候,需要将要被保护的数据的所有权移交Mutex。并且,守卫只能在加锁成功后产生,因此,每个时刻,每个Mutex最多存在1个守卫。
|
||||
|
||||
  当需要读取、修改Mutex保护的数据时,请先使用Mutex的`lock()`方法。该方法会返回一个`MutexGuard`。您可以使用被保护的数据的成员函数来进行一些操作。或者是直接读取、写入被保护的数据。(相当于您获得了被保护的数据的可变引用)
|
||||
|
||||
  完整示例如下方代码所示:
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
  对于结构体内部的变量,我们可以使用Mutex进行细粒度的加锁,也就是使用Mutex包裹需要细致加锁的成员变量,比如这样:
|
||||
|
||||
```rust
|
||||
pub struct a {
|
||||
pub data: Mutex<data_struct>,
|
||||
}
|
||||
```
|
||||
|
||||
  当然,我们也可以对整个结构体进行加锁:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
#### 说明
|
||||
|
||||
  `new()`方法用于初始化一个Mutex。该方法需要一个被保护的数据作为参数。并且,该方法会返回一个Mutex。
|
||||
|
||||
|
||||
### 4.2. lock - 加锁
|
||||
|
||||
#### 原型
|
||||
|
||||
```rust
|
||||
pub fn lock(&self) -> MutexGuard<T>
|
||||
```
|
||||
|
||||
#### 说明
|
||||
|
||||
  对Mutex加锁,返回Mutex的守卫,您可以使用这个守卫来操作被保护的数据。
|
||||
|
||||
  如果Mutex已经被加锁,那么,该方法会阻塞当前进程,直到Mutex被释放。
|
||||
|
||||
### 4.3. try_lock - 尝试加锁
|
||||
|
||||
#### 原型
|
||||
|
||||
```rust
|
||||
pub fn try_lock(&self) -> Result<MutexGuard<T>, i32>
|
||||
```
|
||||
|
||||
#### 说明
|
||||
|
||||
  尝试对Mutex加锁。如果加锁失败,不会将当前进程加入等待队列。如果加锁成功,返回Mutex的守卫;如果当前Mutex已经被加锁,返回`Err(错误码)`。
|
||||
|
||||
## 5. C版本的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;
|
||||
```
|
||||
|
||||
### 5.1. 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。
|
@ -71,6 +71,24 @@ let x :SpinLock<Vec<i32>>= SpinLock::new(Vec::new());
|
||||
kdebug!("x={:?}", x);
|
||||
```
|
||||
|
||||
  对于结构体内部的变量,我们可以使用SpinLock进行细粒度的加锁,也就是使用SpinLock包裹需要细致加锁的成员变量,比如这样:
|
||||
|
||||
```rust
|
||||
pub struct a {
|
||||
pub data: SpinLock<data_struct>,
|
||||
}
|
||||
```
|
||||
|
||||
  当然,我们也可以对整个结构体进行加锁:
|
||||
|
||||
```rust
|
||||
struct MyStruct {
|
||||
pub data: data_struct,
|
||||
}
|
||||
/// 被全局加锁的结构体
|
||||
pub struct LockedMyStruct(SpinLock<MyStruct>);
|
||||
```
|
||||
|
||||
### 3.2. 原理
|
||||
|
||||
  `SpinLock`之所以能够实现编译期检查,是因为它引入了一个`SpinLockGuard`作为守卫。我们在编写代码的时候,保证只有调用`SpinLock`的`lock()`方法加锁后,才能生成一个`SpinLockGuard`。 并且,当我们想要访问受保护的数据的时候,都必须获得一个守卫。然后,我们为`SpinLockGuard`实现了`Drop` trait,当守卫的生命周期结束时,将会自动释放锁。除此以外,没有别的方法能够释放锁。因此我们能够得知,一个上下文中,只要`SpinLockGuard`的生命周期没有结束,那么它就拥有临界区数据的访问权,数据访问就是安全的。
|
||||
|
@ -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,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)]
|
||||
|
@ -6,4 +6,5 @@ pub mod refcount;
|
||||
pub mod atomic;
|
||||
pub mod list;
|
||||
pub mod lockref;
|
||||
pub mod mutex;
|
||||
pub mod wait_queue;
|
||||
|
169
kernel/src/libs/mutex.rs
Normal file
169
kernel/src/libs/mutex.rs
Normal file
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user