spinlock 不能睡眠——持有时间长的临界区需要 mutex(互斥锁)和 rwsem(读写信号量)。
一、先看图
flowchart TD
LOCK[mutex_lock] --> FAST{owner == 0?}
FAST -- 是 --> GET[直接获取<br/>一条 CAS]
FAST -- 否 --> SPIN{owner 在运行?}
SPIN -- 是 --> ADAPTIVE[adaptive spin<br/>等 owner 释放]
SPIN -- 否 --> SLEEP[加入等待队列<br/>schedule]
ADAPTIVE --> RELEASED{owner 释放?}
RELEASED -- 是 --> GET
RELEASED -- 超时 --> SLEEP
classDef fast fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef slow fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class GET,FAST fast
class SPIN,ADAPTIVE,RELEASED slow
class SLEEP slow
二、为什么 mutex 先自旋
如果 owner 正在另一个核上运行 → 它很可能马上释放锁 → 自旋等比睡眠+唤醒(两次上下文切换)更快。
这就是 adaptive spin(自适应自旋)。
三、mutex 实现
struct mutex {
atomic_long_t owner; // 持有者 task_struct * + flags
raw_spinlock_t wait_lock; // 保护等待队列
struct list_head wait_list;
// ...
};3.1 Fast path
owner == 0 →
atomic_long_cmpxchg(&lock->owner, 0, current)
→ 获取。
3.2 Optimistic spinning(MCS 等待)
owner 在运行中 → 在 MCS 节点上自旋 → owner 释放时直接交接(handoff)。
3.3 Slow path
owner 不在运行 → 加入 wait_list →
schedule() 睡眠。
3.4 Owner handoff
防止新来者一直抢锁(插队)→ 等待队列第一个 waiter 被标记 handoff → owner 必须直接交给它。
四、rwsem(读写信号量)
struct rw_semaphore {
atomic_long_t count; // 读者计数 + 写者标志
raw_spinlock_t wait_lock;
struct list_head wait_list;
struct task_struct *owner;
// ...
};4.1 语义
- 多读者并行
- 写者独占
- 读写互斥
4.2 公平性
旧版 rwsem 不公平 → 新读者可以在写者等待时插队 → 写者饥饿。
现代 rwsem(5.x+):写者等待时,新读者排队 → 写者优先避免饥饿。
4.3 Optimistic spinning
rwsem 同样支持 adaptive spin → writer 在运行时等,不运行时睡。
五、mutex vs semaphore
| 特性 | mutex | semaphore |
|---|---|---|
| 拥有者 | 有(只能 owner 释放) | 无 |
| recursive | 不允许 | N/A |
| adaptive spin | 有 | 无 |
| lockdep | 支持 | 部分 |
内核建议:尽量用 mutex,不用 semaphore。semaphore 主要用于计数场景。
六、ww_mutex
Wound/Wait mutex:多锁获取时避免死锁。
ww_acquire_init(&ctx, &ww_class);
ww_mutex_lock(&lock_a, &ctx);
ret = ww_mutex_lock(&lock_b, &ctx);
if (ret == -EDEADLK) {
ww_mutex_unlock(&lock_a);
ww_mutex_lock_slow(&lock_b, &ctx);
// 重试 lock_a
}GPU 驱动(DRM/TTM)大量使用。
七、PREEMPT_RT 替换
PREEMPT_RT 内核把 mutex →
rt_mutex:
- 支持优先级继承
- 阻塞时让 waiter 继承 owner 优先级
- 避免优先级反转
八、lockdep 检测
lockdep 在运行时跟踪锁的获取顺序 → 检测 AB-BA 死锁模式。
============================================
WARNING: possible circular locking dependency detected
lockdep 是内核开发者的第一道防线。
九、观察
cat /proc/lock_stat # 锁统计(需 CONFIG_LOCK_STAT)
echo 1 > /proc/sys/kernel/lock_stat # 启用
# lockdep
dmesg | grep -i "lock"
cat /proc/lockdep_chains十、小结
- mutex = adaptive spin + MCS 等待 + sleep
- rwsem = 多读并行 + 写者优先
- adaptive spin 让 mutex 在低竞争时接近 spinlock 性能
- PREEMPT_RT 用 rt_mutex 替换 → 优先级继承
- lockdep 运行时检测死锁
参考文献
kernel/locking/mutex.ckernel/locking/rwsem.cDocumentation/locking/mutex-design.rst- Waiman Long, “rwsem: Implement down_read with ACQUIRE semantics.” 2019
工具
/proc/lock_statperf lockbpftrace -e 'kprobe:mutex_lock_slowpath { @[kstack] = count(); }'- lockdep
上一篇:spinlock 家族 下一篇:RCU
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】lockdep 与内核锁验证
lockdep 报的 ABBA 死锁该不该怕?本文讲 lockdep 锁依赖追踪、lockdep_assert_held、KCSAN 并发检查、KFENCE 内存越界检测、false positive 处理。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。