Linux自旋锁
Linux内核中最常见的锁是自旋锁(spin lock)。自旋锁最多只能被一个可执行的线程持有。如果一个执行线程试图获得一个被已经持有(即所谓的争用)的自旋锁,那么该线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求锁的执行线程便能立刻得到它,继续执行。在任意时间,自旋锁可以防止多余一个的执行线程同时进入临界区。同一个锁可以用在多个位置,例如,对于给定数据的所有访问都可以得到保护和同步。
一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),这种行为是自旋锁的要点。所以自旋锁不应该被长时间持有。事实上,这点正是使用自旋锁的初衷:在短期间内进行轻量级加锁。还可以采取另外的方式来处理对锁的争用:让请求线程睡眠,直到锁重新可用时再唤醒它。这样处理器就不必循环等待,可以去执行其他代码。这也会带来一定的开销——这里有两次明显的上下文切换,被阻塞的线程要唤出和换入,与实现自旋锁的少数几行代码相比,上下文切换当然有比较多的代码。
因此,持有自旋锁的时间最好小于完成两次上下文切换的耗时。当然我们大多数人都不会无聊到去测量上下文切换的耗时,所以我们让持有自旋锁的时间应尽可能的短就可以了。
注意:信号量便提供了上述第二种锁机制,它使得在发生争用时,等待的线程能投入睡眠,而不是旋转。
注意:因为自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只能有一个线程位于临界区内,这就为多处理器机器提供了防止并发访问所需的保护机制。注意在单处理器上,编译的时候并不会加入自旋锁。它仅仅被当作一个设置内核抢占机制是否被启用的开关。如果禁止内核抢占,那么在编译时自旋锁被完全剔除出内核。
注意:Linux内核实现的自旋锁是不可递归的,这点不同于其他操作系统的实现。所以如果你试图得到一个你正持有的锁,你必须自旋,等待你自己释放这个锁。
注意:自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为他们会导致睡眠)。在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。这样一来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行。这正是我们在前面的内容中提到的双重请求死锁。注意需要关闭的只是当前处理器上的中断。如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。
摘自:《Linux内核设计与实现》
========END========