Linux下写者优先的读写锁的设计Word文档下载推荐.docx
《Linux下写者优先的读写锁的设计Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Linux下写者优先的读写锁的设计Word文档下载推荐.docx(5页珍藏版)》请在冰豆网上搜索。
unsignedmagic;
#endif
}rwlock_t;
这里的magic是用于调试的,而lock就是允许可以加的读锁数。
这个代码在linux/include/asm-i386/spinlock.h中定义了read_lock和write_lockstaticinlinevoidread_lock(rwlock_t*rw)
{
if(rw->
magic!
=RWLOCK_MAGIC)
BUG();
__build_read_lock(rw,"
__read_lock_failed"
);
}
staticinlinevoidwrite_lock(rwlock_t*rw)
__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)asmvolatile(LOCK"
subl$"
RW_LOCK_BIAS_STR"
(%0)\n\t"
"
jnz2f\n"
1:
\n"
.section.text.lock,\"
ax\"
2:
\tpushl%%eax\n\t"
leal%0,%%eax\n\t"
call"
helper"
\n\t"
popl%%eax\n\t"
jmp1b\n"
.previous"
:
"
=m"
(*(volatileint*)rw):
memory"
)这里的RW_LOCK_BIAS_STR就是"
0x01000000"
,取这个值的原因是这个值足够大,可以使满足读的请求足够多。
在"
中的内容是把这一段的代码汇编到一个叫.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$"
(%eax)
rep;
nop
cmpl$"
jne1b
jnz__write_lock_failed
ret这里的LOCK是一个在SMP体系结构中锁住总线,不让其它的CPU访问内存。
在这里先"
(%eax)是为了防止在后面的自旋等待中,不会让后面的读请求受阻,要不然的话,就会出现死锁,这是致命的。
而1:
jne1b就是不断的检查锁是否可以得到,得不到就会nop,这种方法可以在不改变lock的值的情况下实现等待,这就不用LOCK,这样的锁住总线了。
最后如果得到锁就"
(%eax)这样就实现了write_lock的功能。
对读锁也是类似#define__build_read_lock_const(rw,helper)asmvolatile(LOCK"
subl$1,%0\n\t"
js2f\n"
)但这里要注意一点,就是对read_lock而言,只要减1并且只要这个值不为负的话,就可以得到锁了,但rw.lock的值在被初始化的时候就被赋值成了0x01000000,这个值足够大,而要减的很小,所以要获得读锁是很容易的,从理论上说比得到写锁要容易0x01000000倍,这就是我前面说现在在linux内部实现的读写锁是读者优先的。
而这个数也让我们容易理解在要获得写锁时,要对这个lock值减去0x01000000,就是如果有一个读或者写请求在临界区内的话,第二个写请求就无法得到写锁了。
而如果得不到读锁,所要跳的是在read_lock所指明的__read_lock_failed.align4
.globl__read_lock_failed
__read_lock_failed:
lock;
incl(%eax)
cmpl$1,(%eax)
js1b
decl(%eax)
js__read_lock_failed
ret这里的道理与前面的__write_lock_failed中的道理是相似的。
回页首三、写者优先的读写锁的实现那既然要实现以写者为优先的读写锁,很自然,我们就想到了在读的请求发生时,不先去试图获得读锁,而是去检查有没有写的请求正在等待,如果有写的请求正在等待,则读的请求必须先处于等待状态。
让写的请求完成之后,发现已经没有写的请求在等待了,才去试图获得读的锁。
这里我们先来设计rwlock_t这个数据结构,typedefstruct{
#ifWLOCK_PRIORITY
volatileunsignedintwlock_waiting;
这里所增加的这个wlock_waiting就是作为检测是否有写的请求在等待的标志数。
如果这个数不等于0则说明已经有写的请求在等待。
它的负值的大小决定了写请求等待的个数。
这里我们先修改__build_write_lock_const中#define__build_write_lock_const(rw,wlock_wait,helper,helper1)asmvolatile(
cmpl$0,(%1)\n\t"
jnz3f\n"
\t"
jnz4f\n"
3:
\tpushl%%ebx\n\t"
leal%1,%%ebx\n\t"
helper1"
popl%%ebx\n\t"
4:
pushl%%ebx\n\t"
jmp2b\n"
(*(volatileint*)rw),"
(*(volatileint*)wlock_waiting):
)这里的新增加的wlock_waiting是表示前面所定义的wlock_waiting的地址,这个是地址,而不是值本身,它的原因是指令寻址的方式决定的,为了保证指令的操作在直接对内存,而不是把内存中的数据load到寄存器中,再进行处理后放回到内存中,如果不是这样的话,就有可能使这个变量的在寄存器内的处理时被其它的cpu在它们的寄存器中被改变。
而helper1则是知道已经有了写请求在等待得到锁,而跳转的处理地址。
这里我取的名字是__read_lock_failed_wlock_wait.align4
.globl__read_lock_failed_wlock_wait
__read_lock_failed_wlock_wait:
1:
cmpl$0,(%ebx)
jnz1b
js__read_lock_failed_wlock_wait
ret这里就是不断的检查wlock_waiting的数值是否为0,如果不是0就要执行空转指令。
而helper就是取前面的__read_lock_failed的名字,但有一点的变化。
.align4
jnz1b
ret这里的还是要不断的检查是否有写请求在等待,因为如果不是这样的话,在前面的指令的跳转过程中就有可能有写的请求到来,而我们是要严格的执行写者优先的读写规则。
如果在试图得到读锁过程中失败,也要跳转到检查写请求的地址,也是这个原因。
对于写锁的获得也要修改。
#define__build_write_lock_const(rw,wlock_wait,helper)asmvolatile(LOCK"
subl$1,(%1)\n\t"
"
LOCK"
addl$1,(%1)\n\t"
:
)这里与在__build_read_lock_const中的原理类似,但有一点不同,就是一旦要试图取得写锁的时候,要先对wlock_waiting这个数行减1,如果得到就要加1,这是为了让同样的读的请求可以知道已经有写的请求在等待获得读写锁了。
而这里应用了addl与subl而不是incl与decl是因为后两个指令只能保证在24个bits的情况下有效,而前两个指令可以对32个bits情况下有效,而采用先减再加的策略,而不是先加后减,是因为如果一减,那原来是0就变成了0xffffffff,这样所有的位都是1对于硬件的可靠性加强。
而对于helper与前面的读者优先的读写锁是相同的。
上面只是给出了在修改读写锁使其是写优先的最主要的内容,其实如果真要实现必须修改十处以上,这里由于篇幅的关系,我把修改好的代码在提供在这里下载:
代码下载回页首四、小结这里对linux内核中的读写锁进行了写者优先的修改,这种修改从代码的内容上看比读者优先要增加了进行的成本,特别是如果有一个写请求在临界区外面等待,那可能会有很多的读请求在__read_lock_failed_wlock_wait中进行空转。
但如果考虑到写请求与读请求的发生概率可能是1:
100甚至更小,而且对系统刷新要求的高标准。
那么这一点的损失是值得的,尤其是对路由器,或是实时要求很高的信息发布平台上(如证券)就应该如果。
这在代码上是"
大巧若拙"
。
而如果把这个代码在一般的PC机平台上应用,可能这样只会是"
弄巧成拙"
了。
从这当中看出开放源代码的精神之所在,让用户实现自己最佳的配置与功能。