锁定课程¶
课程 1:自旋锁¶
最基本的锁定原语是自旋锁
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 问题:自旋锁在两者下都能正确工作。
注意! 自旋锁对内存的影响在以下文档中进一步描述:
Documentation/memory-barriers.txt
获取操作。
释放操作。
以上通常非常简单(通常对于大多数事情,您只需要一个自旋锁 - 使用多个自旋锁会使事情变得更加复杂甚至更慢,并且通常只有在您**知道**需要拆分序列时才值得:如果不确定,请不惜一切代价避免)。
这实际上是关于自旋锁的唯一真正困难的部分:一旦您开始使用自旋锁,它们往往会扩展到您可能之前没有注意到的区域,因为您必须确保自旋锁正确地保护共享数据结构**在它们被使用的每个地方**。 自旋锁最容易添加到与其他代码完全独立的地方(例如,从未有人接触过的内部驱动程序数据结构)。
注意! 仅当您**还**使用锁本身跨 CPU 进行锁定时,自旋锁才是安全的,这意味着访问共享变量的**所有**内容都必须就它们想要使用的自旋锁达成一致。
课程 2:读者-写者自旋锁。¶
如果您的数据访问具有非常自然的模式,您通常倾向于主要从共享变量中读取数据,则自旋锁的读者-写者锁(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 更适合列表遍历,但需要仔细注意设计细节(请参阅 使用 RCU 保护主要用于读取的链表)。
另外,您不能将读锁“升级”为写锁,因此,如果在_任何_时候需要进行任何更改(即使您不是每次都这样做),则必须在一开始就获得写锁。
注意! 我们正在努力在大多数情况下删除读者-写者自旋锁,因此请不要在没有达成共识的情况下添加新的自旋锁。 (相反,请参阅 RCU 概念 以获取完整信息。)
课程 3:重新审视自旋锁。¶
上面的单个自旋锁原语绝不是唯一的。 它们是最安全的,并且是在所有情况下都适用的,但是在某种程度上**因为**它们是安全的,所以它们也相当慢。 它们比需要的慢,因为它们确实必须禁用中断(这只是 x86 上的单个指令,但是这是一个昂贵的指令 - 并且在其他体系结构上可能会更糟)。
如果您需要在多个 CPU 之间保护数据结构,并且想要使用自旋锁,则可能会使用更便宜的自旋锁版本。 仅当您知道自旋锁永远不会在中断处理程序中使用时,才能使用非 irq 版本
spin_lock(&lock);
...
spin_unlock(&lock);
(当然,还有等效的读写版本)。 自旋锁将保证相同的独占访问,并且速度会快得多。 如果您知道所讨论的数据仅从“进程上下文”进行操作,即不涉及中断,则这很有用。
如果您有中断来使用自旋锁,则不得使用这些版本的原因是您可能会遇到死锁
spin_lock(&lock);
...
<- interrupt comes in:
spin_lock(&lock);
其中一个中断尝试锁定一个已经锁定的变量。 如果另一个中断发生在另一个 CPU 上,这是可以的,但是如果中断发生在已经持有锁的同一 CPU 上,则这是_不可以_的,因为锁显然永远不会被释放(因为中断正在等待锁,并且持锁者被中断中断,并且在中断被处理之前不会继续)。
(这也是自旋锁的 irq 版本只需要禁用_本地_中断的原因 - 在其他 CPU 上的中断中使用自旋锁是可以的,因为另一个 CPU 上的中断不会中断持有锁的 CPU,因此持锁者可以继续并最终释放锁)。
Linus
参考信息:¶
对于动态初始化,请根据需要使用 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()。