新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > 透过Linux内核看无锁编程

透过Linux内核看无锁编程

作者:时间:2012-05-21来源:网络收藏

1105/*Double-checkwithlockheld。*/

1106if(p->real_parent!=p->parent){

1107__ptrace_unlink(p);

1108//TODO:isthissafe?

1109p->exit_state=EXIT_ZOMBIE;

……

1120}

1121write_unlock_irq(tasklist_lock);

1122}

……

1127}

如果将write_lock_irq放置于1103行之前,锁的范围过大,锁的负载也会加重,影响效率;如果将加锁的代码放到判断里面,且没有1106行的代码,程序会正确吗?在单核情况下是正确的,但在双核情况下问题就出现了。一个非主进程在一个CPU上运行,正准备调用exit退出,此时主进程在另外一个CPU上运行,在子进程调用release_task函数之前调用上述代码。子进程在exit_notify函数中,先持有读写锁tasklist_lock,调用forget_original_parent。主进程运行到1104处,由于此时子进程先持有该锁,所以父进程只好等待。在forget_original_parent函数中,如果该子进程还有子进程,则会调用reparent_thread(),将执行p->parent=p->real_parent;语句,导致两者相等,等非主进程释放读写锁tasklist_lock时,另外一个CPU上的主进程被唤醒,一旦开始执行,继续运行将会导致bug。

严格的说,Double-checkedlocking不属于无锁的范畴,但由原来的每次加锁访问到大多数情况下无须加锁,就是一个巨大的进步。同时从这里也可以看出一点端倪,开发者为了降低锁冲突率,减少等待时间,提高运行效率,一直在持续不断的进行改进。

原子操作可以保证指令以原子的方式执行——执行过程不被打断。提供了两组原子操作接口:一组针对于整数进行操作,另外一组针对于单独的位进行操作。中的原子操作通常是内联函数,一般是通过内嵌汇编指令来完成。对于一些简单的需求,例如全局统计、引用计数等等,可以归结为是对整数的原子计算。

1。Lock-free应用场景一——SpinLock

SpinLock是一种轻量级的同步方法,一种非阻塞锁。当lock操作被阻塞时,并不是把自己挂到一个等待队列,而是死循环CPU空转等待其他线程释放锁。Spinlock锁实现代码如下:

清单4。spinlock实现代码

staticinlinevoid__preempt_spin_lock(spinlock_t*lock)

{

……

do{

preempt_enable();

while(spin_is_locked(lock))

cpu_relax();

preempt_disable();

}while(!_raw_spin_trylock(lock));

}

staticinlineint_raw_spin_trylock(spinlock_t*lock)

{

charoldval;

__asm____volatile__(

xchgb%b0,%1

:=q(oldval),=m(lock->lock)

:0(0):memory);

returnoldval>0;

}

汇编语言指令xchgb原子性的交换8位oldval(存0)和lock->lock的值,如果oldval为1(lock初始值为1),则获取锁成功,反之,则继续循环,接着relax休息一会儿,然后继续周而复始,直到成功。

对于应用程序来说,希望任何时候都能获取到锁,也就是期望lock->lock为1,那么用CAS原语来描述_raw_spin_trylock(lock)就是CAS(lock->lock,1,0);

如果同步操作总是能在数条指令内完成,那么使用SpinLock会比传统的mutexlock快一个数量级。SpinLock多用于多核系统中,适合于锁持有时间小于将一个线程阻塞和唤醒所需时间的场合。

pthread库已经提供了对spinlock的支持,所以用户态程序也能很方便的使用spinlock了,需要包含pthread。h。在某些场景下,pthread_spin_lock效率是pthread_mutex_lock效率的一倍多。美中不足的是,内核实现了读写spinlock锁,但pthread未能实现。

2。Lock-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读写锁,读锁和写锁具有相同的优先级,只要读持有锁,写锁就必须等待,反之亦然。

2。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();

linux操作系统文章专题:linux操作系统详解(linux不再难懂)

linux相关文章:linux教程




关键词: 编程 内核 Linux 透过

评论


相关推荐

技术专区

关闭