本文最后更新于:2020年7月1日 晚上
* 对内核中的信号量和自旋锁经过源码剖析后,再来看最后一个内核中的互斥操作——读写锁。。。→_→ *
初始化读写锁
读写锁的类型定义
typedef struct { //不调试时,读写锁实际上就是一个无符号整形。通过下面的代码还可以看出其实质就是一个计数器 volatile unsigned int lock; #if SPINLOCK_DEBUG unsigned magic; #endif } rwlock_t;
rwlock_init()源代码
#define RW_LOCK_BIAS 0x01000000 #define RW_LOCK_BIAS_STR "0x01000000" #define RWLOCK_MAGIC 0xdeaf1eed #if SPINLOCK_DEBUG #define RWLOCK_MAGIC_INIT , RWLOCK_MAGIC #else #define RWLOCK_MAGIC_INIT /* */ #endif #define RW_LOCK_UNLOCKED (rwlock_t) { RW_LOCK_BIAS RWLOCK_MAGIC_INIT } //初始化读写锁时,将读写锁的lock初始化为0x01000000 #define rwlock_init(x) do { *(x) = RW_LOCK_UNLOCKED; } while(0)
内核中的read_lock()
read_lock()源代码
static inline void read_lock(rwlock_t *rw) { #if SPINLOCK_DEBUG if (rw->magic != RWLOCK_MAGIC) BUG(); #endif //第一个参数为读写锁指针,第二个为获取读锁失败时的处理函数的函数指针 __build_read_lock(rw, "__read_lock_failed"); }
__build_read_lock()源代码
#define __build_read_lock(rw, helper) do { \ //是编译器gcc的内置函数,用于判断一个值是否为编译时常量,如果是常数,函数返回1,否则返回0 if (__builtin_constant_p(rw)) \ __build_read_lock_const(rw, helper); \ else \ __build_read_lock_ptr(rw, helper); \ } while (0)
build_read_lock_const()与build_read_lock_ptr()源代码
#define __build_read_lock_ptr(rw, helper) \ //锁总线,将rw减1,判断结果的符号位是否为1,若符号位为0则获取读锁成功并返回 asm volatile(LOCK "subl $1,(%0)\n\t" \ "js 2f\n" \ "1:\n" \ ".section .text.lock,\"ax\"\n" \ //若符号位为1则获取读锁失败,调用失败处理函数helper "2:\tcall " helper "\n\t" \ "jmp 1b\n" \ ".previous" \ ::"a" (rw) : "memory") #define __build_read_lock_const(rw, helper) \ asm volatile(LOCK "subl $1,%0\n\t" \ "js 2f\n" \ "1:\n" \ ".section .text.lock,\"ax\"\n" \ "2:\tpushl %%eax\n\t" \ "leal %0,%%eax\n\t" \ "call " helper "\n\t" \ "popl %%eax\n\t" \ "jmp 1b\n" \ ".previous" \ :"=m" (*(volatile int *)rw) : : "memory")
__read_lock_failed()源代码
#if defined(CONFIG_SMP) asm( " .align 4 .globl __read_lock_failed __read_lock_failed: lock ; incl (%eax) //锁总线,将rw加1 1: cmpl $1,(%eax) //判断结果是否小于1 js 1b //若符号位为1,则继续循环测试rw的值 lock ; decl (%eax) //若结果大于等于1,锁总线,将rw减1 js __read_lock_failed//判断结果的符号位是否为0,若为1,继续循环测试rw的值 ret//否则返回 " ); #endif
内核中的read_unlock()
read_unlock()源代码
//锁总线,将读写锁的计数加1 #define read_unlock(rw) asm volatile("lock ; incl %0" :"=m" ((rw)->lock) : : "memory")
内核中的write_lock()
write_lock()源代码
static inline void write_lock(rwlock_t *rw) { #if SPINLOCK_DEBUG if (rw->magic != RWLOCK_MAGIC) BUG(); #endif __build_write_lock(rw, "__write_lock_failed");//第一个参数为写锁,第二参数为获取写锁失败时的处理函数的函数指针 }
__build_write_lock()源代码
//格式同__build_read_lock() #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)
build_write_lock_const()和build_write_lock_ptr()源代码
#define __build_write_lock_ptr(rw, helper) \ //锁总线,将rw减RW_LOCK_BIAS_STR,即rw减0x01000000,判断结果是否为0,若为0则获取写锁成功并返回 asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)\n\t" \ "jnz 2f\n" \ "1:\n" \ ".section .text.lock,\"ax\"\n" \ //若结果不为0则获取写锁失败,调用失败处理函数helper "2:\tcall " helper "\n\t" \ "jmp 1b\n" \ ".previous" \ ::"a" (rw) : "memory") #define __build_write_lock_const(rw, helper) \ asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)\n\t" \ "jnz 2f\n" \ "1:\n" \ ".section .text.lock,\"ax\"\n" \ "2:\tpushl %%eax\n\t" \ "leal %0,%%eax\n\t" \ "call " helper "\n\t" \ "popl %%eax\n\t" \ "jmp 1b\n" \ ".previous" \ :"=m" (*(volatile int *)rw) : : "memory")
__write_lock_failed源代码
#if defined(CONFIG_SMP) asm( " .align 4 .globl __write_lock_failed __write_lock_failed: " LOCK "addl $" RW_LOCK_BIAS_STR ",(%eax) //锁总线,将rw加RW_LOCK_BIAS_STR,即rw加0x01000000 1: cmpl $" RW_LOCK_BIAS_STR ",(%eax) //判断结果是否小于RW_LOCK_BIAS_STR jne 1b //若结果不等于RW_LOCK_BIAS_STR ,则继续循环测试rw的值 " LOCK "subl $" RW_LOCK_BIAS_STR ",(%eax) //若结果等于RW_LOCK_BIAS_STR,锁总线,将rw减RW_LOCK_BIAS_STR jnz __write_lock_failed //判断结果是否等于0,若不等于0,则继续循环测试rw的值 ret //否则返回 #endif
内核中的write_unlock()
write_unlock()源代码
//锁总线,将读写锁的计数加RW_LOCK_BIAS_STR #define write_unlock(rw) asm volatile("lock ; addl $" RW_LOCK_BIAS_STR ",%0":"=m" ((rw)->lock) : : "memory")
内核中的读写锁具体应用的类型
不同类型的异同见自旋锁中的分析——传送门请戳:自旋锁
获取读写锁的操作
#define read_lock_irqsave(lock, flags) do { local_irq_save(flags); read_lock(lock); } while (0) #define read_lock_irq(lock) do { local_irq_disable(); read_lock(lock); } while (0) #define read_lock_bh(lock) do { local_bh_disable(); read_lock(lock); } while (0) #define write_lock_irqsave(lock, flags) do { local_irq_save(flags); write_lock(lock); } while (0) #define write_lock_irq(lock) do { local_irq_disable(); write_lock(lock); } while (0) #define write_lock_bh(lock) do { local_bh_disable(); write_lock(lock); } while (0)
释放读写锁的操作
#define read_unlock_irqrestore(lock, flags) do { read_unlock(lock); local_irq_restore(flags); } while (0) #define read_unlock_irq(lock) do { read_unlock(lock); local_irq_enable(); } while (0) #define read_unlock_bh(lock) do { read_unlock(lock); local_bh_enable(); } while (0) #define write_unlock_irqrestore(lock, flags) do { write_unlock(lock); local_irq_restore(flags); } while (0) #define write_unlock_irq(lock) do { write_unlock(lock); local_irq_enable(); } while (0) #define write_unlock_bh(lock) do { write_unlock(lock); local_bh_enable(); } while (0)
总结
读写锁本质上就是一个计数器,初始化值为0x01000000,表示最多可以有0x01000000个读者同时获取读锁
获取读锁时,rw计数减1,判断结果的符号位是否为1。若结果符号位为0时,获取读锁成功
获取读锁时,rw计数减1,判断结果的符号位是否为1。若结果符号位为1时,获取读锁失败,表示此时读写锁被写者占有,此时调用__read_lock_failed失败处理函数,循环测试rw+1的值,直到结果的值大于等于1
获取写锁时,rw计数减RW_LOCK_BIAS_STR,即rw-0x01000000,判断结果是否为0。若结果为0时,获取写锁成功
获取写锁时,rw计数减RW_LOCK_BIAS_STR,即rw-0x01000000,判断结果是否为0。若结果不为0时,获取写锁失败,表示此时有读者占有读写锁或有写着占有读写锁,此时调用__write_lock_failed失败处理函数,循环测试rw+0x01000000,直到结果的值等于0x01000000
通过对读写锁的源代码分析,可以看出读写锁其实是带计数的特殊自旋锁,能同时被多个读者占有或一个写者占有,但不能同时被读者和写者占有
操作系统为了避免一直有读者占有读写锁而导致写者饥饿的情况,让写者等待时排在读者前面,使写者的优先级更高
* 一个下午就这样过去了。。。→_→ *
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!