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

This commit is contained in:
login 2023-01-14 10:35:49 +08:00 committed by GitHub
parent 41474ba3df
commit ec53d23ed0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 14 deletions

View File

@ -3,4 +3,4 @@
您可以从以下镜像站下载到DragonOS的源代码和其他文件
- [DragonOS镜像站](https://mirrors.dragonos.org/)
- [DragonOS国内镜像站 (RingoTek)](https://mirrors.RinGoTek.cn)
- [DragonOS国内镜像站 (RinGoTek)](https://mirrors.RinGoTek.cn)

View File

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

View File

@ -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信号量是基于计数实现的。

View File

@ -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就能访问临界区内的数据。

View File

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

View File

@ -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();
}
}