spinlock 与自旋锁
这页讲的是:有些共享状态必须立刻保护,但当前环境又不允许睡下等待,这时内核常会用 spinlock 这种“先别动,我很快就好”的方式。学这页的重点,是理解自旋锁的核心不是“锁住资源”这么泛,而是“在不能睡眠的短临界区里,用忙等换取原子化的执行窗口”。
这块是什么
spinlock 与自旋锁,讲的是 Linux 怎样在一些很短、很敏感、而且不能睡眠的执行路径里,保护共享状态不被并发访问。它关注的是“先让别的执行别进来,我马上处理完”,而不是让等待者舒服地睡下慢慢等。
可以把它理解成:你要在窄门口快速搬一下东西,别人先别挤进来;如果很快就结束,让他们站着等一小会儿反而比安排所有人去休息室再叫回来更划算。
它负责什么
保护很短的共享临界区
- 有些状态变化必须一口气做完
- 中途不能让别的 CPU 或上下文插进来
- 自旋锁适合保护这种短小而敏感的片段
- 把共享修改压进一个紧凑执行窗口
适配不能睡眠的环境
- 有些上下文里根本不允许阻塞等待
- 这时只能让竞争者短暂忙等
- 用 CPU 空转换取语义上的立即保护
- 让原子上下文也能有正式同步手段
配合中断与本地时序约束
- 不只是多核并发,有时还要考虑本地中断或下半部重入
- 锁语义常和中断关闭范围一起考虑
- 让“谁会同时碰这份状态”被认真算清楚
- 避免本地路径自己打断自己
为什么它不是“更快的 mutex”
| 如果想得太简单 | 会怎样 | 真正关键在哪 |
|---|
| 觉得自旋锁只是更轻量 | 会忽略它和 mutex 的最大差别其实是等待语义不同。 | 一个忙等不睡,一个会让等待者睡下。 |
| 把它用在很长路径上 | 会让 CPU 白白空转,延迟和吞吐一起变差。 | 自旋锁最怕临界区过长。 |
| 只看多核竞争 | 会漏掉本地中断或下半部也可能重入同一状态。 | 锁设计常常同时在防跨 CPU 和防本地重入。 |
| 以为拿了锁就万事大吉 | 会忽略上下文约束:拿着自旋锁时通常不能做会睡眠的事。 | 锁不仅保护数据,也在约束代码写法。 |
关键概念
| 概念 | 现在怎么理解 |
|---|
| spinlock | 竞争失败时不睡眠,而是在原地短暂忙等的锁。 |
| 忙等 | 等待者持续占着 CPU 检查锁是否释放,而不是阻塞挂起。 |
| 短临界区 | 适合自旋锁保护的通常是很快就能完成的小段共享修改。 |
| 原子上下文 | 不能阻塞睡眠的执行环境,自旋锁经常出现在这里。 |
| 本地中断约束 | 有时不仅要防别的 CPU,还要防本地事件在不合适时机重入。 |
为什么重要
- 它解释了为什么内核并发控制不只是“谁先拿到锁”,还要看当前环境允不允许睡。
- 很多中断、调度和底层高频路径,都离不开这种短平快的保护方式。
- 理解这层后,你会更容易把抢占、中断、本地 CPU 状态和共享数据保护串起来。
- 它让你看到:同步原语不是一个抽象名字,而是带着明确上下文假设的工具。
常见误解
- 误解一:自旋锁一定更快。实际上临界区一长,它就可能比睡眠锁更糟。
- 误解二:只有多核才需要它。实际上本地重入和上下文约束同样会让它变重要。
- 误解三:锁只是防并发访问。实际上拿锁后“还能不能做别的事”同样关键。
它不负责什么
- 它不适合长时间等待资源准备好的场景,那通常该交给可睡眠同步机制。
- 它不替代生命周期管理;对象活着与否仍要靠别的规则保证。
- 它不天然解决所有顺序问题,跨 CPU 可见性和更大范围协议仍要整体设计。
和其他模块的关系
| 相关模块 | 关系 |
|---|
| preemption 与抢占 | 某些本地连续性假设要求拿锁时同时避免当前执行被不合适地切走。 |
| 中断与下半部 | 很多自旋锁使用场景都和中断上下文或底半部重入密切相关。 |
| mutex | 两者都在做互斥,但一个适合不能睡的短路径,一个适合能睡的较长等待。 |
| per-CPU data | 缩小共享范围常常是避免到处上自旋锁的另一种思路。 |
读完这页后,你应该能回答
- 为什么自旋锁真正区分开的不是“锁”本身,而是“等待时不睡觉”这件事?
- 为什么自旋锁最怕临界区变长、最适合原子上下文短保护?
- 为什么有些锁选择不仅在防多核竞争,也在防本地中断或底半部重入?
后面适合继续问:spin_lock、spin_lock_irqsave 和禁止抢占最容易混在哪?什么时候把共享状态改成 per-CPU 比继续加自旋锁更好?为什么拿着自旋锁去做可能睡眠的事情会出大问题?