读写自旋锁是自旋锁的一种提升,通过并发读提升性能。
读写锁自旋锁和自旋锁的数据结构一致:
struct {
raw_rwlock_t raw_lock;//分为两个不同部分,下面解释;
unsigned int break_lock;//当没有被读或者写时设置该位,否则清0;
} spinlock_t;
raw_rwlock_t {volatile unsigned int lock;}
可见,除了type名不同外,其他一致;
区别:
raw_rwlock_t raw_lock;
24位计数器,表示对受保护的数据结构并发读操作的内核控制路径数目,这个计数器的二进制补码存放在这个字段的0-23位;
“未锁”标志字段,当没有内核控制路径读/写时设置该位,否则清0。位于lock的24位;
自旋锁为空时,lock字段值为:0x0100 0000;若写着获取锁,则lock为0x0000 0000。若一个、两个或多个读者获取自旋锁,那么lock为:0xffff ffff,0xffff fffe等。
int _raw_read_trylock(rwlock_t *lock) {
atomic_t *count = (atomic_t*) lock->lock;
if (atomic_dec_return(count) >= 0) {//从count中减去1,返回新值;
return 1;
}
atomic_inc(count);
return 0;
}
注意:
尽管上述的读锁访问(读-修改-写)是原子的,但整个_raw_read_trylock()整个函数对计数器的操作并不是原子进行。在语句测试返回后,return 1之前,计数器的值可能发生变化。
但是函数能够正常工作。事实上,只有在递减之前计数器的值不为0或负数情况下,函数才返回1。因为计数器等于0x0100 0000表示没有任何进程占用锁,等于0x0fff ffff表示有一个读者,等于0x0000 0000表示有一个写者。
read_lock
static inline void __raw_read_lock(raw_rwlock_t *rw)
{
unsigned long tmp0, tmp1;
/*
* rw->lock : >0 : unlock
* : <=0 : lock
*
* for ( ; ; ) {
* rw->lock -= 1; <-- need atomic operation
* if (rw->lock >= 0) break;
* rw->lock += 1; <-- need atomic operation
* for ( ; rw->lock <= 0 ; );
* }
*/
__asm__ __volatile__ (
"# read_lock \n\t"
".fillinsn \n"
"1: \n\t"
"mvfc %1, psw; \n\t" //保存psw值到tmp1
"clrpsw #0x40 -> nop; \n\t"// ;
DCACHE_CLEAR("%0", "r6", "%2")
"lock %0, @%2; \n\t"
"addi %0, #-1; \n\t"
"unlock %0, @%2; \n\t"
"mvtc %1, psw; \n\t"
"bltz %0, 2f; \n\t"
LOCK_SECTION_START(".balign 4 \n\t")
".fillinsn \n"
"2: \n\t"
"clrpsw #0x40 -> nop; \n\t"
DCACHE_CLEAR("%0", "r6", "%2")
"lock %0, @%2; \n\t"
"addi %0, #1; \n\t"
"unlock %0, @%2; \n\t"
"mvtc %1, psw; \n\t"
".fillinsn \n"
"3: \n\t"
"ld %0, @%2; \n\t"
"bgtz %0, 1b; \n\t"
"bra 3b; \n\t"
LOCK_SECTION_END
: "=&r" (tmp0), "=&r" (tmp1)
: "r" (&rw->lock)
: "memory"
#ifdef CONFIG_CHIP_M32700_TS1
, "r6"
#endif /* CONFIG_CHIP_M32700_TS1 */
);
}
上段源码的部分指令看不懂,后续解释。
AT&T汇编语言语法,
转:https://blog.csdn.net/u014160900/article/details/44900303
https://blog.csdn.net/cedricporter/article/details/6787026
psw:
程序状态寄存器每一位均用来反映结算结果,如ZF标志表示运算结果为0时,返回1;否则返回0;
位编号 | 标志位 | 标志位名称 | =1 | =0 |
---|---|---|---|---|
0 | CF | 进位标志/Carry Flag | CY/Carry/进位 | NC/No Carry/无进位 |
2 | PF | 奇偶标志/Parity Flag | PE/Parity Even/偶 | PO/Parity Odd/奇 |
4 | AF | 辅助进位标志/Auxiliary Carry Flag | AC/Auxiliary Carry/进位 | NA/No Auxiliary Carry/无进位 |
6 | ZF | 零标志/Zero Flag | ZR/Zero/等于零 | NZ/Not Zero/不等于零 |
7 | SF | 符号标志/Sign Flag | NG/Negative/负 | PL/Positive/非负 |
8 | TF | 跟踪标志/Trace Flag | ||
9 | IF | 中断标志/Interrupt Flag | EI/Enable Interrupt/允许 | DI/Disable Interrupt/禁止 |
10 | DF | 方向标志/Direction Flag | DN/Down/减少 | UP/增加 |
11 | OF | 溢出标志/Overflow Flag | OV/Overflow/溢出 | NV/Not Overflow/未溢出 |
若支持内核抢占,则禁用抢占,并通过_raw_write_trylock()立即获得锁。若函数返回0,则说明锁已经被占用,则需要重新启用抢占,并开始忙等。
_raw_write_trylock(rwlock_t *lock) {
atomic_t *count = (atomic_t*) lock->lock;
// 从count减去0x01000000,结果为0,则返回1;否则返回0
if (atomic_sub_and_test(0x01000000, count))
return 1;
atomic_add(0x01000000, count);
return 0;
}
write_lock:
static inline void __raw_write_lock(raw_rwlock_t *rw)
{
unsigned long tmp0, tmp1, tmp2;
/*
* rw->lock : =RW_LOCK_BIAS_STR : unlock
* : !=RW_LOCK_BIAS_STR : lock
*
* for ( ; ; ) {
* rw->lock -= RW_LOCK_BIAS_STR; <-- need atomic operation 0x0100 0000
* if (rw->lock == 0) break;
* rw->lock += RW_LOCK_BIAS_STR; <-- need atomic operation
* for ( ; rw->lock != RW_LOCK_BIAS_STR ; ) ;
* }
*/
__asm__ __volatile__ (
"# write_lock \n\t"
"seth %1, #high(" RW_LOCK_BIAS_STR "); \n\t"// 设置tmp1高32位;
"or3 %1, %1, #low(" RW_LOCK_BIAS_STR "); \n\t"// 设置tmp1低32位
".fillinsn \n" //结束一段
"1: \n\t" //1段
"mvfc %2, psw; \n\t" //这里应该是保存psw值到tmp2
"clrpsw #0x40 -> nop; \n\t"清除psw的zf标志位;
DCACHE_CLEAR("%0", "r7", "%3")
"lock %0, @%3; \n\t"//这里应该是将rw->lock的值保存到tmp0
"sub %0, %1; \n\t"//执行rw->lock -= RW_LOCK_BIAS_STR;
"unlock %0, @%3; \n\t"//将tmp0值保存到rw->lock
"mvtc %2, psw; \n\t"//恢复psw寄存器;
"bnez %0, 2f; \n\t"//判断tmp0是否为0,若不等于0则向前跳转到第2段
LOCK_SECTION_START(".balign 4 \n\t")
".fillinsn \n"//
"2: \n\t"
"clrpsw #0x40 -> nop; \n\t" //清除psw的zf标志位
DCACHE_CLEAR("%0", "r7", "%3")
"lock %0, @%3; \n\t" //这里应该是将rw->lock的值保存到tmp0
"add %0, %1; \n\t"// 执行rw->lock += RW_LOCK_BIAS_STR;
"unlock %0, @%3; \n\t"//将tmp0值保存到rw->lock
"mvtc %2, psw; \n\t"//恢复psw寄存器;
".fillinsn \n"
"3: \n\t"
"ld %0, @%3; \n\t" //不进行加锁,这里应该是将rw->lock的值保存到tmp0;从内存中load数据
"beq %0, %1, 1b; \n\t"// 判断tmp0是否等于tmp1,向后跳转;
"bra 3b; \n\t"//向后跳转;
LOCK_SECTION_END
: "=&r" (tmp0), "=&r" (tmp1), "=&r" (tmp2)
: "r" (&rw->lock)
: "memory"
#ifdef CONFIG_CHIP_M32700_TS1
, "r7"
#endif /* CONFIG_CHIP_M32700_TS1 */
);
}
写饥饿问题?
读写锁利于读者,对于写者,若有大量读者进来,则写者可能饥饿。
读者始终可以获取锁,然而写者则需要自旋到所有读者退出,在写者自旋过程中,可能不断有新的读者进来,导致写者饿死。
若加锁时间不长或代码不会处于睡眠状态,则自旋锁比较适合。
若加锁时间长或代码睡眠,则适合使用信号量。
由此,可以知道读写锁内部实现机制,后续持续补充,暂完。