读写旋转锁是旋转锁的变种,与一般自旋锁不同的是,自旋锁一次只能一个线程进入临界区,而读写旋转锁,可以同时存在多个读者,最多一个写者。
    下面分析下linux源码中读写旋转锁的实现方式:    

点击(此处)折叠或打开

  1. typedef struct {
  2.     volatile unsigned int lock;
  3. #ifdef CONFIG_DEBUG_SPINLOCK
  4.     unsigned magic;
  5. #endif
  6. } rwlock_t;
此结构定义在include\asm-*\spinlock.h中, magic 用于调试,lock则是表示最多可以由几个reader进入临界区。

点击(此处)折叠或打开

  1. #define RW_LOCK_BIAS         0x01000000
  2. #define RW_LOCK_BIAS_STR    "0x01000000"
  3. #define RW_LOCK_UNLOCKED (rwlock_t) { RW_LOCK_BIAS RWLOCK_MAGIC_INIT }
  4. #define rwlock_init(x)    do { *(x) = RW_LOCK_UNLOCKED; } while(0)
  5. #define rwlock_is_locked(x) ((x)->lock != RW_LOCK_BIAS)
初始化lock为0x01000000,它的取值做够大,可以满足读的请求足够多。当lock != RW_LOCK_BIAS,此时为锁定的,否则可获取锁。

点击(此处)折叠或打开

  1. static inline void _raw_read_lock(rwlock_t *rw)
  2. {
  3. #ifdef CONFIG_DEBUG_SPINLOCK
  4.     BUG_ON(rw->magic != RWLOCK_MAGIC);
  5. #endif
  6.     __build_read_lock(rw, "__read_lock_failed");
  7. }
实现读者锁的内部函数,分别调用__build_read_lock,其实现定义在include\asm-*\rwlock.h,第一个参数是允许读的锁,第二个参数实际上是失败的处理函数。

点击(此处)折叠或打开

  1. #define __build_write_lock(rw, helper)    do {
  2.                         if (__builtin_constant_p(rw))
  3.                             __build_write_lock_const(rw, helper);
  4.                         else
  5.                             __build_write_lock_ptr(rw, helper);
  6.                     } while (0)
 以上为读锁的实现,__builtin_constant_p  为gcc的内建函数,用于判断一个值是否为编译时常数,如果参数为常数,返回1,否则返回0;它的取决因素是调用参数的操作指令寻址方式,先介绍下const类。

点击(此处)折叠或打开

  1. #define __build_write_lock_const(rw, helper)
  2.     asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)nt"
  3.          "jnz 2fn"
  4.          "1:n"
  5.          LOCK_SECTION_START("")
  6.          "2:tpushq %%raxnt"
  7.          "leaq %0,%%raxnt"
  8.          "call " helper "nt"
  9.          "popq %%raxnt"
  10.          "jmp 1bn"
  11.          LOCK_SECTION_END
  12.          :"=m" (*((volatile long *)rw))::"memory")
首先对rw执行减操作,rw - RW_LOCK_BIAS,如果减后的值不为0则执行跳转2,调用__read_lock_failed,否则获取锁。
__build_write_lock_const 与 __build_write_lock_ptr之间的差别就在于前者多了粗体部分代码。

点击(此处)折叠或打开

  1. #define LOCK_SECTION_NAME            
  2.     ".text.lock." __stringify(KBUILD_BASENAME)
  3. #define LOCK_SECTION_START(extra)        
  4.     ".subsection 1nt"            
  5.     extra                    
  6.     ".ifndef " LOCK_SECTION_NAME "nt"    
  7.     LOCK_SECTION_NAME ":nt"        
  8.     ".endifnt"

  9. #define LOCK_SECTION_END            
  10.     ".previousnt"
LOCK_SECTION_START,LOCK_SECTION_END中间的内容是把这一段的代码汇编到一个叫.text.lock的节中,并且这个节的属性是可重定位和可执行的,这样在代码的执行过程中,因为不同的节会被加载到不同的页去,所以如果前面不出现jmp,就在1: 处结束了。而call的是在前面提到的__read_lock_failed,其定义在arch\i386\kernel\semaphore.c中。
 

点击(此处)折叠或打开

  1. asm(
  2. ".section .sched.textn"
  3. ".align    4n"
  4. ".globl    __read_lock_failedn"
  5. "__read_lock_failed:nt"
  6.     LOCK "incl    (%eax)n"
  7. "1:    rep; nopnt"
  8.     "cmpl    $1,(%eax)nt"
  9.     "js    1bnt"
  10.     LOCK "decl    (%eax)nt"
  11.     "js    __read_lock_failednt"
  12.     "ret"
  13. );
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返回。
与读自旋锁类似,写锁部分的代码如下:
 

点击(此处)折叠或打开

  1. static inline void _raw_write_lock(rwlock_t *rw)
  2. {
  3. #ifdef CONFIG_DEBUG_SPINLOCK
  4.     BUG_ON(rw->magic != RWLOCK_MAGIC);
  5. #endif
  6.     __build_write_lock(rw, "__write_lock_failed");
  7. }

  8. #define __build_write_lock_const(rw, helper)
  9.     asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)nt"
  10.          "jnz 2fn"
  11.          "1:n"
  12.          LOCK_SECTION_START("")
  13.          "2:tpushq %%raxnt"
  14.          "leaq %0,%%raxnt"
  15.          "call " helper "nt"
  16.          "popq %%raxnt"
  17.          "jmp 1bn"
  18.          LOCK_SECTION_END
  19.          :"=m" (*((volatile long *)rw))::"memory")

  20. #define __build_write_lock(rw, helper)    do {
  21.                         if (__builtin_constant_p(rw))
  22.                             __build_write_lock_const(rw, helper);
  23.                         else
  24.                             __build_write_lock_ptr(rw, helper);
  25.                     } while (0)

  26. asm(
  27. ".section .sched.textn"
  28. ".align    4n"
  29. ".globl    __write_lock_failedn"
  30. "__write_lock_failed:nt"
  31.     LOCK "addl    $" RW_LOCK_BIAS_STR ",(%eax)n"
  32. "1:    rep; nopnt"
  33.     "cmpl    $" RW_LOCK_BIAS_STR ",(%eax)nt"
  34.     "jne    1bnt"
  35.     LOCK "subl    $" RW_LOCK_BIAS_STR ",(%eax)nt"
  36.     "jnz    __write_lock_failednt"
  37.     "ret"
  38. );
由以上可看出:
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
 
 
 
12-17 00:17