读写旋转锁是旋转锁的变种,与一般自旋锁不同的是,自旋锁一次只能一个线程进入临界区,而读写旋转锁,可以同时存在多个读者,最多一个写者。
下面分析下linux源码中读写旋转锁的实现方式:
-
typedef struct {
-
volatile unsigned int lock;
-
#ifdef CONFIG_DEBUG_SPINLOCK
-
unsigned magic;
-
#endif
-
} rwlock_t;
此结构定义在include\asm-*\spinlock.h中, magic 用于调试,lock则是表示最多可以由几个reader进入临界区。
-
#define RW_LOCK_BIAS 0x01000000
-
#define RW_LOCK_BIAS_STR "0x01000000"
-
#define RW_LOCK_UNLOCKED (rwlock_t) { RW_LOCK_BIAS RWLOCK_MAGIC_INIT }
-
#define rwlock_init(x) do { *(x) = RW_LOCK_UNLOCKED; } while(0)
-
#define rwlock_is_locked(x) ((x)->lock != RW_LOCK_BIAS)
初始化lock为0x01000000,它的取值做够大,可以满足读的请求足够多。当lock != RW_LOCK_BIAS,此时为锁定的,否则可获取锁。
-
static inline void _raw_read_lock(rwlock_t *rw)
-
{
-
#ifdef CONFIG_DEBUG_SPINLOCK
-
BUG_ON(rw->magic != RWLOCK_MAGIC);
-
#endif
-
__build_read_lock(rw, "__read_lock_failed");
-
}
实现读者锁的内部函数,分别调用__build_read_lock,其实现定义在include\asm-*\rwlock.h,第一个参数是允许读的锁,第二个参数实际上是失败的处理函数。
-
#define __build_write_lock(rw, helper) do {
-
if (__builtin_constant_p(rw))
-
__build_write_lock_const(rw, helper);
-
else
-
__build_write_lock_ptr(rw, helper);
-
} while (0)
以上为读锁的实现,__builtin_constant_p 为gcc的内建函数,用于判断一个值是否为编译时常数,如果参数为常数,返回1,否则返回0;它的取决因素是调用参数的操作指令寻址方式,先介绍下const类。
-
#define __build_write_lock_const(rw, helper)
-
asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)nt"
-
"jnz 2fn"
-
"1:n"
-
LOCK_SECTION_START("")
-
"2:tpushq %%raxnt"
-
"leaq %0,%%raxnt"
-
"call " helper "nt"
-
"popq %%raxnt"
-
"jmp 1bn"
-
LOCK_SECTION_END
-
:"=m" (*((volatile long *)rw))::"memory")
首先对rw执行减操作,rw - RW_LOCK_BIAS,如果减后的值不为0则执行跳转2,调用__read_lock_failed,否则获取锁。
__build_write_lock_const 与 __build_write_lock_ptr之间的差别就在于前者多了粗体部分代码。
-
#define LOCK_SECTION_NAME
-
".text.lock." __stringify(KBUILD_BASENAME)
-
#define LOCK_SECTION_START(extra)
-
".subsection 1nt"
-
extra
-
".ifndef " LOCK_SECTION_NAME "nt"
-
LOCK_SECTION_NAME ":nt"
-
".endifnt"
-
-
#define LOCK_SECTION_END
-
".previousnt"
LOCK_SECTION_START,LOCK_SECTION_END中间的内容是把这一段的代码汇编到一个叫.text.lock的节中,并且这个节的属性是可重定位和可执行的,这样在代码的执行过程中,因为不同的节会被加载到不同的页去,所以如果前面不出现jmp,就在1: 处结束了。而call的是在前面提到的__read_lock_failed,其定义在arch\i386\kernel\semaphore.c中。
-
asm(
-
".section .sched.textn"
-
".align 4n"
-
".globl __read_lock_failedn"
-
"__read_lock_failed:nt"
-
LOCK "incl (%eax)n"
-
"1: rep; nopnt"
-
"cmpl $1,(%eax)nt"
-
"js 1bnt"
-
LOCK "decl (%eax)nt"
-
"js __read_lock_failednt"
-
"ret"
-
);
LOCK 是一个在SMP体系中锁住总线,不让其他的CPU访问内存。
incl (%eax),在__build_write_lock_const中尝试加锁时,递减了lock的值,此处先归还回去;
循环测试直到lock的值为1。如果不为1,则继续尝试获取锁(LOCK "decl (%eax)\n\t"),加锁不成功,从头再来(js __read_lock_failed);如果为1,则执行到ret,相当于从read_lock返回。
与读自旋锁类似,写锁部分的代码如下:
-
static inline void _raw_write_lock(rwlock_t *rw)
-
{
-
#ifdef CONFIG_DEBUG_SPINLOCK
-
BUG_ON(rw->magic != RWLOCK_MAGIC);
-
#endif
-
__build_write_lock(rw, "__write_lock_failed");
-
}
-
-
#define __build_write_lock_const(rw, helper)
-
asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)nt"
-
"jnz 2fn"
-
"1:n"
-
LOCK_SECTION_START("")
-
"2:tpushq %%raxnt"
-
"leaq %0,%%raxnt"
-
"call " helper "nt"
-
"popq %%raxnt"
-
"jmp 1bn"
-
LOCK_SECTION_END
-
:"=m" (*((volatile long *)rw))::"memory")
-
-
#define __build_write_lock(rw, helper) do {
-
if (__builtin_constant_p(rw))
-
__build_write_lock_const(rw, helper);
-
else
-
__build_write_lock_ptr(rw, helper);
-
} while (0)
-
-
asm(
-
".section .sched.textn"
-
".align 4n"
-
".globl __write_lock_failedn"
-
"__write_lock_failed:nt"
-
LOCK "addl $" RW_LOCK_BIAS_STR ",(%eax)n"
-
"1: rep; nopnt"
-
"cmpl $" RW_LOCK_BIAS_STR ",(%eax)nt"
-
"jne 1bnt"
-
LOCK "subl $" RW_LOCK_BIAS_STR ",(%eax)nt"
-
"jnz __write_lock_failednt"
-
"ret"
-
);
由以上可看出:
1 读写自旋锁本质上是一个内存计数器,初始化一个很大的值0x01000000,表示可最多存在这么多个读者。
2 获取读锁时,计数器减1,判断符号位是否为1,也就是是否为负数,是则表示已经有写者,读锁获取失败;符号位为0则表示获取读锁成功。
3 获取写锁时,计数器减0x01000000,判断是否为0,是则表示没有其他的读者或者写者,获取锁成功;不为0则表示有其他读者或者写者,获取锁失败。
4 获取读锁失败时,先将计数器加1,判断值是否小于1(减1符号位为1),是则循环判断读,直到值大于1。获取读锁失败时,则表示已有写者,计数器小于等于0.
5 获取写锁失败时,先将计数器增加0x01000000,判断值是否为0x01000000,不为0x01000000则循环判断,直到值为0x01000000为止。为初值0x01000000则表示无锁状态,可以尝试获取锁。
参考:
linux读写锁的理解
linux下写者优先的读写锁的设计
linux内核复习之进程同步
汇编学习笔记一则
如何理解leaveq和retq
阅读(722) | 评论(0) | 转发(0) |