本页目录
锁机制
覆盖: spinlock (qspinlock) → mutex (乐观自旋) → rw_semaphore (percpu) → seqlock → atomic → memory barriers (acquire/release) 内核版本: 2.6 ~ 6.x,重点标注 qspinlock (4.2+) 和 percpu rwsem (5.x+)
概述
内核并发控制的核心问题:多个 CPU 同时访问共享数据。解决方案从最轻量的原子操作到重量级的互斥锁,形成一条清晰的"锁选择链"。选错锁的代价从不必要的 cache line bouncing 到死锁。
关键原则:锁保护的是数据,不是代码。设计锁策略时,先确定"哪些数据被哪些上下文访问",再选合适的锁。
锁选择决策树
flowchart TD
START["访问共享数据<br/>需要同步?"]
START --> Q1{"只是单个整型<br/>(加/减/置位)?"}
Q1 -->|"是"| ATOMIC["atomic_t / atomic64_t<br/>无锁, 硬件保证原子性 ✅"]
Q1 -->|"否"| Q2{"可以睡眠?"}
Q2 -->|"是"| Q3{"读多写少?"}
Q3 -->|"是"| RWSEM["rw_semaphore<br/>多个读者可同时持锁"]
Q3 -->|"否"| MUTEX["mutex (首选)<br/>乐观自旋 → 睡眠"]
Q2 -->|"否<br/>(中断/softirq 上下文)"| Q4{"写少读多<br/>+ 读不能阻塞?"}
Q4 -->|"是"| RCU["RCU / seqlock<br/>读者零开销"]
Q4 -->|"否"| Q5{"需要多个读者<br/>同时持锁?"}
Q5 -->|"是"| RWLOCK["rwlock_t"]
Q5 -->|"否"| SPIN["spinlock"]
SPIN --> Q6{"防什么?"}
Q6 -->|"只防 softirq"| SPIN_BH["spin_lock_bh"]
Q6 -->|"防中断+softirq"| SPIN_IRQ["spin_lock_irqsave"]
classDef start fill:#e3f2fd,stroke:#1565c0
classDef decision fill:#fff3e0,stroke:#ef6c00
classDef lock fill:#e8f5e9,stroke:#2e7d32
classDef api fill:#f3e5f5,stroke:#7b1fa2
class START start
class Q1,Q2,Q3,Q4,Q5,Q6 decision
class ATOMIC,RWSEM,MUTEX,RCU,RWLOCK,SPIN lock
class SPIN_BH,SPIN_IRQ api
Spinlock: qspinlock (4.2+)
为什么需要 qspinlock
旧 ticket spinlock 的问题:
每个 CPU 在 lock word 上自旋 → 所有 CPU 读同一个 cache line
→ cache line 在所有 CPU 之间弹跳 (bouncing)
→ 16 核以上: 性能灾难
qspinlock (queued spinlock):
每个 CPU 在自己的 per-CPU MCS node 上自旋
→ 每个 CPU 自旋在不同的 cache line!
→ 只有 unlock 时需要写下一个 CPU 的 node → 一次 cache line transfer
→ 128 核以上仍然有效
// kernel/locking/qspinlock.c
// qspinlock 的 4 字节 atomic word:
// byte 0: locked (1 bit) + pending (1 bit)
// byte 1: tail index (指向 MCS node 队列尾)
// bytes 2-3: tail CPU
void
// API (include/linux/spinlock.h):
; // 拿锁 (假定已关抢占)
; // 释放
; // 关本地中断 + 拿锁
;
; // 保存 IF 状态 + 关中断 + 拿锁
;
; // 关 bottom half (softirq) + 拿锁
;
Mutex: 乐观自旋
// kernel/locking/mutex.c
;
:
1. 快速路径: 无人持锁 → atomic_long_cmpxchg 设 owner = current, 返回
2. 乐观自旋: owner 在另一个 CPU 上正在运行?
→
→ owner → 停止自旋, 进入睡眠
3. 睡眠等待: 设 TASK_UNINTERRUPTIBLE, 加入 wait_list,
:
1. 设 owner = 0
2. 如果 wait_list 非空 → 唤醒第一个等待者
→ → 等待者回到 mutex_lock 第 2 步
rw_semaphore: 读写信号量
// kernel/locking/rwsem.c
// percpu rwsem (5.x+ 优化):
// 读锁: 增加 percpu refcount → 不需要全局锁 (本地 CPU 操作)
// 写锁: 遍历所有 CPU 的 percpu refcount → 等待全零
// 读者饥饿保护:
// rwsem 跟踪是否有等待的写者
// 如果有等待的写者 → 新读者不进入 → 写者不会被饿死
; // 获取读锁
;
; // 获取写锁
;
; // 非阻塞
Seqlock: 序列锁
// include/linux/seqlock.h
// 写者互斥 (拿 spinlock), 读者无锁
// 读者通过序列号检测"写者是否在修改" → 重试
;
// 读者:
do while ; // 序列号变了? 重试
// 写者:
;
seq++; // 奇数 → 读者看到后重试
// ... 修改数据 ...
seq++; // 偶数 → 读者可以读了
;
// 经典用途: timekeeper (gettimeofday 无锁读, 极少写)
原子操作
// include/linux/atomic/atomic-instrumented.h
// 所有架构保证的基本原子操作:
// 算术
; ;
; ;
// CAS (Compare-And-Swap)
; // if (v==old) v=new; return old;
// RMW with memory ordering (5.x+)
; // return v+n (full barrier)
; // ACQUIRE semantics
; // RELEASE semantics
// 位操作
; ;
; // 原子的 test + set → 用于实现简单锁
Memory Barriers
编译屏障:
barrier(): 防编译器重排 (不影响 CPU 硬件重排)
CPU 内存屏障:
smp_mb(): 全屏障 (前后所有内存操作的有序性)
smp_rmb(): 读屏障 (load 不跨越)
smp_wmb(): 写屏障 (store 不跨越)
Acquire/Release 语义:
ACQUIRE: 这条操作后面的 load/store 不能重排到前面
RELEASE: 前面的 load/store 不能重排到后面
spin_lock() = ACQUIRE (临界区操作不泄漏到锁外)
spin_unlock() = RELEASE
何时需要显式 barrier?
→ 无锁数据结构 (环形缓冲区, RCU)
→ 设备 DMA (确保 DMA 缓冲区写入在启动 DMA 之前完成)
调试与锁分析
# lockdep: 锁依赖验证 (死锁检测)
|
# lockstat: 锁竞争统计
|
# 触发锁竞争追踪
参考
- 源码:
kernel/locking/(qspinlock, mutex, rwsem, rtmutex),include/linux/seqlock.h,include/linux/atomic/ - 内核文档:
Documentation/locking/目录 - LWN: "Queued spinlocks", "The percpu rwsem", "Memory barriers for kernel hackers"
关键词: qspinlock, mutex, rw_semaphore, seqlock, atomic, CAS, memory barrier, acquire/release, lockdep