您的位置:首页 > 运维架构 > Linux

学习笔记 --- LINUX的同步互斥机制 --- 自旋锁与信号量的区别

2014-03-09 11:51 387 查看
所谓同步互斥就是说一个设备只能被一个应用程序打开,不能被两个应用程序同时访问,比如在PC机上只能用一个串口调试助手打开某个COM口,如果再打开一个调试助手,提示串口打开失败,只能先关闭先前打开的那个,才能再打开这个COM口。那么第二次打开调试助手的时候他怎么知道这个串口已经被占用了呢?这种情况需要使用同步互斥机制去解决。

linux里面用的做多的同步互斥机制有自旋锁和信号量,他们实现的方式也不同,他们使用的场合有不同,看下网友的:

来源:http://blog.csdn.net/xu_guo/article/details/6072823

自旋锁最多只能被一个可执行线程持有(读写自旋锁除外)。自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直进行忙循环,一直等待下去(一直占用 CPU ),在那里看是否该自旋锁的保持者已经释放了锁, " 自旋 " 一词就是因此而得名。
由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(因为中断的上下文不允许休眠)( _trylock 的变种能够在中断上下文使用);而自旋锁 适合于保持时间非常短的情况,因为一个被争用的自旋锁使得请求它的线程在等待重新可用时自旋,特别浪费处理时间,这是自旋锁的要害之处,所以自旋锁不应该 被长时间持有。在实际应用中自旋锁代码只有几行,而持有自旋锁的时间也一般不会超过两次上下方切换,因线程一旦要进行切换,就至少花费切出切入两次,自旋 锁的占用时间如果远远长于两次上下文切换,我们就可以让线程睡眠,这就失去了设计自旋锁的意义。
如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。
自旋锁保持期间是抢占失效的(内核不允许被抢占) ,而信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或 SMP 的情况下才真正需要,在单 CPU 且不可抢占的内核下,自旋锁的所有操作都是空操作。
注意,如果在一个已经对某个自旋锁加锁的进程的临界区中又申请对这个自旋锁加锁,则会导致进程自旋在那里,引起死机。
因为自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只能有一个线程位于临界区,这就为多处理器提供了防止并发访问所需的保护机制,但是在单处理器上,编译的时候不会加入自旋锁。它仅仅被当作一个设置内核抢占机制是否被启用的开关( gx 自己的理解:在单内核可抢占式内核中,对自旋锁加锁导致禁止抢占,对自旋锁解锁导致恢复抢占模式。)。 注意, Linux 内核实现的自旋锁是不可递归的,这一点不同于自旋锁 在其他操作系统中的实现,如果你想得到一个你正持有的锁,你必须自旋,等待你自己释放这个锁,但是你处于自旋忙等待中,所以永远没有机会释放锁,于是你就 被自己锁死了,一定要注意!
自旋锁可以用在中断处理程序中,但是在使用时一定要在获取锁之前,首先禁止本地中断(当前处理器上的中 断),否则中断处理程序就可能打断正持有锁的内核代码,有可能会试图争用这个已经被持有的自旋锁。这样一来,中断处理程序就会自旋,等待该锁重新可用, 但是锁的持有者在这个中断处理程序执行完毕之前不可能运行,这就会造成双重请求死锁。这个概念和“[b]如果在一个已经对某个自旋锁加锁的进程的临界区中又申请对这个自旋锁加锁,则会导致进程自旋在那里,引起死机[/b]”是一样的。

-------- 自旋锁对信号量 ------------------------------------------------------
需求                                     建议的加锁方法
低开销加锁                            优先使用自旋锁
短期锁定                             优先使用自旋锁
长期加锁                             优先使用信号量
中断上下文中加锁                   使用自旋锁
持有锁是需要睡眠、调度           使用信号量
 
