一、自旋锁
最基本的锁原语是自旋锁。
static DEFINE_SPINLOCK(xxx_lock);
unsigned long flags;
spin_lock_irqsave(&xxx_lock, flags);
... critical section here ..
spin_unlock_irqrestore(&xxx_lock, flags);
以上都是安全的。它将在本地禁用中断,但是自旋锁本身将保证全局锁,因此它将保证在该锁保护的区域内只有一个控制线程。这甚至在 UP
下也能很好地工作,所以代码不需要担心 UP
和 SMP
问题:自旋锁在这两种情况下都能正常工作。
注意!spin_locks
对记忆的影响将在以下部分中进一步描述:
Documentation/memory-barriers.txt
(5) LOCK operations.
(6) UNLOCK operations.
以上通常非常简单(对于大多数事情,你通常需要并且只想要一个自旋锁 - 使用多个自旋锁可以使事情变得更加复杂,甚至更慢,通常只对你知道需要拆分的序列是值得的:如果你不确定,不惜一切代价避免它)。
这真的是关于自旋锁的唯一真正困难的部分:一旦你开始使用自旋锁,它们往往会扩展到你以前可能没有注意到的区域,因为你必须确保自旋锁正确地保护共享数据结构。自旋锁最容易添加到完全独立于其他代码的位置(例如,其他人从未接触过的内部驱动程序数据结构)。
注意!只有当您使用锁本身在 CPU
上进行锁定时,旋转锁定才是安全的,这意味着触及共享变量的所有内容都必须就他们想要使用的旋转锁定达成一致。
二、读写锁
如果您的数据访问具有非常自然的模式,并且您通常倾向于主要从共享变量中读取,则自旋锁的读取器 - 编写器锁(rw_lock
)版本有时很有用。它们允许多个读取器同时位于同一关键区域,但如果有人想要更改变量,则必须获得独占写锁定。
注意!读写锁比简单的自旋锁需要更多的原子内存操作。除非读临界区很长,否则最好使用自旋锁。
例程看起来与上面相同:
rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);
unsigned long flags;
read_lock_irqsave(&xxx_lock, flags);
.. critical section that only reads the info ...
read_unlock_irqrestore(&xxx_lock, flags);
write_lock_irqsave(&xxx_lock, flags);
.. read and write exclusive access to the info ...
write_unlock_irqrestore(&xxx_lock, flags);
上述类型的锁对于链接列表等复杂数据结构可能很有用,尤其是在不更改列表本身的情况下搜索条目。读锁定允许许多并发读取。任何更改列表的内容都必须获取写锁定。
注意!RCU
更适合列表遍历,但需要仔细注意设计细节(请参见 Documentation/RCU/listRCU.txt
)。
此外,您无法将读锁定"升级"为写锁定,因此,如果您在任何时候需要进行任何更改(即使您不是每次都这样做),则必须在一开始就获得写锁定。
注意!我们正在努力消除大多数情况下的读写自旋锁,所以请不要在没有达成共识的情况下添加新的自旋锁。
三、自旋锁重新审视
上面单自旋锁原语绝不是唯一的一种。它们是最安全的,也是在所有情况下都能工作的,但部分原因是因为它们是安全的,它们也相当慢。它们的速度比需要的要慢,因为它们必须禁用中断(这在 x86
上只是一条指令,但代价很高——在其他架构上可能更糟)。
如果您必须跨多个CPU保护数据结构,并且想要使用自旋锁,则可以使用更便宜的自旋锁版本。IFF 你知道自旋锁从来不会在中断处理程序中使用,你可以使用非 irq 版本:
spin_lock(&lock);
...
spin_unlock(&lock);
(当然还有等效的读写版本)。自旋锁将保证相同类型的独占访问,而且速度会快得多。如果您知道所讨论的数据仅从"进程上下文"中操作,即不涉及中断,这将非常有用。
如果您有使用旋转锁的中断,则不得使用这些版本的原因是您可能会遇到死锁:
spin_lock(&lock);
...
<- interrupt comes in:
spin_lock(&lock);
其中,中断尝试锁定已锁定的变量。如果另一个中断发生在另一个 CPU
上,这是可以的,但如果中断发生在已经持有锁的同一 CPU
上,则不可以,因为锁显然永远不会被释放(因为中断正在等待锁,并且锁持有者被中断中断,并且在处理中断之前不会继续)。
这也是为什么 irq
版本的自旋锁只需要禁用本地中断的原因 - 可以在其他 CPU
的中断中使用自旋锁,因为另一个 CPU
上的中断不会中断持有锁的 CPU
,因此锁持有者可以继续并最终释放锁。
请注意,您可以巧妙地使用读写锁定和中断。例如,如果您知道中断只获得读锁定,那么您可以在任何地方使用非 irq
版本的读锁定 - 因为它们不会相互阻塞(因此没有死锁 wrt
中断)。但是当你做写锁定时,你必须使用 irq-safe
版本。
关于 rw-locks
的例子,可以看看 kernel/sched/core.c
中的 "waitqueue_lock"
锁处理 - 在中断中,没有什么会改变等待队列,他们只读取队列以便知道要唤醒谁。因此,读锁是安全的(这很好:它们确实很常见),而写锁需要保护自己免受中断。
四、参考信息
于动态初始化,根据需要使用 spin lock init( )
或 rwlock init( )
:
spinlock_t xxx_lock;
rwlock_t xxx_rw_lock;
static int __init xxx_init(void)
{
spin_lock_init(&xxx_lock);
rwlock_init(&xxx_rw_lock);
...
}
module_init(xxx_init);
对于静态初始化,请根据需要使用 DEFINE_SPINLOCK( ) / DEFINE_RWLOCK( )
或 __SPIN_LOCK_UNLOCKED( ) / __RW_LOCK_UNLOCKED( )
。