diff --git a/docs/kernel/locking/lockref.md b/docs/kernel/locking/lockref.md index 8aed07bb..2e75fa5e 100644 --- a/docs/kernel/locking/lockref.md +++ b/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` +- `pub fn inc_not_dead(&mut self) -> Result` + +#### 4.1.1. inc + +##### 说明 + +  原子的将引用计数加1。 + +##### 返回值 + +  无 + +#### 4.1.2. inc_not_zero + +##### 说明 + +  原子地将引用计数加1.如果原来的count≤0,则操作失败。 + +##### 返回值 + +| 返回值 | 说明 | +| :--- | :--- | +| Ok(self.count) | 成功,返回新的引用计数 | +| Err(-1) | 失败,返回-1 | + +#### 4.1.3. inc_not_dead + +##### 说明 + +  引用计数自增1。(除非该lockref已经被标记为死亡) + +##### 返回值 + +| 返回值 | 说明 | +| :--- | :--- | +| Ok(self.count) | 成功,返回新的引用计数 | +| Err(-1) | 失败,返回-1 | + +### 4.2. 引用计数自减 +- `pub fn dec(&mut self) -> Result` +- `pub fn dec_return(&mut self) -> Result` +- `pub fn dec_not_zero(&mut self) -> Result` +- `pub fn dec_or_lock_not_zero(&mut self) -> Result` + +#### 4.2.1. dec + +##### 说明 + +  原子地将引用计数-1。如果已处于count≤0的状态,则返回Err(-1) + +  本函数与`lockref_dec_return()`的区别在于,当在`cmpxchg()`中检测到`count<=0`或已加锁,本函数会再次尝试通过加锁来执行操作,而`lockref_dec_return()`会直接返回错误 + +##### 返回值 + +| 返回值 | 说明 | +| :--- | :--- | +| Ok(self.count) | 成功,返回新的引用计数 | +| Err(-1) | 失败,返回-1 | + +#### 4.2.2. dec_return + +  原子地将引用计数减1。如果处于已加锁或count≤0的状态,则返回-1 + +  本函数与`lockref_dec()`的区别在于,当在`cmpxchg()`中检测到`count<=0`或已加锁,本函数会直接返回错误,而`lockref_dec()`会再次尝试通过加锁来执行操作. + +:::{note} +若当前处理器架构不支持cmpxchg,则退化为`self.dec()` +::: + +##### 返回值 + +| 返回值 | 说明 | +| :--- | :--- | +| Ok(self.count) | 成功,返回新的引用计数 | +| Err(-1) | 失败,返回-1 | + +#### 4.2.3. dec_not_zero + +##### 说明 + +  原子地将引用计数减1。若当前的引用计数≤1,则操作失败. + +  该函数与`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 + +##### 说明 + +  原子地将引用计数减1。若当前的引用计数≤1,则操作失败. + +  该函数与`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 + +##### 说明 + +  将引用计数原子地标记为死亡状态. + ## 参考资料   [Introducing lockrefs - LWN.net, Jonathan Corbet](https://lwn.net/Articles/565734/) diff --git a/kernel/src/arch/x86_64/asm/cmpxchg.c b/kernel/src/arch/x86_64/asm/cmpxchg.c new file mode 100644 index 00000000..77c2a4e6 --- /dev/null +++ b/kernel/src/arch/x86_64/asm/cmpxchg.c @@ -0,0 +1,7 @@ +#include + +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; +} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/asm/cmpxchg.rs b/kernel/src/arch/x86_64/asm/cmpxchg.rs new file mode 100644 index 00000000..306e1863 --- /dev/null +++ b/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; +} diff --git a/kernel/src/arch/x86_64/asm/mod.rs b/kernel/src/arch/x86_64/asm/mod.rs index 6ed5e1f1..84c2473a 100644 --- a/kernel/src/arch/x86_64/asm/mod.rs +++ b/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; \ No newline at end of file +pub mod bitops; +pub mod cmpxchg; \ No newline at end of file diff --git a/kernel/src/arch/x86_64/cpu.rs b/kernel/src/arch/x86_64/cpu.rs index bc4b5c5d..89f48387 100644 --- a/kernel/src/arch/x86_64/cpu.rs +++ b/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"); + } +} diff --git a/kernel/src/arch/x86_64/include/asm/cmpxchg.h b/kernel/src/arch/x86_64/include/asm/cmpxchg.h index d27f83f6..c890a135 100644 --- a/kernel/src/arch/x86_64/include/asm/cmpxchg.h +++ b/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); \ No newline at end of file diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 606f6b02..4c5b7e44 100644 --- a/kernel/src/lib.rs +++ b/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; } diff --git a/kernel/src/libs/lockref.rs b/kernel/src/libs/lockref.rs new file mode 100644 index 00000000..d1c36169 --- /dev/null +++ b/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::(), 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 { + 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 { + 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 操作成功=>Ok(self.count) + * 操作失败=>Err(-1) + */ + pub fn inc_not_zero(&mut self) -> Result { + { + 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 { + { + 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 { + { + let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::Decrease); + if cmpxchg_result.is_ok() { + return cmpxchg_result; + } + } + let retval: Result; + 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 { + 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 { + { + 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; + 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 { + { + let cmpxchg_result = self.cmpxchg_loop(CmpxchgMode::DecreaseOrLockNotZero); + if cmpxchg_result.is_ok() { + return cmpxchg_result; + } + } + + let retval: Result; + 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); + */ \ No newline at end of file diff --git a/kernel/src/libs/mod.rs b/kernel/src/libs/mod.rs index 310fb132..620fa802 100644 --- a/kernel/src/libs/mod.rs +++ b/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; \ No newline at end of file +pub mod list; +pub mod lockref; \ No newline at end of file diff --git a/kernel/src/libs/spinlock.rs b/kernel/src/libs/spinlock.rs index 71c51122..db523cfe 100644 --- a/kernel/src/libs/spinlock.rs +++ b/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