原子操作、信号量、读写信号量和自旋锁:
http://kom118.blog.163.com/blog/static/476673182010312113630768/
自旋锁和信号量:
http://canlynet.blog.163.com/blog/static/2550136520091025069172/
自旋锁:一种当获取不到锁时,采用循环 “ 测试并设置 ” 的方式等待获取锁的一种互斥锁。
获取方法:采用 test-and-set 原子操作测试并设置某个内存变量。 —— 实现互斥的方法。
不被打断:通过禁止抢占实现。
使用场合:可在进程上下文和中断上下文使用。
特点:
1 、这种锁在获取不到时系统开销大,所以获取锁和释放锁的区间代码执行时间应该尽可能短。
2 、在单 CPU 内核可抢占系统中,自旋锁持有期间内核抢占被禁止。但在单 CPU 内核不支持抢占的系统,自旋锁退化为空操作。 —— 也就是说,如果持有锁的区间代码执行时间过长,会出现其它操作的不响应(假死机)现象。
3 、因为抢占被禁止,自旋锁可保证临界区不受 “ 别的 CPU 和本 CPU 内 ” 的抢占进程进程打扰。(但是可响应中断,如果连中断都不响应,需要特定的加锁函数)
4 、可能受到中断和底半部( BH) 的影响,为此,与开、关中断配合,为此有:
spin_lock_irq(),spin_unlock_irq()
spin_lock_irqsave(),spin_unlock_irqstore()
spin_lock_bh(),spin_unlock_bh()
具体含义请参考教材或网络搜索。
自旋锁的扩展: 1 、读写自旋锁; 2 、顺序锁; 3 、读 - 拷贝 - 更新( RCU)
 ******************************************************************
信号量:一种当获取不到锁时,采用睡眠以等待唤醒的方式的一种同步机制。
不被打断:通过何种方式实现没找到参考,但可推断为同自旋锁。
获取方法:同自旋锁。
适用场合:由于会导致睡眠,只能在进程上下文中使用。但是 int down_trylock(struct semphore *sem) 可以。
特点:
获取不到锁时,进入睡眠状态,系统开销为上下文切换的时间 Tsw 。
自旋锁和信号量的对比:
1 、当锁不能获取时,信号量开销为 Tsw (上下文切换时间),自旋锁开销为等待获取时间 Tcs ,这两个时间对比权衡使用哪种机制。
2 、信号量可用于保护包含可能引起阻塞的代码 ( 即保护的代码中有可引起睡眠的函数如copy_to_user(),copy_from_user()) ,自旋锁不能。自旋锁如果也使用这样的代码,当睡眠时另一个程进也要获取这把锁时,会 进入循环,而睡眠时间一般相对较长,系统资源开销大的时间过长时,资源耗尽的情况会发生,直到这个睡眠的进程被唤醒,代码执行完毕,资源才得以释放。至于 自旋锁区间睡眠引起死锁的情况我实在想不出来。但教材中都这么说。
3 、中断上下文中只能使用自旋锁,不可使用信号量。因为中断上下文中是不能被调度的,但睡眠后会发生上下文切换,需要调度,在中断上下文中睡眠只能永久睡眠 —— 死机!
如果一个函数需要一个锁,并且接着调用另外一个函数也试图请求这个锁,那么会导致代码死锁。
获得多个锁可能是危险的,然而,如果你有 2 个锁,称为 lock1 和 lock2 ,代码需要同时获取,你有一个潜在的死锁。这个问题的解决方法常常是简单的:当多个锁必须获得时,它们应当一直以同样的顺序获得,只要遵照这个惯例,像上面描述的简单的死锁能够避免。
__________________________________________________________
个人理解的使用与程序设计原则:

1 中断中先不考虑使用互斥机制

2 进程中先使用信号量实现保证系统没bug

3 再使用自旋锁提高系统性能,此时注意:

   a 不能锁中有堵塞

   b 不能锁中有锁

4 如果在中断中一定要用互斥机制,那么先关了总中断,再使用自旋锁,再释放自旋锁,再开总中断

总之一定要以保证系统稳定性为前提,很多bug可以在程序架构的设计阶段避免。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: