文章总结: 本文解析Linux内核MCS自旋锁原理,解决多核高并发下的缓存行颠簸。MCS锁通过构建FIFO队列,使CPU在本地变量自旋,减少缓存一致性流量。文章阐述了其数据结构与加解锁流程,指出该机制将集中式竞争转化为分布式等待,兼顾公平性与性能,是理解内核排队自旋锁的基础。 综合评分: 88 文章分类: 二进制安全
Linux 内核 MCS Spin Lock 原理
原创
独钓孤舟雪
MyStackTrace
2025年12月13日 23:36 上海
在上一篇文章中,我们介绍了 Linux 内核 Ticket Spin Lock 的原理,这是一种非常精巧的设计,它通过引入类似银行柜台排队叫号系统的机制,优雅地解决了传统自旋锁的公平性问题。但是在 Ticket Spin Lock 的设计中,所有等待的 CPU 都会频繁地读取同一个锁变量,当持有自旋锁的那个 CPU 解锁更新锁变量时,会导致其他所有 CPU 上缓存了该变量的缓存行失效,从而引发“缓存行颠簸”现象,这对 CPU 性能是有影响的。为了解决这个问题,聪明的 Linux 内核开发者们设计出了排队自旋锁(Queued Spin Lock),排队自旋锁是基于著名的 MCS Spin Lock 实现的,理解 MCS Spin Lock 的原理对于我们理解 Queued Spin Lock 会很有帮助,这篇文章我们先来介绍 MCS Spin Lock 的原理。
MCS 是 Mellor-Crummey and Scott 的简写,这是提出这个机制的两位教授的名字:
- John M. Mellor-Crummey(莱斯大学教授)
- Michael L. Scott(罗切斯特大学教授)
MCS 算法是这两位教授在 1991 年发表的论文《Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors》中提出的算法。
MCS Spin Lock 和 Ticket Spin Lock 一样,也是一种公平的自旋锁,也实现了先来先服务的公平性,并且在此基础上 MCS Spin Lock 还解决了传统自旋锁在多核高争用下的性能问题(缓存行颠簸)。然而,Linux 内核并未直接使用 MCS Spin Lock,而是使用的它的优化变种 Queued Spin Lock,这一方面是因为 MCS Spin Lock 的实现除了需要一个锁结构,还需要一个节点结构体 mcs_spinlock,占用内存比传统自旋锁大,struct mcs_spinlock 一共占用 16 个字节,而传统自旋锁结构 spinlock_t 只有 4 个字节,另一方面是兼容性要求,spinlock_t 必须保持 4 字节,并且不能改变现有的 spin_lock,spin_unlock 等系列函数的函数原型,比如不能把这些函数的 spinlock_t 参数都改成 mcs_spinlock,这样使用传统自旋锁的旧代码就也全都得改。正是基于这些原因,Linux 内核才没有直接使用 MCS Spin Lock,但是研究 MCS Spin Lock 的原理还是很有必要的。
MCS Spin Lock 的核心思想是:将持有和等待自旋锁的 CPU 组织成一个队列,当前持有自旋锁的 CPU 在队头,等待自旋锁的 CPU 按照先后顺序排在这个队列中,所有等待自旋锁的 CPU 只在本地的一个变量上自旋,当锁释放时,当前持有自旋锁的 CPU 会通知它在队列中的下一个 CPU,并且只需要修改该 CPU 的本地变量,该 CPU 就能够获得自旋锁,而其他等待自旋锁的 CPU 不受影响,继续在自己的本地变量上面自旋,从而极大地减少了缓存一致性流量。
MCS Spin Lock 使用两个基本的数据结构:一个锁结构和一个节点结构。锁结构只有一个指向队列尾部的指针。每个 CPU 在尝试获取锁时,会分配一个节点结构(通常可以在栈上或 Per-CPU 变量中),这个节点结构其实就是上面的 struct mcs_spinlock,包含了三个字段:一个指向下一个节点的指针(next),一个自旋标志(locked),还有一个用来支持自旋锁嵌套的计数(count)。
下面我们就来看看 MCS Spin Lock 在 Linux 内核中的具体实现,首先是加锁函数 mcs_spin_lock,该函数有两个参数:lock 其实就是自旋锁结构本身,本质上是一个指向队列尾部节点的指针,node 表示当前来获取自旋锁的 CPU 的节点。函数 mcs_spin_lock 一上来就先对表示当前 CPU 的节点进行初始化,locked = 0 表示未获取自旋锁,next = NULL,表示没有下一个节点,因为它即将成为队列中的最后一个节点。初始化完当前节点之后,mcs_spin_lock 使用原子操作 xchg 把 lock,也就是队尾指针设置为当前节点,并且返回原来的队尾指针 prev。如果 prev 为 NULL,则表示队列原本为空,该自旋锁目前处于空闲状态,则当前 CPU 就可以直接获取到锁,函数 mcs_spin_lock 直接返回即可。
如果 prev 非 NULL,则表示队列非空,里面至少有一个节点,也就是该自旋锁目前被占用,这种情况,当前 CPU 就只能排队并且自旋等待:
- 排队 – 其实就是把表示当前 CPU 的节点挂在队列中,也就是将其设置为 prev->next。
WRITE_ONCE(prev->next, node);
- 自旋等待 – 其实就是在当前节点的 locked 变量上自旋,等待当前节点的 locked 变量被它的前驱节点(prev)对应的 CPU 给设置为 1。
arch_mcs_spin_lock_contended(&node->locked);
下面是 MCS Spin Lock 的解锁函数 mcs_spin_unlock 的实现。当持有自旋锁的 CPU 要释放锁的时候,它会先检查自己的有没有后继节点,也就是 next 指针是否为空,如果 next 指针为 NULL,则说明目前没有 CPU 在等待这个自旋锁,这时释放锁的操作就是把 lock 指针,也就是队列的队尾指针设置为 NULL,但是这个操作需要使用原子操作 cmpxchg,该原子操作会判断 lock 指针是否指向当前节点 node,这个条件如果成立就把 lock 设置为 NULL,并且返回 lock 原来的值,也就是 node,所以这里会直接退出 mcs_spin_unlock 函数。
通常情况下 lock == node 是成立的,因为刚刚已经确认过当前节点 node 没有后继节点,而它又表示当前持有自旋锁的 CPU,也就是说当前节点 node 既是队头也有队尾,既然是队尾,那 lock 指针当然应该指向当前节点 node,但是这个条件(lock == node)并非一定会成立,因为有可能在判断完 if (likely(!next)) 成立之后,并且在执行 cmpxchg_release 之前这段时间内有其他 CPU X 尝试来获取这个自旋锁,如果这个 CPU X 刚好执行了函数 mcs_spin_lock 中的 prev = xchg(lock, node),但是还未执行 WRITE_ONCE(prev->next, node),也就是说这个试图获取自旋锁的 CPU X 把队尾指针 lock 给更新成它自己的节点,但是还没有把自己的节点挂在 prev 后面,这是一个中间状态,在这种情况下函数 mcs_spin_unlock 中的那个 cmpxchg_release 判断就会失败,因为这个时候虽然 CPU X 的节点还没有挂在队列上,但是它已经更新了队尾指针 lock,所以当前打算释放自旋锁的 CPU 就需要等待一小会儿。
while (!(next = READ_ONCE(node->next))) cpu_relax();
等 CPU X 把它自己的节点挂在队列中(执行 WRITE_ONCE(prev->next, node)),也就是挂在当前打算释放自旋锁的节点的后面,然后当前 CPU 就会跳出上面那个 while 循环。
释放自旋锁做的最后一件事就是通知当前 CPU 节点的后继节点 next。
arch_mcs_spin_unlock_contended(&next->locked);
其实就是把当前 CPU 节点的后继节点的 locked 变量置 1,这样后继节点对应的 CPU 就会立刻从自旋等待(arch_mcs_spin_lock_contended)中跳出,就这样自旋锁的持有就转移到了下一个等待的 CPU 上了。
从上面的实现中我们就能看出 MCS 的两个关键优化特性:
- 公平性:使用严格的 FIFO 队列,采用先来先服务的调度策略,避免饥饿。
- 缓存友好性:每个 CPU 只自旋在自己的缓存行上,避免伪共享(false sharing),释放锁时只修改后继者的缓存行,最小化缓存一致性流量。
最后总结一下,MCS Spin Lock 的核心思想是将集中式竞争转化为分布式等待。在传统思维中,所有人盯着同一个资源,而在 MCS 机制中,每个人只关注自己的等待状态,通过有序的链式通知实现协调。这种设计体现了计算机科学中的一个重要原则:通过增加局部性来减少全局冲突。MCS 在锁竞争激烈时尤其有效,就像在拥挤的地铁站设置排队栏杆,虽然增加了少量管理开销,但大大提高了整体通行效率,不会出现一窝蜂似的往地铁里面挤而导致的低吞吐量。MCS Spin Lock 的成功表明,有时候解决竞争问题的最佳方法不是让竞争更高效,而是减少竞争本身。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MyStackTrace 独钓孤舟雪《Linux 内核 MCS Spin Lock 原理》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论