Linux下写者优先的读写锁的设计
2012-09-04 01:40
309 查看
Linux下写者优先的读写锁的设计
一、本文的目的
在linux下有两种实现数据互斥的基本机制,包括了semaphore(信号量),spinlock(自旋锁)。这里要说的读写锁(read write lock)是自旋锁的一个变种,与一般的自旋锁的区别是,自旋锁一次只能有一个进程进入临界区,而对读写锁而言,如果进程是读的话,那就可以有多个进程同时进入临界区,而如果是写的话,则只有一个可以。
就现在的linux内核源代码的发行版本而言,已经实现了读写锁的一个类型,就是读者优先的读写锁。(在这个设计中,作为读的请求可以更容易的进入临界区,而写的请求的请求往往容易受阻,这个我在后面会分析),而我要设计的读写锁,则是以写进程为优先的考虑的对象,如果有写的请求发出,则它会在被允许的最快时间内得到响应。这样的好处是在一个由很多客户端以读的权限访问的服务器(如一般的公众服务器),如果管理员对服务器的某些内容或配置进行修改的话,那它的及时性就有可能无法满足。这有时是不可以被接受的。
二、linux现有的读写锁状况
我先来分析现在linux内核源代码中的读写锁的实现方式,这样就可以很容易的理解后面的写者优先的读写锁的设计。
先介绍一个数据结构,这是在读写锁起到重要作用。
(注:下面所有的内核源代码均来自linux 2.4.17,如果与你的现有的内核源代码不同,请你作一些相应的改变就可以了,原理部分没有变化)
typedef struct {
volatile unsigned int lock;
#if SPINLOCK_DEBUG
unsigned magic;
#endif
} rwlock_t;
这里的magic是用于调试的,而lock就是允许可以加的读锁数。
这个代码在linux/include/asm-i386/spinlock.h中定义了read_lock和write_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");
}
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");
}
注意这里有两个参数,一个是rw就是允许的读锁数,而后面一个参数是如果加锁失败的话,处理失败的函数。
在这里真正调用的对write_lock是__build_write_lock_const或__build_write_lock_ptr,对read_lock中调用的是__build_read_lock_const或__build_read_lock_ptr,这里的取决因素是调用参数的操作指令寻址方式。我们这里只看const类
#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") |
在".section .text.lock,/"ax/"/n" /
".previous" /
中的内容是把这一段的代码汇编到一个叫.text.lock的节中,并且这个节的属性是可重定位和可执行的,这样在代码的执行过程中,因为不同的节会被加载到不同的页面中,所以如果在前面不出现jmp,就在1:处结束了。而call的是在前面的write_lock中所写入的__write_lock_failed,这个函数在arch/asm-i386/kernel/semaphore.c中定义
.align .globl __write_lock_failed __write_lock_failed: " LOCK "addl $" RW_LOCK_BIAS_STR ",(%eax) 1: rep; nop cmpl $" RW_LOCK_BIAS_STR ",(%eax) jne 1b " LOCK "subl $" RW_LOCK_BIAS_STR ",(%eax) jnz __write_lock_failed ret |
1: rep; nop cmpl $" RW_LOCK_BIAS_STR ",(%eax) jne 1b |
" LOCK "subl $" RW_LOCK_BIAS_STR ",(%eax) |
对读锁也是类似
#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所指明的__read_lock_failed
.align 4 .globl __read_lock_failed __read_lock_failed: lock ; incl (%eax) 1: rep; nop cmpl $1,(%eax) js 1b lock ; decl (%eax) js __read_lock_failed ret |
三、写者优先的读写锁的实现
那既然要实现以写者为优先的读写锁,很自然,我们就想到了在读的请求发生时,不先去试图获得读锁,而是去检查有没有写的请求正在等待,如果有写的请求正在等待,则读的请求必须先处于等待状态。让写的请求完成之后,发现已经没有写的请求在等待了,才去试图获得读的锁。
这里我们先来设计rwlock_t这个数据结构,
typedef struct { volatile unsigned int lock; #if WLOCK_PRIORITY volatile unsigned int wlock_waiting; #endif #if SPINLOCK_DEBUG unsigned magic; #endif } rwlock_t; |
这里我们先修改__build_write_lock_const中
#define __build_write_lock_const(rw, wlock_wait,helper,helper1) / asm volatile( "cmpl $0,(%1)/n/t" / "jnz 3f/n" / "1:/t" LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)/n/t" / "jnz 4f/n" / "2:/n" / ".section .text.lock,/"ax/"/n" / "3:/tpushl %%ebx/n/t" / "leal %1,%%ebx/n/t" / "call " helper1 "/n/t" / "popl %%ebx/n/t" / "jmp 1b/n" / "4:/tpushl %%eax/n/t" / pushl %%ebx/n/t" / "leal %0,%%eax/n/t" / "leal %1,%%ebx/n/t" / "call " helper "/n/t" / "popl %%ebx/n/t" / "popl %%eax/n/t" / "jmp 2b/n" / ".previous" / :"=m" (*(volatile int *)rw) ,"=m" (*(volatile int *)wlock_waiting) : : "memory") |
.align 4 .globl __read_lock_failed_wlock_wait __read_lock_failed_wlock_wait: 1: rep; nop cmpl $0,(%ebx) jnz 1b js __read_lock_failed_wlock_wait ret |
而helper就是取前面的__read_lock_failed的名字,但有一点的变化。
.align 4 .globl __read_lock_failed __read_lock_failed: lock ; incl (%eax) 1: rep; nop cmpl $0,(%ebx) jnz 1b rep; nop cmpl $1,(%eax) js 1b lock ; decl (%eax) js __read_lock_failed ret |
对于写锁的获得也要修改。
#define __build_write_lock_const(rw,wlock_wait, helper) / asm volatile(LOCK "subl $1,(%1)/n/t" / LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)/n/t" / "jnz 2f/n" / "1:/n"LOCK "addl $1,(%1)/n/t" / ".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) ,"=m" (*(volatile int *)wlock_waiting) : : "memory") |
上面只是给出了在修改读写锁使其是写优先的最主要的内容,其实如果真要实现必须修改十处以上,这里由于篇幅的关系,我把修改好的代码在提供在这里下载:
代码下载
四、小结
这里对linux内核中的读写锁进行了写者优先的修改,这种修改从代码的内容上看比读者优先要增加了进行的成本,特别是如果有一个写请求在临界区外面等待,那可能会有很多的读请求在__read_lock_failed_wlock_wait中进行空转。但如果考虑到写请求与读请求的发生概率可能是1:100甚至更小,而且对系统刷新要求的高标准。那么这一点的损失是值得的,尤其是对路由器,或是实时要求很高的信息发布平台上(如证券)就应该如果。这在代码上是"大巧若拙"。而如果把这个代码在一般的PC机平台上应用,可能这样只会是"弄巧成拙"了。从这当中看出开放源代码的精神之所在,让用户实现自己最佳的配置与功能。
相关文章推荐
- Linux下写者优先的读写锁的设计
- Linux下写者优先的读写锁的设计
- William Stallings 《操作系统内核和设计原理》书中Linux下C语言实现读者写者问题(写者优先)代码
- Linux多线程实践(6) --Posix读写锁解决读者写者问题
- linux多线程编程——读者优先、写者优先问题
- 优先读者的读者/写者问题的算法设计
- Linux多线程实践(6) --Posix读写锁解决读者写者问题
- Linux使用互斥锁和条件变量实现读写锁(写优先)
- Linux多线程实践(6) --Posix读写锁解决读者写者问题
- 基于信号量与P/V操作同步机制的读者/写者问题的设计与实现 (写者优先)
- Linux多线程实践(6) --Posix读写锁解决读者写者问题
- 基于信号量与P/V操作同步机制的读者/写者问题的设计与实现 (写者优先)
- 讨论个问题吧,只用MUTEX,你能实现一个写者优先的读写锁吗?
- linux 读者写者问题 读者优先c++
- Linux使用互斥锁和条件变量实现读写锁(读优先)
- 关于linux锁的c++封装 条件锁,读写锁(不同方式实现读写优先)
- linux/unix核心设计思想
- Linux程式设计入门 - socket/inetd programming
- 网盟投放平台体验优化项目总结――效率优先 适度设计
- 面向对象的设计法则(1)——优先使用(对象)组合,而非(类)继承