读写旋转锁是旋转锁的变种,与一般自旋锁不同的是,自旋锁一次只能一个线程进入临界区,而读写旋转锁,可以同时存在多个读者,最多一个写者。
下面分析下linux源码中读写旋转锁的实现方式:
此结构定义在include\asm-*\spinlock.h中, magic 用于调试,lock则是表示最多可以由几个reader进入临界区。
初始化lock为0x01000000,它的取值做够大,可以满足读的请求足够多。当lock != RW_LOCK_BIAS,此时为锁定的,否则可获取锁。
实现读者锁的内部函数,分别调用__build_read_lock,其实现定义在include\asm-*\rwlock.h,第一个参数是允许读的锁,第二个参数实际上是失败的处理函数。
以上为读锁的实现,__builtin_constant_p 为gcc的内建函数,用于判断一个值是否为编译时常数,如果参数为常数,返回1,否则返回0;它的取决因素是调用参数的操作指令寻址方式,先介绍下const类。
由以上可看出:
点击(此处)折叠或打开
- typedef struct {
- volatile unsigned int lock;
- #ifdef CONFIG_DEBUG_SPINLOCK
- unsigned magic;
- #endif
- } rwlock_t;
点击(此处)折叠或打开
- #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)
点击(此处)折叠或打开
- 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");
- }
点击(此处)折叠或打开
- #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)
点击(此处)折叠或打开
- #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之间的差别就在于前者多了粗体部分代码。
LOCK_SECTION_START,LOCK_SECTION_END中间的内容是把这一段的代码汇编到一个叫.text.lock的节中,并且这个节的属性是可重定位和可执行的,这样在代码的执行过程中,因为不同的节会被加载到不同的页去,所以如果前面不出现jmp,就在1: 处结束了。而call的是在前面提到的__read_lock_failed,其定义在arch\i386\kernel\semaphore.c中。
点击(此处)折叠或打开
- #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"
点击(此处)折叠或打开
- 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