透过Linux内核看无锁编程
ock-free应用场景二——Seqlock
手表最主要最常用的功能是读时间,而不是校正时间,一旦后者成了最常用的功能,消费者肯定不会买账。计算机的时钟也是这个功能,修改时间是小概率事件,而读时间是经常发生的行为。以下代码摘自2。4。34内核:
清单5。2。4。34seqlock实现代码
443voiddo_gettimeofday(structtimeval*tv)
444{
……
448read_lock_irqsave(xtime_lock,flags);
……
455sec=xtime。tv_sec;
456usec+=xtime。tv_usec;
457read_unlock_irqrestore(xtime_lock,flags);
……
466}
468voiddo_settimeofday(structtimeval*tv)
469{
470write_lock_irq(xtime_lock);
……
490write_unlock_irq(xtime_lock);
491}
不难发现获取时间和修改时间采用的是spinlock读写锁,读锁和写锁具有相同的优先级,只要读持有锁,写锁就必须等待,反之亦然。
Linux2。6内核中引入一种新型锁——顺序锁(seqlock),它与spinlock读写锁非常相似,只是它为写者赋予了较高的优先级。也就是说,即使读者正在读的时候也允许写者继续运行。当存在多个读者和少数写者共享一把锁时,seqlock便有了用武之地,因为seqlock对写者更有利,只要没有其他写者,写锁总能获取成功。根据lock-free和时钟功能的思想,内核开发者在2。6内核中,将上述读写锁修改成了顺序锁seqlock,代码如下:
清单6。2。6。10seqlock实现代码
staticinlineunsignedread_seqbegin(constseqlock_t*sl)
{
unsignedret=sl->sequence;
smp_rmb();
returnret;
}
staticinlineintread_seqretry(constseqlock_t*sl,unsignediv)
{
smp_rmb();
return(iv1)|(sl->sequence^iv);
}
staticinlinevoidwrite_seqlock(seqlock_t*sl)
{
spin_lock(sl->lock);
++sl->sequence;
smp_wmb();
}
voiddo_gettimeofday(structtimeval*tv)
{
unsignedlongseq;
unsignedlongusec,sec;
unsignedlongmax_ntp_tick;
……
do{
unsignedlonglost;
seq=read_seqbegin(xtime_lock);
……
sec=xtime。tv_sec;
usec+=(xtime。tv_nsec/1000);
}while(read_seqretry(xtime_lock,seq));
……
tv->tv_sec=sec;
tv->tv_usec=usec;
}
intdo_settimeofday(structtimespec*tv)
{
……
write_seqlock_irq(xtime_lock);
……
write_sequnlock_irq(xtime_lock);
clock_was_set();
return0;
}
Seqlock实现原理是依赖一个序列计数器,当写者写入数据时,会得到一把锁,并且将序列值加1。当读者读取数据之前和之后,该序列号都会被读取,如果读取的序列号值都相同,则表明写没有发生。反之,表明发生过写事件,则放弃已进行的操作,重新循环一次,直至成功。不难看出,do_gettimeofday函数里面的while循环和接下来的两行赋值操作就是CAS操作。
采用顺序锁seqlock好处就是写者永远不会等待,缺点就是有些时候读者不得不反复多次读相同的数据直到它获得有效的副本。当要保护的临界区很小,很简单,频繁读取而写入很少发生(WRRM---WriteRarelyReadMostly)且必须快速时,就可以使用seqlock。但seqlock不能保护包含有指针的数据结构,因为当写者修改数据结构时,读者可能会访问一个无效的指针。
3。Lock-free应用场景三——RCU
在2。6内核中,开发者还引入了一种新的无锁机制-RCU(Read-Copy-Update),允许多个读者和写者并发执行。RCU技术的核心是写操作分为写和更新两步,允许读操作在任何时候无阻碍的运行,换句话说,就是通过延迟写来提高同步性能。RCU主要应用于WRRM场景,但它对可保护的数据结构做了一些限定:RCU只保护被动态分配并通过指针引用的数据结构,同时读写控制路径不能有睡眠。以下数组动态增长代码摘自2。4。34内核:
清单7。2。4。34RCU实现代码
其中ipc_lock是读者,grow_ary是写者,不论是读或者写,都需要加spinlock对被保护的数据结构进行访问。改变数组大小是小概率事件,而读取是大概率事件,同时被保护的数据结构是指针,满足RCU运用场景。以下代码摘自2。6。10内核:
清单8。2。6。10RCU实现代码
#definercu_read_lock()preempt_disable()
#definercu_read_unlock()preempt_enable()
#definercu_assign_pointer(p,v)({
smp_wmb();
(p)=(v);
})
structkern_ipc_perm*ipc_lock(structipc_ids*ids,intid)
{
……
rcu_read_lock();
entries=rcu_dereference(ids->entries);
if(lid>=entries->size){
rcu_read_unlock();
returnNULL;
}
out=entries->p
- Reed Solomon编解码器的可编程逻辑实现(06-21)
- 基于TMS320C62X DSP的混合编程研究(07-12)
- 基于JTAG的DSP外部FLASH在线编程与引导技术(01-22)
- 基于嵌入式系统的手机编程开发平台(07-30)
- DSP编程的几个关键问题(10-01)
- 基于VxWorks的MB系列智能可编程控制器设计(01-16)