1(_lockref)= 2# lockref 3 4  lockref是将自旋锁与引用计数变量融合在连续、对齐的8字节内的一种技术。 5 6  目前,DragonOS中,通过C、Rust各实现了一个版本的lockref。请注意,二者不兼容。对于新的功能模块,请使用Rust版本的lockref。随着代码重构工作的进行,我们将会删除C版本的lockref。 7 8## 1. lockref结构 9 10### 1.1. Rust版本 11```rust 12/// 仅在x86_64架构下使用cmpxchg 13#[cfg(target_arch = "x86_64")] 14/// 由于需要cmpxchg,所以整个lockref按照8字节对齐 15#[repr(align(8))] 16#[derive(Debug)] 17pub struct LockRef { 18 pub lock: RawSpinlock, 19 pub count: i32, 20} 21 22/// 除了x86_64以外的架构,不使用cmpxchg进行优化 23#[cfg(not(target_arch = "x86_64"))] 24pub struct LockRef { 25 lock: RawSpinlock, 26 count: i32, 27} 28``` 29 30### 1.2. C版本 31```c 32struct lockref 33{ 34 union 35 { 36#ifdef __LOCKREF_ENABLE_CMPXCHG__ 37 aligned_u64 lock_count; // 通过该变量的声明,使得整个lockref的地址按照8字节对齐 38#endif 39 struct 40 { 41 spinlock_t lock; 42 int count; 43 }; 44 }; 45}; 46``` 47 48## 2. 特性描述 49  由于在高负载的情况下,系统会频繁的执行“锁定-改变引用变量-解锁”的操作,这期间很可能出现spinlock和引用计数跨缓存行的情况,这将会大大降低性能。lockref通过强制对齐,尽可能的降低缓存行的占用数量,使得性能得到提升。 50 51  并且,在x64体系结构下,还通过cmpxchg()指令,实现了无锁快速路径。不需要对自旋锁加锁即可更改引用计数的值,进一步提升性能。当快速路径不存在(对于未支持的体系结构)或者尝试超时后,将会退化成“锁定-改变引用变量-解锁”的操作。此时由于lockref强制对齐,只涉及到1个缓存行,因此性能比原先的spinlock+ref_count的模式要高。 52 53## 3. 关于cmpxchg_loop 54 55  在改变引用计数时,cmpxchg先确保没有别的线程持有锁,然后改变引用计数,同时通过`lock cmpxchg`指令验证在更改发生时,没有其他线程持有锁,并且当前的目标lockref的值与old变量中存储的一致,从而将新值存储到目标lockref。这种无锁操作能极大的提升性能。如果不符合上述条件,在多次尝试后,将退化成传统的加锁方式来更改引用计数。 56 57## 4. Rust版本的API 58 59### 4.1. 引用计数自增 60 61- `pub fn inc(&mut self)` 62- `pub fn inc_not_zero(&mut self) -> Result<i32, SystemError>` 63- `pub fn inc_not_dead(&mut self) -> Result<i32, SystemError>` 64 65#### 4.1.1. inc 66 67##### 说明 68 69  原子的将引用计数加1。 70 71##### 返回值 72 73  无 74 75#### 4.1.2. inc_not_zero 76 77##### 说明 78 79  原子地将引用计数加1.如果原来的count≤0,则操作失败。 80 81##### 返回值 82 83| 返回值 | 说明 | 84| :--- | :--- | 85| Ok(self.count) | 成功,返回新的引用计数 | 86| Err(SystemError::EPERM) | 失败,返回EPERM | 87 88#### 4.1.3. inc_not_dead 89 90##### 说明 91 92  引用计数自增1。(除非该lockref已经被标记为死亡) 93 94##### 返回值 95 96| 返回值 | 说明 | 97| :--- | :--- | 98| Ok(self.count) | 成功,返回新的引用计数 | 99| Err(SystemError::EPERM) | 失败,返回EPERM | 100 101### 4.2. 引用计数自减 102- `pub fn dec(&mut self) -> Result<i32, SystemError>` 103- `pub fn dec_return(&mut self) -> Result<i32, SystemError>` 104- `pub fn dec_not_zero(&mut self) -> Result<i32, SystemError>` 105- `pub fn dec_or_lock_not_zero(&mut self) -> Result<i32, SystemError>` 106 107#### 4.2.1. dec 108 109##### 说明 110 111  原子地将引用计数-1。如果已处于count≤0的状态,则返回Err(SystemError::EPERM) 112 113  本函数与`lockref_dec_return()`的区别在于,当在`cmpxchg()`中检测到`count<=0`或已加锁,本函数会再次尝试通过加锁来执行操作,而`lockref_dec_return()`会直接返回错误 114 115##### 返回值 116 117| 返回值 | 说明 | 118| :--- | :--- | 119| Ok(self.count) | 成功,返回新的引用计数 | 120| Err(SystemError::EPERM) | 失败,返回EPERM | 121 122#### 4.2.2. dec_return 123 124  原子地将引用计数减1。如果处于已加锁或count≤0的状态,则返回SystemError::EPERM 125 126  本函数与`lockref_dec()`的区别在于,当在`cmpxchg()`中检测到`count<=0`或已加锁,本函数会直接返回错误,而`lockref_dec()`会再次尝试通过加锁来执行操作. 127 128:::{note} 129若当前处理器架构不支持cmpxchg,则退化为`self.dec()` 130::: 131 132##### 返回值 133 134| 返回值 | 说明 | 135| :--- | :--- | 136| Ok(self.count) | 成功,返回新的引用计数 | 137| Err(SystemError::EPERM) | 失败,返回EPERM | 138 139#### 4.2.3. dec_not_zero 140 141##### 说明 142 143  原子地将引用计数减1。若当前的引用计数≤1,则操作失败. 144 145  该函数与`lockref_dec_or_lock_not_zero()`的区别在于,当`cmpxchg()`时发现`old.count≤1`时,该函数会直接返回`Err(-1)`,而`lockref_dec_or_lock_not_zero()`在这种情况下,会尝试加锁来进行操作。 146 147##### 返回值 148 149| 返回值 | 说明 | 150| :--- | :--- | 151| Ok(self.count) | 成功,返回新的引用计数 | 152| Err(SystemError::EPERM) | 失败,返回EPERM | 153 154 155#### 4.2.4. dec_or_lock_not_zero 156 157##### 说明 158 159  原子地将引用计数减1。若当前的引用计数≤1,则操作失败. 160 161  该函数与`lockref_dec_not_zero()`的区别在于,当cmpxchg()时发现`old.count≤1`时,该函数会尝试加锁来进行操作,而`lockref_dec_not_zero()`在这种情况下,会直接返回`Err(SystemError::EPERM)`. 162 163##### 返回值 164 165| 返回值 | 说明 | 166| :--- | :--- | 167| Ok(self.count) | 成功,返回新的引用计数 | 168| Err(SystemError::EPERM) | 失败,返回EPERM | 169 170### 4.3. 其他 171- `pub fn mark_dead(&mut self)` 172 173#### 4.3.1. mark_dead 174 175##### 说明 176 177  将引用计数原子地标记为死亡状态. 178 179## 参考资料 180 181  [Introducing lockrefs - LWN.net, Jonathan Corbet](https://lwn.net/Articles/565734/) 182