锁定课程 【ChatGPT】

发布时间 2023-12-10 11:45:54作者: 摩斯电码

锁定课程

课程1:自旋锁

用于锁定的最基本原语是自旋锁:

static DEFINE_SPINLOCK(xxx_lock);

unsigned long flags;

spin_lock_irqsave(&xxx_lock, flags);
... 临界区域 ...
spin_unlock_irqrestore(&xxx_lock, flags);

上述代码始终是安全的。它会在本地禁用中断,但自旋锁本身将保证全局锁,因此它将保证在由该锁保护的区域内只有一个控制线程。即使在单处理器系统(UP)下,这也能很好地工作,因此代码在单处理器系统和多处理器系统(SMP)下都能正确工作,因此代码在单处理器系统和多处理器系统(SMP)下都能正确工作。

注意! 自旋锁对内存的影响进一步在《Documentation/memory-barriers.txt》中有详细描述:

  • 获取操作(ACQUIRE operations)
  • 释放操作(RELEASE operations)

上述内容通常相当简单(通常大多数情况下只需要一个自旋锁 - 使用多个自旋锁可能会使事情变得更加复杂甚至更慢,通常只有在已知需要分割的序列时才值得这样做:如果不确定,尽量避免使用)。

这确实是自旋锁中唯一真正困难的部分:一旦开始使用自旋锁,它们往往会扩展到你以前可能没有注意到的领域,因为你必须确保自旋锁正确地保护了它们被使用的所有共享数据结构。自旋锁最容易添加到完全独立于其他代码的地方(例如,没有其他人会触及的内部驱动程序数据结构)。

注意! 自旋锁只有在你还使用锁本身来跨CPU进行锁定时才是安全的,这意味着一切触及共享变量的东西都必须同意他们想要使用的自旋锁。

课程2:读-写自旋锁

如果你的数据访问具有非常自然的模式,通常你倾向于大部分时间从共享变量中读取,那么自旋锁的读-写锁(rw_lock)版本有时是有用的。它们允许多个读者同时处于同一临界区域,但如果有人想要更改变量,则必须获得独占的写锁。

注意! 读-写锁比简单自旋锁需要更多的原子内存操作。除非读取临界区域很长,否则最好只使用自旋锁。

这些例程看起来与上面的例程相同:

rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);

unsigned long flags;

read_lock_irqsave(&xxx_lock, flags);
.. 仅读取信息的临界区域 ..
read_unlock_irqrestore(&xxx_lock, flags);

write_lock_irqsave(&xxx_lock, flags);
.. 读取和独占写入访问信息 ..
write_unlock_irqrestore(&xxx_lock, flags);

上述类型的锁对于像链表这样的复杂数据结构可能是有用的,特别是在搜索条目而不更改列表本身时。读锁允许许多并发读者。任何更改列表的操作都必须获得写锁。

注意! RCU 更适合于列表遍历,但需要对设计细节进行仔细注意(参见《Using RCU to Protect Read-Mostly Linked Lists》)。

此外,你不能将读锁升级为写锁,因此如果你在任何时候需要进行任何更改(即使你不是每次都这样做),你必须在一开始就获得写锁。

注意! 我们正在努力在大多数情况下删除读-写自旋锁,因此请不要在没有共识的情况下添加新的读-写自旋锁(而是参见《RCU Concepts》获取完整信息)。

课程3:自旋锁再探

上面的单个自旋锁原语绝不是唯一的。它们是最安全的,也是在所有情况下都能工作的,但部分是因为它们是安全的,它们也相当慢。它们比需要的慢,因为它们必须禁用中断(在x86上只是一个指令,但这是一个昂贵的指令 - 在其他架构上可能更糟)。

如果你有一个情况,你必须跨多个CPU保护一个数据结构,并且你想使用自旋锁,你可以潜在地使用更便宜的自旋锁版本。如果你知道自旋锁从不在中断处理程序中使用,你可以使用非中断版本:

spin_lock(&lock);
...
spin_unlock(&lock);

(当然,读-写版本也是如此)。自旋锁将保证相同类型的独占访问,并且速度会更快。如果你知道所讨论的数据只会从“进程上下文”中操作,即没有涉及中断,这是有用的。

你不能在有中断与自旋锁交互的情况下使用这些版本的原因是你可能会遇到死锁:

spin_lock(&lock);
...
<- 中断到来:
        spin_lock(&lock);

其中一个中断尝试锁定已经锁定的变量。如果其他中断发生在另一个CPU上,这是可以的,但如果中断发生在已经持有锁的相同CPU上,这是不可以的,因为锁显然永远不会被释放(因为中断正在等待锁,而持有锁的CPU被中断打断,并且直到中断被处理后才会继续)。

(这也是自旋锁的irq版本只需要禁用_本地_中断的原因 - 在其他CPU上的中断中使用自旋锁是可以的,因为在另一个CPU上的中断不会打断持有锁的CPU,因此持有锁的CPU可以继续并最终释放锁)。

林纳斯

参考信息:

对于动态初始化,请使用适当的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():

static DEFINE_SPINLOCK(xxx_lock);