Linux内核锁机制主流程总览
几乎所有锁类型都遵循这个主流程,但,决定了不同锁的适用场景和性能表现。
·
一、Linux内核锁机制主流程总览
主流程节点
- 加锁(Lock):尝试获取锁,保护临界区资源
- 临界区(Critical Section):被锁保护的共享资源访问
- 解锁(Unlock):释放锁,允许其他线程/CPU获取
几乎所有锁类型都遵循这个主流程,但加锁/解锁的具体实现和策略、临界区访问特性,决定了不同锁的适用场景和性能表现。
二、六大锁机制主线环节细化剖析
2.1 自旋锁(Spinlock)
设计思想
- 利用原子操作,循环等待直到获取锁,不可睡眠,适合临界区极短场景
- 用于SMP(多核),避免睡眠带来的上下文切换开销
主流程与方法
// include/linux/spinlock.h
typedef struct spinlock {
atomic_t lock;
} spinlock_t;
// arch/x86/include/asm/spinlock.h
static inline void arch_spin_lock(arch_spinlock_t *lock) {
while (atomic_cmpxchg(&lock->slock, 0, 1) != 0)
cpu_relax(); // 忙等,避免CPU过热
}
内部逻辑:
- CAS原子操作尝试置1(加锁)
- 未获取成功则循环自旋(cpu_relax)
- 成功则进入临界区
流程图&口诀:
尝试CAS加锁
|
成功?
/ \
是 否
进入 循环
临界 忙等
区
口诀:短用自旋,忙等不眠
典型用法:
spin_lock(&mylock);
// critical section
spin_unlock(&mylock);
调试技巧:
- 使用
spin_lock_debug()追踪死锁 cpu_relax()降低能耗
设计模式:
- 模板方法模式:平台相关的加锁用内联汇编实现
- 乐观锁思想:假设锁竞争少
参考资料:
- Linux Kernel source: spinlock.h
- 《Linux内核设计与实现》
2.2 互斥锁(Mutex)
设计思想
- 临界区可长,可睡眠,适合复杂操作
- 获取失败阻塞当前线程,进入等待队列
主流程与方法
// kernel/locking/mutex.c
void mutex_lock(struct mutex *lock) {
if (!atomic_cmpxchg(&lock->count, 1, 0))
return; // 成功获取
__mutex_lock_slowpath(lock); // 阻塞睡眠
}
内部逻辑:
- 原子尝试加锁
- 失败则排队,当前线程睡眠
- 唤醒后重试获取
流程图&口诀:
尝试CAS加锁
|
成功?
/ \
是 否
进入 阻塞
临界 等待队列
区
口诀:临界长,休眠忙;队列调度不慌张
典型用法:
mutex_lock(&my_mutex);
// 临界区
mutex_unlock(&my_mutex);
调试技巧:
lockdep检测死锁依赖mutex_trylock()非阻塞尝试
设计模式:
- 责任链模式:线程挂队列,依次唤醒
- 悲观锁思想
参考资料:
- Linux Kernel source: mutex.c
- [Linux Kernel Development, Robert Love]
2.3 读写自旋锁(RWLock)
设计思想
- 读多写少,读并发提升吞吐,写时独占
- 读操作可并行,写操作需要全部独占
主流程与方法
// include/linux/rwlock.h
void read_lock(rwlock_t *lock) {
atomic_dec(&lock->counter); // 读计数递减,允许并发
}
void write_lock(rwlock_t *lock) {
while (atomic_cmpxchg(&lock->counter, RW_UNLOCKED, RW_LOCKED) != RW_UNLOCKED)
cpu_relax();
}
内部逻辑:
- 读:计数递减,多个读并发
- 写:CAS独占,等待所有读写结束
流程图&口诀:
读锁:
计数递减
多个读并发
写锁:
CAS独占
等待所有读释放
口诀:多读并发,写独占
典型用法:
read_lock(&rwlock);
// 多线程读
read_unlock(&rwlock);
write_lock(&rwlock);
// 独占写
write_unlock(&rwlock);
调试技巧:
lockstat分析竞争瓶颈
设计模式:
- 读写锁模式
- 细粒度锁
参考资料:
- Linux Kernel source: rwlock.h
- 《深入理解Linux内核》
2.4 信号量(Semaphore)
设计思想
- 资源计数型同步
- 适合多资源并发,支持睡眠等待
主流程与方法
// kernel/locking/semaphore.c
void down(struct semaphore *sem) {
if (atomic_dec_return(&sem->count) < 0)
__down(sem); // 阻塞
}
内部逻辑:
- 资源计数减1
- 小于0则阻塞,否则进入临界区
流程图&口诀:
计数--
计数<0?
是 --> 阻塞
否 --> 进入临界区
口诀:计数同步,资源共享;可睡等待,灵活扩展
典型用法:
down(&sem);
// 临界区
up(&sem);
调试技巧:
sema_init()初始化信号量up()/down()配合使用
设计模式:
- 生产者-消费者模式
- 信号驱动同步
参考资料:
- Linux Kernel source: semaphore.c
- [Understanding the Linux Kernel]
2.5 RCU锁(Read-Copy-Update)
设计思想
- 读操作无锁,极大提升多核并发读性能
- 写操作延迟回收,保证数据一致性
主流程与方法
rcu_read_lock();
p = rcu_dereference(ptr);
// 读操作
rcu_read_unlock();
// 写操作
p_new = kmalloc(...);
rcu_assign_pointer(ptr, p_new);
synchronize_rcu();
kfree(p_old);
内部逻辑:
- 读:直接访问,无锁
- 写:新建对象,指针切换,等待所有旧读完成后回收
流程图&口诀:
读操作
直接无锁
写操作
新对象
指针切换
延迟回收
口诀:读快无锁,写慢延迟
典型用法:
rcu_read_lock();
data = rcu_dereference(global_ptr);
rcu_read_unlock();
update:
new = kmalloc(...);
rcu_assign_pointer(global_ptr, new);
synchronize_rcu();
kfree(old);
调试技巧:
rcu_barrier()等待回调完成synchronize_rcu()同步回收
设计模式:
- 副本-更新模式
- 延迟回收
参考资料:
- Linux Kernel source: rcupdate.h
- What is RCU? by Paul E. McKenney
2.6 顺序锁(Seqlock)
设计思想
- 通过版本号检测,读操作无锁,写操作独占
- 适合读多写少,对一致性要求不高场景
主流程与方法
unsigned seq;
do {
seq = read_seqbegin(&seqlock);
// 读数据
} while (read_seqretry(&seqlock, seq));
内部逻辑:
- 读前记录版本号
- 读后检查版本号是否变化,变化则重试
流程图&口诀:
读操作
记录版本号
读数据
版本号变? -> 重试
否则成功
口诀:顺序检测,重试无锁;读多写少,效率拔高
典型用法:
do {
seq = read_seqbegin(&seqlock);
data = shared_data;
} while (read_seqretry(&seqlock, seq));
调试技巧:
- 适合统计类、监控类数据
设计模式:
- 乐观并发控制
- 版本检测
参考资料:
- Linux Kernel source: seqlock.h
- Linux Seqlock Documentation
三、高阶设计思想与集成解决方案
3.1 多锁嵌套与死锁预防
- 锁排序法:规定获取顺序,避免循环依赖
- lockdep工具自动检测死锁
3.2 NUMA架构优化
- 分布式锁(如qspinlock、MCS lock):减少跨节点通信
- 局部热路径锁分离:提升缓存命中率
3.3 锁粒度细化与无锁化
- 细粒度锁:按数据区域分锁,减少竞争
- 无锁数据结构:如RCU、seqlock适应极端高并发
3.4 调试与性能分析
lockstat、perf lock分析热点ftrace、systemtap跟踪锁操作
四、方法论归纳
- 选锁有道:依据临界区长度、并发读写比例、是否可睡眠选择锁类型
- 源码为根:行级解读,理解实现逻辑
- 调试为辅:善用内核调试工具
- 模式驱动:结合经典并发设计模式
五、参考资料与文献
- Love, R. Linux Kernel Development
- Bovet, D. P., & Cesati, M. Understanding the Linux Kernel
- Paul E. McKenney, What is RCU?
- Linux Kernel source code: elixir.bootlin.com
- Linux Seqlock Documentation: kernel.org
六、锁机制速查表
| 场景 | 推荐锁类型 | 典型用法 | 口诀 |
|---|---|---|---|
| 短临界区、SMP | 自旋锁 | spin_lock | 短用自旋 |
| 长临界区、可睡眠 | 互斥锁 | mutex_lock | 长用互斥 |
| 读多写少 | 读写锁/RCU/顺序锁 | rwlock/RCU/seqlock | 读多用RW/RCU/顺序 |
| 资源计数 | 信号量 | down/up | 资源同步 |
七、流程图口诀速记
- 自旋锁:CAS忙等→成功进区→失败循环
- 互斥锁:CAS失败进队列→唤醒调度
- 读写锁:读计数并发,写独占
- 信号量:计数–,<0阻塞
- RCU:读无锁,写复制延迟回收
- 顺序锁:版本检测,重试无锁
如果你还有某一具体锁机制(如spinlock、mutex等)需要进一步源码行级讲解,或者某一流程图、设计模式、调试技巧需要更详细说明,可以继续追问!
更多推荐
所有评论(0)