当前位置: 首页 >> 开源操作系统 >> Linux下写者优先的读写锁的设计
 

Linux下写者优先的读写锁的设计

作者:王瑞川 (jeppeterone@163.com)      来源:     发表时间:2006-03-02     浏览次数:      字号:    

一、本文的目的

在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")
     

这里的RW_LOCK_BIAS_STR就是 "0x01000000",取这个值的原因是这个值足够大,可以使满足读的请求足够多。

在".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
	

这里的LOCK是一个在SMP体系结构中锁住总线,不让其它的CPU访问内存。在这里先" LOCK "addl $" RW_LOCK_BIAS_STR ",(%eax)是为了防止在后面的自旋等待中,不会让后面的读请求受阻,要不然的话,就会出现死锁,这是致命的。而


	1:	rep; nop
	cmpl	$" RW_LOCK_BIAS_STR ",(%eax)
	jne	1b
	

就是不断的检查锁是否可以得到,得不到就会nop,这种方法可以在不改变lock的值的情况下实现等待,这就不用LOCK,这样的锁住总线了。最后如果得到锁就


" LOCK "subl	$" RW_LOCK_BIAS_STR ",(%eax)

这样就实现了write_lock的功能。

对读锁也是类似


     #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而言,只要减1并且只要这个值不为负的话,就可以得到锁了,但rw.lock的值在被初始化的时候就被赋值成了0x01000000,这个值足够大,而要减的很小,所以要获得读锁是很容易的,从理论上说比得到写锁要容易0x01000000倍,这就是我前面说现在在linux内部实现的读写锁是读者优先的。而这个数也让我们容易理解在要获得写锁时,要对这个lock值 减去0x01000000,就是如果有一个读或者写请求在临界区内的话,第二个写请求就无法得到写锁了。

而如果得不到读锁,所要跳的是在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
	

这里的道理与前面的__write_lock_failed中的道理是相似的。

三、写者优先的读写锁的实现

那既然要实现以写者为优先的读写锁,很自然,我们就想到了在读的请求发生时,不先去试图获得读锁,而是去检查有没有写的请求正在等待,如果有写的请求正在等待,则读的请求必须先处于等待状态。让写的请求完成之后,发现已经没有写的请求在等待了,才去试图获得读的锁。

这里我们先来设计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;

这里所增加的这个wlock_waiting就是作为检测是否有写的请求在等待的标志数。如果这个数不等于0则说明已经有写的请求在等待。它的负值的大小决定了写请求等待的个数。

这里我们先修改__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")
		     

这里的新增加的wlock_waiting是表示前面所定义的wlock_waiting的地址,这个是地址,而不是值本身,它的原因是指令寻址的方式决定的,为了保证指令的操作在直接对内存,而不是把内存中的数据load到寄存器中,再进行处理后放回到内存中,如果不是这样的话,就有可能使这个变量的在寄存器内的处理时被其它的cpu在它们的寄存器中被改变。而helper1则是知道已经有了写请求在等待得到锁,而跳转的处理地址。这里我取的名字是__read_lock_failed_wlock_wait


	.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
	

这里就是不断的检查wlock_waiting的数值是否为0, 如果不是0就要执行空转指令。

而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")
	

这里与在__buil

[1] [2]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •