读写自旋锁--来自深入理解Linux内核一书

读写自旋锁

读写自旋锁是自旋锁的一种提升,通过并发读提升性能。
读写锁自旋锁和自旋锁的数据结构一致:

 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 */
	);
}

写饥饿问题?
读写锁利于读者,对于写者,若有大量读者进来,则写者可能饥饿。
读者始终可以获取锁,然而写者则需要自旋到所有读者退出,在写者自旋过程中,可能不断有新的读者进来,导致写者饿死。
若加锁时间不长或代码不会处于睡眠状态,则自旋锁比较适合。
若加锁时间长或代码睡眠,则适合使用信号量。
由此,可以知道读写锁内部实现机制,后续持续补充,暂完。

你可能感兴趣的:(C语言,linux)