Jelajahi Sumber

新增rust版本的lockref (#135)

* new:Rust封装cpu_relax(),通过pause指令,让cpu休息一会儿。降低空转功耗

* new: Rust版本的lockref

* Rust的RawSpinlock新增is_locked()和set_value()方法。

* lockref文档
login 2 tahun lalu
induk
melakukan
61de2cdc3f

+ 149 - 3
docs/kernel/locking/lockref.md

@@ -3,8 +3,31 @@
 
   lockref是将自旋锁与引用计数变量融合在连续、对齐的8字节内的一种技术。
 
-## lockref结构
+  目前,DragonOS中,通过C、Rust各实现了一个版本的lockref。请注意,二者不兼容。对于新的功能模块,请使用Rust版本的lockref。随着代码重构工作的进行,我们将会删除C版本的lockref。
 
+## 1. lockref结构
+
+### 1.1. Rust版本
+```rust
+/// 仅在x86_64架构下使用cmpxchg
+#[cfg(target_arch = "x86_64")]
+/// 由于需要cmpxchg,所以整个lockref按照8字节对齐
+#[repr(align(8))]
+#[derive(Debug)]
+pub struct LockRef {
+    pub lock: RawSpinlock,
+    pub count: i32,
+}
+
+/// 除了x86_64以外的架构,不使用cmpxchg进行优化
+#[cfg(not(target_arch = "x86_64"))]
+pub struct LockRef {
+    lock: RawSpinlock,
+    count: i32,
+}
+```
+
+### 1.2. C版本
 ```c
 struct lockref
 {
@@ -21,15 +44,138 @@ struct lockref
     };
 };
 ```
-## 特性描述
+
+## 2. 特性描述
   由于在高负载的情况下,系统会频繁的执行“锁定-改变引用变量-解锁”的操作,这期间很可能出现spinlock和引用计数跨缓存行的情况,这将会大大降低性能。lockref通过强制对齐,尽可能的降低缓存行的占用数量,使得性能得到提升。
 
   并且,在x64体系结构下,还通过cmpxchg()指令,实现了无锁快速路径。不需要对自旋锁加锁即可更改引用计数的值,进一步提升性能。当快速路径不存在(对于未支持的体系结构)或者尝试超时后,将会退化成“锁定-改变引用变量-解锁”的操作。此时由于lockref强制对齐,只涉及到1个缓存行,因此性能比原先的spinlock+ref_count的模式要高。
 
-## 关于cmpxchg_loop
+## 3. 关于cmpxchg_loop
 
   在改变引用计数时,cmpxchg先确保没有别的线程持有锁,然后改变引用计数,同时通过`lock cmpxchg`指令验证在更改发生时,没有其他线程持有锁,并且当前的目标lockref的值与old变量中存储的一致,从而将新值存储到目标lockref。这种无锁操作能极大的提升性能。如果不符合上述条件,在多次尝试后,将退化成传统的加锁方式来更改引用计数。
 
+## 4. Rust版本的API
+
+### 4.1. 引用计数自增
+
+- `pub fn inc(&mut self)`
+- `pub fn inc_not_zero(&mut self) -> Result<i32, i32>`
+- `pub fn inc_not_dead(&mut self) -> Result<i32, i32>`
+
+#### 4.1.1. inc
+
+##### 说明
+
+&emsp;&emsp;原子的将引用计数加1。
+
+##### 返回值
+
+&emsp;&emsp;无
+
+#### 4.1.2. inc_not_zero
+
+##### 说明
+
+&emsp;&emsp;原子地将引用计数加1.如果原来的count≤0,则操作失败。
+
+##### 返回值
+
+|   返回值          |      说明    |
+|   :---        |   :---      |
+| Ok(self.count) | 成功,返回新的引用计数 |
+| Err(-1)       | 失败,返回-1 |
+
+#### 4.1.3. inc_not_dead
+
+##### 说明
+
+&emsp;&emsp;引用计数自增1。(除非该lockref已经被标记为死亡)
+
+##### 返回值
+
+|   返回值          |      说明    |
+|   :---        |   :---      |
+| Ok(self.count) | 成功,返回新的引用计数 |
+| Err(-1)       | 失败,返回-1 |
+
+### 4.2. 引用计数自减
+- `pub fn dec(&mut self) -> Result<i32, i32>`
+- `pub fn dec_return(&mut self) -> Result<i32, i32>`
+- `pub fn dec_not_zero(&mut self) -> Result<i32, i32>`
+- `pub fn dec_or_lock_not_zero(&mut self) -> Result<i32, i32>`
+
+#### 4.2.1. dec
+
+##### 说明
+
+&emsp;&emsp;原子地将引用计数-1。如果已处于count≤0的状态,则返回Err(-1)
+
+&emsp;&emsp;本函数与`lockref_dec_return()`的区别在于,当在`cmpxchg()`中检测到`count<=0`或已加锁,本函数会再次尝试通过加锁来执行操作,而`lockref_dec_return()`会直接返回错误
+
+##### 返回值
+
+|   返回值          |      说明    |
+|   :---        |   :---      |
+| Ok(self.count) | 成功,返回新的引用计数 |
+| Err(-1)       | 失败,返回-1 |
+
+#### 4.2.2. dec_return
+
+&emsp;&emsp;原子地将引用计数减1。如果处于已加锁或count≤0的状态,则返回-1
+
+&emsp;&emsp;本函数与`lockref_dec()`的区别在于,当在`cmpxchg()`中检测到`count<=0`或已加锁,本函数会直接返回错误,而`lockref_dec()`会再次尝试通过加锁来执行操作.
+
+:::{note}
+若当前处理器架构不支持cmpxchg,则退化为`self.dec()`
+:::
+
+##### 返回值
+
+|   返回值          |      说明    |
+|   :---        |   :---      |
+| Ok(self.count) | 成功,返回新的引用计数 |
+| Err(-1)       | 失败,返回-1 |
+
+#### 4.2.3. dec_not_zero
+
+##### 说明
+
+&emsp;&emsp;原子地将引用计数减1。若当前的引用计数≤1,则操作失败.
+
+&emsp;&emsp;该函数与`lockref_dec_or_lock_not_zero()`的区别在于,当`cmpxchg()`时发现`old.count≤1`时,该函数会直接返回`Err(-1)`,而`lockref_dec_or_lock_not_zero()`在这种情况下,会尝试加锁来进行操作。
+
+##### 返回值
+
+|   返回值          |      说明    |
+|   :---        |   :---      |
+| Ok(self.count) | 成功,返回新的引用计数 |
+| Err(-1)       | 失败,返回-1 |
+
+
+#### 4.2.4. dec_or_lock_not_zero
+
+##### 说明
+
+&emsp;&emsp;原子地将引用计数减1。若当前的引用计数≤1,则操作失败.
+
+&emsp;&emsp;该函数与`lockref_dec_not_zero()`的区别在于,当cmpxchg()时发现`old.count≤1`时,该函数会尝试加锁来进行操作,而`lockref_dec_not_zero()`在这种情况下,会直接返回`Err(-1)`.
+
+##### 返回值
+
+|   返回值          |      说明    |
+|   :---        |   :---      |
+| Ok(self.count) | 成功,返回新的引用计数 |
+| Err(-1)       | 失败,返回-1 |
+
+### 4.3. 其他
+- `pub fn mark_dead(&mut self)`
+
+#### 4.3.1. mark_dead
+
+##### 说明
+
+&emsp;&emsp;将引用计数原子地标记为死亡状态.
+
 ## 参考资料
 
 &emsp;&emsp;[Introducing lockrefs - LWN.net, Jonathan Corbet](https://lwn.net/Articles/565734/)

+ 7 - 0
kernel/src/arch/x86_64/asm/cmpxchg.c

@@ -0,0 +1,7 @@
+#include <arch/x86_64/include/asm/cmpxchg.h>
+
+bool __try_cmpxchg_q(uint64_t *ptr, uint64_t *old_ptr, uint64_t *new_ptr)
+{
+    bool success = __raw_try_cmpxchg(ptr, old_ptr, *new_ptr, 8);
+    return success;
+}

+ 12 - 0
kernel/src/arch/x86_64/asm/cmpxchg.rs

@@ -0,0 +1,12 @@
+// 该函数在cmpxchg.c中实现
+extern "C" {
+    fn __try_cmpxchg_q(ptr: *mut u64, old_ptr: *mut u64, new_ptr: *mut u64) -> bool;
+}
+
+/// @brief 封装lock cmpxchg指令
+/// 由于Rust实现这部分的内联汇编比较麻烦(实在想不出办法),因此使用C的实现。
+#[inline]
+pub unsafe fn try_cmpxchg_q(ptr: *mut u64, old_ptr: *mut u64, new_ptr: *mut u64) -> bool {
+    let retval = __try_cmpxchg_q(ptr, old_ptr, new_ptr);
+    return retval;
+}

+ 2 - 1
kernel/src/arch/x86_64/asm/mod.rs

@@ -2,4 +2,5 @@ pub mod irqflags;
 #[macro_use]
 pub mod current;
 pub mod ptrace;
-pub mod bitops;
+pub mod bitops;
+pub mod cmpxchg;

+ 7 - 0
kernel/src/arch/x86_64/cpu.rs

@@ -14,3 +14,10 @@ pub fn arch_current_apic_id() -> u8 {
     }
     return (cpuid_res >> 24) as u8;
 }
+
+/// @brief 通过pause指令,让cpu休息一会儿。降低空转功耗
+pub fn cpu_relax() {
+    unsafe {
+        asm!("pause");
+    }
+}

+ 2 - 0
kernel/src/arch/x86_64/include/asm/cmpxchg.h

@@ -79,3 +79,5 @@ extern void __cmpxchg_wrong_size(void) __compiletime_error("Bad argument size fo
 
 #define arch_try_cmpxchg(ptr, old_ptr, new_ptr) \
     __raw_try_cmpxchg((ptr), (old_ptr), (new_ptr), sizeof(*ptr))
+
+bool __try_cmpxchg_q(uint64_t *ptr, uint64_t *old_ptr, uint64_t *new_ptr);

+ 2 - 2
kernel/src/lib.rs

@@ -29,8 +29,6 @@ mod sched;
 mod smp;
 mod time;
 
-
-
 extern crate alloc;
 
 use mm::allocator::KernelAllocator;
@@ -39,6 +37,7 @@ use mm::allocator::KernelAllocator;
 use crate::{
     arch::asm::current::current_pcb,
     include::bindings::bindings::{process_do_exit, BLACK, GREEN},
+    libs::lockref::LockRef,
 };
 
 // 声明全局的slab分配器
@@ -85,5 +84,6 @@ pub fn panic(info: &PanicInfo) -> ! {
 #[no_mangle]
 pub extern "C" fn __rust_demo_func() -> i32 {
     printk_color!(GREEN, BLACK, "__rust_demo_func()\n");
+
     return 0;
 }

+ 355 - 0
kernel/src/libs/lockref.rs

@@ -0,0 +1,355 @@
+#![allow(dead_code)]
+use super::spinlock::RawSpinlock;
+use crate::{
+    arch::asm::cmpxchg::try_cmpxchg_q,
+    include::bindings::bindings::{ENOTSUP, ETIMEDOUT},
+};
+use core::{fmt::Debug, intrinsics::size_of};
+
+#[cfg(target_arch = "x86_64")]
+/// 由于需要cmpxchg,所以整个lockref按照8字节对齐
+#[repr(align(8))]
+#[derive(Debug)]
+pub struct LockRef {
+    pub lock: RawSpinlock,
+    pub count: i32,
+}
+
+/// 除了x86_64以外的架构,不使用cmpxchg进行优化
+#[cfg(not(target_arch = "x86_64"))]
+pub struct LockRef {
+    lock: RawSpinlock,
+    count: i32,
+}
+
+enum CmpxchgMode {
+    Increase,
+    IncreaseNotZero,
+    IncreaseNotDead,
+    Decrease,
+    DecreaseReturn,
+    DecreaseNotZero,
+    DecreaseOrLockNotZero,
+}
+
+impl LockRef {
+    pub const INIT: LockRef = LockRef {
+        lock: RawSpinlock::INIT,
+        count: 0,
+    };
+
+    pub fn new() -> LockRef {
+        assert_eq!(size_of::<LockRef>(), 8);
+        return LockRef::INIT;
+    }
+
+    /// @brief 为X86架构实现cmpxchg循环,以支持无锁操作。
+    ///
+    /// @return 操作成功:返回Ok(new.count)
+    /// @return 操作失败,原因:超时 => 返回Err(-ETIMEDOUT)
+    /// @return 操作失败,原因:不满足规则 => 返回Err(1)
+    #[cfg(target_arch = "x86_64")]
+    #[inline]
+    fn cmpxchg_loop(&mut self, mode: CmpxchgMode) -> Result<i32, i32> {
+        use core::ptr::read_volatile;
+
+        use crate::arch::cpu::cpu_relax;
+
+        let mut old: LockRef = LockRef::INIT;
+        old.count = unsafe { read_volatile(&self.count) };
+        for _ in 0..100 {
+            if !old.lock.is_locked() {
+                let mut new = LockRef::INIT;
+                unsafe {
+                    *(&mut new as *mut LockRef as *mut usize as *mut u64) =
+                        read_volatile(&mut old as *mut LockRef as *mut usize as *mut u64);
+                    new.lock.set_value(false);
+                }
+
+                // 根据不同情况,执行不同代码
+                match mode {
+                    CmpxchgMode::Increase => {
+                        new.count += 1;
+                    }
+                    CmpxchgMode::IncreaseNotZero => {
+                        // 操作失败
+                        if old.count <= 0 {
+                            return Err(1);
+                        }
+                        new.count += 1;
+                    }
+
+                    CmpxchgMode::IncreaseNotDead => {
+                        if old.count < 0 {
+                            return Err(1);
+                        }
+                        new.count += 1;
+                    }
+
+                    CmpxchgMode::Decrease | CmpxchgMode::DecreaseReturn => {
+                        if old.count <= 0 {
+                            return Err(1);
+                        }
+                        new.count -= 1;
+                    }
+                    CmpxchgMode::DecreaseNotZero | CmpxchgMode::DecreaseOrLockNotZero => {
+                        if old.count <= 1 {
+                            return Err(1);
+                        }
+                        new.count -= 1;
+                    }
+                }
+
+                if unsafe {
+                    try_cmpxchg_q(
+                        self as *mut LockRef as *mut usize as *mut u64,
+                        &mut old as *mut LockRef as *mut usize as *mut u64,
+                        &mut new as *mut LockRef as *mut usize as *mut u64,
+                    )
+                } {
+                    // 无锁操作成功,返回新的值
+                    return Ok(new.count);
+                }
+                cpu_relax();
+            }
+        }
+
+        return Err(-(ETIMEDOUT as i32));
+    }
+
+    /// @brief 对于不支持无锁lockref的架构,直接返回Err(-ENOTSUP),表示不支持
+    #[cfg(not(target_arch = "x86_64"))]
+    #[inline]
+    fn cmpxchg_loop(&mut self, mode: CmpxchgMode) -> Result<i32, i32> {
+        use crate::include::bindings::bindings::ENOTSUP;
+
+        return Err(-(ENOTSUP as i32));
+    }
+
+    /// @brief 原子的将引用计数加1
+    pub fn inc(&mut self) {
+        let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::Increase);
+        if cmpxchg_result.is_ok() {
+            return;
+        }
+
+        self.lock.lock();
+        self.count += 1;
+        self.lock.unlock();
+    }
+
+    /**
+     * @brief 原子地将引用计数加1.如果原来的count≤0,则操作失败。
+     *
+     * @return Result<i32, i32>     操作成功=>Ok(self.count)
+     *                              操作失败=>Err(-1)
+     */
+    pub fn inc_not_zero(&mut self) -> Result<i32, i32> {
+        {
+            let cmpxchg_res = self.cmpxchg_loop(CmpxchgMode::IncreaseNotZero);
+            if cmpxchg_res.is_ok() {
+                return cmpxchg_res;
+            } else if cmpxchg_res.unwrap_err() == 1 {
+                // 不满足not zero 的条件
+                return Err(-1);
+            }
+        }
+
+        let mut retval = Err(-1);
+        self.lock.lock();
+
+        if self.count > 0 {
+            self.count += 1;
+            retval = Ok(self.count);
+        }
+
+        self.lock.unlock();
+        return retval;
+    }
+
+    /**
+     * @brief 引用计数自增1。(除非该lockref已经被标记为死亡)
+     *
+     * @return Ok(self.count) 操作成功
+     * @return Err(-1) 操作失败,lockref已死亡
+     */
+    pub fn inc_not_dead(&mut self) -> Result<i32, i32> {
+        {
+            let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::IncreaseNotDead);
+            if cmpxchg_result.is_ok() {
+                return cmpxchg_result;
+            } else if cmpxchg_result.unwrap_err() == 1 {
+                return Err(-1);
+            }
+        }
+        // 快捷路径操作失败,尝试加锁
+        let mut retval = Err(-1);
+
+        self.lock.lock();
+        if self.count >= 0 {
+            self.count += 1;
+            retval = Ok(self.count);
+        }
+        self.lock.unlock();
+        return retval;
+    }
+
+    /**
+     * @brief 原子地将引用计数-1。如果已处于count≤0的状态,则返回Err(-1)
+     *
+     * 本函数与lockref_dec_return()的区别在于,当在cmpxchg()中检测到count<=0或已加锁,本函数会再次尝试通过加锁来执行操作
+     * 而后者会直接返回错误
+     *
+     * @return int 操作成功 => 返回新的引用变量值
+     *             操作失败lockref处于count≤0的状态 => 返回-1
+     */
+    pub fn dec(&mut self) -> Result<i32, i32> {
+        {
+            let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::Decrease);
+            if cmpxchg_result.is_ok() {
+                return cmpxchg_result;
+            }
+        }
+        let retval: Result<i32, i32>;
+        self.lock.lock();
+
+        if self.count > 0 {
+            self.count -= 1;
+            retval = Ok(self.count);
+        } else {
+            retval = Err(-1);
+        }
+
+        self.lock.unlock();
+
+        return retval;
+    }
+
+    /**
+     * @brief 原子地将引用计数减1。如果处于已加锁或count≤0的状态,则返回-1
+     *      若当前处理器架构不支持cmpxchg,则退化为self.dec()
+     *
+     * 本函数与lockref_dec()的区别在于,当在cmpxchg()中检测到count<=0或已加锁,本函数会直接返回错误
+     * 而后者会再次尝试通过加锁来执行操作
+     *
+     * @return int  操作成功 => 返回新的引用变量值
+     *              操作失败,lockref处于已加锁或count≤0的状态 => 返回-1
+     */
+    pub fn dec_return(&mut self) -> Result<i32, i32> {
+        let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::DecreaseReturn);
+        if cmpxchg_result.is_ok() {
+            return cmpxchg_result;
+        } else if cmpxchg_result.unwrap_err() == 1 {
+            return Err(-1);
+        }
+
+        // 由于cmpxchg超时,操作失败
+        if cmpxchg_result.unwrap_err() != -(ENOTSUP as i32) {
+            return Err(-1);
+        }
+
+        // 能走到这里,代表架构当前不支持cmpxchg
+        // 退化为直接dec,加锁
+        return self.dec();
+    }
+
+    /**
+     * @brief 原子地将引用计数减1。若当前的引用计数≤1,则操作失败
+     *
+     * 该函数与lockref_dec_or_lock_not_zero()的区别在于,当cmpxchg()时发现old.count≤1时,该函数会直接返回Err(-1)
+     * 而后者在这种情况下,会尝试加锁来进行操作。
+     *
+     * @return Ok(self.count) 成功将引用计数减1
+     * @return Err(-1) 如果当前的引用计数≤1,操作失败
+     */
+    pub fn dec_not_zero(&mut self) -> Result<i32, i32> {
+        {
+            let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::DecreaseNotZero);
+            if cmpxchg_result.is_ok() {
+                return cmpxchg_result;
+            } else if cmpxchg_result.unwrap_err() == 1 {
+                return Err(-1);
+            }
+        }
+
+        let retval: Result<i32, i32>;
+        self.lock.lock();
+        if self.count > 1 {
+            self.count -= 1;
+            retval = Ok(self.count);
+        } else {
+            retval = Err(-1);
+        }
+        self.lock.unlock();
+        return retval;
+    }
+
+    /**
+     * @brief 原子地将引用计数减1。若当前的引用计数≤1,则操作失败
+     *
+     * 该函数与lockref_dec_not_zero()的区别在于,当cmpxchg()时发现old.count≤1时,该函数会尝试加锁来进行操作。
+     * 而后者在这种情况下,会直接返回Err(-1).
+     *
+     * @return Ok(self.count) 成功将引用计数减1
+     * @return Err(-1) 如果当前的引用计数≤1,操作失败
+     */
+    pub fn dec_or_lock_not_zero(&mut self) -> Result<i32, i32> {
+        {
+            let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::DecreaseOrLockNotZero);
+            if cmpxchg_result.is_ok() {
+                return cmpxchg_result;
+            }
+        }
+
+        let retval: Result<i32, i32>;
+        self.lock.lock();
+        if self.count > 1 {
+            self.count -= 1;
+            retval = Ok(self.count);
+        } else {
+            retval = Err(-1);
+        }
+        self.lock.unlock();
+        return retval;
+    }
+
+    /**
+     * @brief 原子地将lockref变量标记为已经死亡(将count设置为负值)
+     */
+    pub fn mark_dead(&mut self) {
+        self.lock.lock();
+        self.count = -128;
+        self.lock.unlock();
+    }
+}
+
+/*
+ * 您可以使用以下代码测试lockref
+
+    let mut lockref = LockRef::new();
+    kdebug!("lockref={:?}", lockref);
+    lockref.inc();
+    assert_eq!(lockref.count, 1);
+    kdebug!("lockref={:?}", lockref);
+    assert!(lockref.dec().is_ok());
+    assert_eq!(lockref.count, 0);
+
+    assert!(lockref.dec().is_err());
+    assert_eq!(lockref.count, 0);
+
+    lockref.inc();
+    assert_eq!(lockref.count, 1);
+
+    assert!(lockref.dec_not_zero().is_err());
+
+    lockref.inc();
+    assert_eq!(lockref.count, 2);
+
+    assert!(lockref.dec_not_zero().is_ok());
+
+    lockref.mark_dead();
+    assert!(lockref.count < 0);
+    
+    assert!(lockref.inc_not_dead().is_err());
+    kdebug!("lockref={:?}", lockref);
+ */

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

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

+ 19 - 0
kernel/src/libs/spinlock.rs

@@ -57,6 +57,10 @@ pub fn spin_unlock_irq(lock: *mut spinlock_t) {
     sti();
 }
 
+/// 原始的Spinlock(自旋锁)
+/// 请注意,这个自旋锁和C的不兼容。
+/// 
+/// @param self.0 这个AtomicBool的值为false时,表示没有被加锁。当它为true时,表示自旋锁已经被上锁。
 #[derive(Debug)]
 pub struct RawSpinlock(AtomicBool);
 
@@ -107,6 +111,21 @@ impl RawSpinlock {
         sti();
     }
 
+    /// @brief 判断自旋锁是否被上锁
+    /// 
+    /// @return true 自旋锁被上锁
+    /// @return false 自旋锁处于解锁状态
+    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){
+        self.0.store(value, Ordering::SeqCst);
+    }
+
     // todo: spin_lock_irqsave
     // todo: spin_unlock_irqrestore