kernel/locking 内核锁机制与原理详解
源码路径:rk3588/kernel-6.1/kernel/locking/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)
该目录实现 Linux 内核 同步原语(Synchronization Primitives) 的核心运行时:自旋锁、互斥锁、读写信号量、计数信号量、实时互斥锁,以及 Lockdep 运行时锁依赖验证框架。这些原语是调度器、内存管理、文件系统、驱动等所有内核子系统并发控制的基础。
定位:内核并发控制的「基础设施层」;上层子系统通过 include/linux/spinlock.h、mutex.h、rwsem.h 等头文件调用本目录实现。
目录
一、源码目录结构
1.1 编译依赖(Makefile)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| obj-y += mutex.o semaphore.o rwsem.o percpu-rwsem.o
obj-$(CONFIG_DEBUG_IRQFLAGS) += irqflag-debug.o obj-$(CONFIG_DEBUG_MUTEXES) += mutex-debug.o obj-$(CONFIG_LOCKDEP) += lockdep.o lockdep_proc.o obj-$(CONFIG_SMP) += spinlock.o obj-$(CONFIG_LOCK_SPIN_ON_OWNER) += osq_lock.o obj-$(CONFIG_QUEUED_SPINLOCKS) += qspinlock.o obj-$(CONFIG_RT_MUTEXES) += rtmutex_api.o obj-$(CONFIG_PREEMPT_RT) += spinlock_rt.o ww_rt_mutex.o obj-$(CONFIG_QUEUED_RWLOCKS) += qrwlock.o obj-$(CONFIG_LOCK_EVENT_COUNTS) += lock_events.o
|
| 配置项 |
说明 |
CONFIG_SMP |
多核下编译 spinlock 通用实现 |
CONFIG_QUEUED_SPINLOCKS |
MCS 风格排队自旋锁(ARM64 默认 y) |
CONFIG_QUEUED_RWLOCKS |
排队读写自旋锁 |
CONFIG_MUTEX_SPIN_ON_OWNER |
mutex 乐观自旋(SMP + 原子 RMW) |
CONFIG_RWSEM_SPIN_ON_OWNER |
rwsem 乐观自旋 |
CONFIG_RT_MUTEXES |
实时互斥锁(PI 支持) |
CONFIG_LOCKDEP |
运行时锁依赖图验证 |
CONFIG_PROVE_LOCKING |
Lockdep 完整证明模式 |
CONFIG_LOCK_STAT |
锁竞争统计(perf lock) |
CONFIG_PREEMPT_RT |
RT 内核:spinlock→sleeping lock 转换 |
Kconfig 主要位于 kernel/Kconfig.locks 与 lib/Kconfig.debug。
1.2 源文件
| 文件 |
行数 |
功能 |
lockdep.c |
6611 |
锁依赖图、死锁/逆序/IRQ 上下文检测 |
rwsem.c |
1716 |
读写信号量:读者计数 + 写者独占 |
rtmutex.c |
1834 |
RT 互斥锁:优先级继承、自适应自旋 |
mutex.c |
1151 |
互斥锁:handoff、乐观自旋、WW-Mutex |
locktorture.c |
1117 |
锁压力测试(debug 配置) |
lockdep_proc.c |
730 |
/proc/lockdep* 接口 |
test-ww_mutex.c |
683 |
WW-Mutex 自测 |
rtmutex_api.c |
612 |
rtmutex 导出 API(含 #include rtmutex.c) |
qspinlock.c |
596 |
排队自旋锁慢路径 |
ww_mutex.h |
569 |
Wait/Wound 死锁避免算法 |
qspinlock_paravirt.h |
562 |
虚拟化 paravirt qspinlock |
spinlock.c |
415 |
SMP spinlock/rwlock 通用实现 |
rwbase_rt.c |
298 |
PREEMPT_RT 下 rwsem 基类 |
spinlock_rt.c |
280 |
PREEMPT_RT spinlock→rtmutex 包装 |
percpu-rwsem.c |
282 |
Per-CPU 读写信号量 |
semaphore.c |
279 |
计数信号量 |
osq_lock.c |
232 |
乐观自旋队列锁 |
lock_events.c |
179 |
debugfs 锁事件计数 |
spinlock_debug.c |
235 |
spinlock 调试检查 |
mutex-debug.c |
118 |
mutex 调试检查 |
qrwlock.c |
92 |
排队读写锁慢路径 |
ww_rt_mutex.c |
101 |
PREEMPT_RT 下 WW-Mutex |
相关头文件(目录外):
| 文件 |
功能 |
include/linux/spinlock.h |
spinlock/rwlock API |
include/linux/mutex.h |
mutex API |
include/linux/rwsem.h |
rwsem API |
include/linux/rtmutex.h |
rtmutex API |
include/linux/lockdep.h |
Lockdep 宏与 lock_class |
arch/arm64/include/asm/spinlock.h |
ARM64 自旋锁类型与 inline |
include/asm-generic/qspinlock.h |
通用 qspinlock 实现 |
二、整体架构与锁分类
2.1 锁类型对比
| 锁类型 |
睡眠 |
上下文 |
典型用途 |
| spinlock |
否(自旋) |
不可睡眠;IRQ 变体 |
短临界区、per-CPU、IRQ |
| rwlock |
否 |
同上 |
读多写少短临界区 |
| mutex |
是 |
进程上下文 |
长临界区、严格所有权 |
| rwsem |
是 |
进程上下文 |
读多写少长临界区 |
| semaphore |
是 |
含 IRQ(down_trylock) |
计数资源、遗留接口 |
| rtmutex |
是 |
进程上下文 |
PI、PREEMPT_RT、futex |
| percpu-rwsem |
读者无锁 |
进程上下文 |
极多读、极少写 |
2.2 架构层次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 应用 / 驱动 / 文件系统 / MM / 网络 ... │ ▼ include/linux/{spinlock,mutex,rwsem,rtmutex,semaphore}.h │ ▼ kernel/locking/ ← 本目录 ├── qspinlock.c ← 自旋锁慢路径 ├── mutex.c ← 睡眠锁 ├── rwsem.c ├── rtmutex.c ├── lockdep.c ← 验证层(插桩于各锁 API) └── osq_lock.c ← 乐观自旋辅助 │ ▼ arch/arm64/include/asm/spinlock.h ← 架构相关 fastpath include/asm-generic/qspinlock.h
|
2.3 设计原则
- Fastpath / Slowpath 分离:热路径 inline 于头文件或架构代码,冷路径在本目录
- 乐观自旋(Optimistic Spinning):mutex/rwsem 在 owner 运行且未阻塞时短暂自旋,避免调度开销
- Handoff 机制:直接传递锁所有权,减少唤醒延迟
- Lockdep 插桩:通过
lock_acquire/lock_release 构建全局依赖图
三、自旋锁与排队自旋锁
3.1 spinlock.c — SMP 通用实现
当 CONFIG_GENERIC_LOCKBREAK 且非 debug 时,提供带 lockbreak 的 spin 循环:长时间自旋时 preempt_enable() 让出 CPU,并向持有者 CPU 发送 break 信号。
1 2 3 4 5 6 7 8 9 10
| #define BUILD_LOCK_OPS(op, locktype) \ void __lockfunc __raw_##op##_lock(locktype##_t *lock) { for (;;) { preempt_disable(); if (likely(do_raw_##op##_trylock(lock))) break; preempt_enable(); arch_##op##_relax(&lock->raw_lock); } }
|
Lockdep 启用时 禁用 preempt spin-ops,因 lockdep 假设加锁期间不重新开中断。
3.2 qspinlock.c — MCS 排队自旋锁
传统 ticket/spinlock 在 SMP 上导致 缓存行乒乓;qspinlock 基于 MCS lock 改进,将 {tail, next->locked} 压缩进 32 位 spinlock_t:
1 2 3
| lock byte (1B) : 锁状态 / pending tail (3B) : (cpu+1)<<偏移 | nesting_level(2bit) nesting: task / softirq / hardirq / nmi 最多 4 层
|
慢路径流程:
1 2 3 4 5 6
| queued_spin_lock_slowpath() ├── pending 位置自旋(减少 CAS 竞争) ├── 获取 per-CPU qnode(MAX_NODES=4) ├── MCS 队列入队 tail ├── 前驱 unlock 后 acquire └── 队首获取锁,设置 _Q_LOCKED_VAL
|
Per-CPU 队列节点:
1
| static DEFINE_PER_CPU_ALIGNED(struct qnode, qnodes[MAX_NODES]);
|
3.3 qrwlock.c — 排队读写锁
读写锁慢路径:queued_read_lock_slowpath / queued_write_lock_slowpath。
- 读者:减
_QR_BIAS 入队等待,无写者时 acquire
- 写者:设置
_QW_WAITING,等所有读者离开后 _QW_LOCKED
- 中断上下文读者:若写者仅 waiting 未 holding,直接 spin acquire(避免睡眠)
3.4 mcs_spinlock.h
MCS 锁节点定义,供 qspinlock 与 osq_lock 共用:
1 2 3 4
| struct mcs_spinlock { struct mcs_spinlock *next; int locked; };
|
四、互斥锁 mutex(mutex.c)
4.1 数据结构
1 2 3 4 5 6 7 8 9
| struct mutex { atomic_long_t owner; raw_spinlock_t wait_lock; struct list_head wait_list; #ifdef CONFIG_MUTEX_SPIN_ON_OWNER struct optimistic_spin_queue osq; #endif struct lockdep_map dep_map; };
|
owner 标志位:
| Bit |
名称 |
含义 |
| 0 |
MUTEX_FLAG_WAITERS |
等待队列非空,unlock 需 wakeup |
| 1 |
MUTEX_FLAG_HANDOFF |
需 handoff 给 top waiter |
| 2 |
MUTEX_FLAG_PICKUP |
handoff 完成,等待 pickup |
4.2 Fastpath
1 2 3 4 5 6
| void __sched mutex_lock(struct mutex *lock) { might_sleep(); if (!__mutex_lock_fastpath(lock)) __mutex_lock_slowpath(lock); }
|
__mutex_trylock_common() 通过 CAS 设置 owner 为 current;失败进入 slowpath。
4.3 Slowpath 与 Handoff
1 2 3 4 5
| __mutex_lock() ├── mutex_optimistic_spin() ← 见 §九 ├── 加入 wait_list ├── schedule() 睡眠 └── 被唤醒后 __mutex_trylock(handoff=true) pickup
|
Handoff:unlock 时 __mutex_handoff(lock, top_waiter) 直接将 owner 设为等待者并置 PICKUP,避免「释放→再竞争」开销。
4.4 乐观自旋
1 2 3 4 5
| mutex_optimistic_spin(lock, ww_ctx, waiter) ├── need_resched() / !owner_on_cpu() → 放弃 ├── osq_lock(&lock->osq) ← 单 CPU 一个 MCS 节点 ├── spin while owner->on_cpu && !need_resched └── trylock 成功 → 返回 true
|
仅在 owner 正在其他 CPU 运行 时自旋;owner 睡眠则立即 schedule。
4.5 WW-Mutex 集成
ww_mutex_lock() 通过 ww_acquire_ctx 实现 Wait/Wound 机制(见 §十),用于 DRM/GPU 等多对象锁顺序场景。
五、读写信号量 rwsem(rwsem.c)
5.1 count 位域(64 位)
1 2 3 4 5
| Bit 0 : RWSEM_WRITER_LOCKED — 写者持有 Bit 1 : RWSEM_FLAG_WAITERS — 有等待者 Bit 2 : RWSEM_FLAG_HANDOFF — handoff 进行中 Bits 8-62 : 55-bit reader count Bit 63 : RWSEM_FLAG_READFAIL — 读者获取失败守卫位
|
5.2 owner 字段
写者 acquire 时写入 task_struct*;读者设置 READER_OWNED。用于 乐观自旋 判断 owner 是否在运行。
5.3 读者/写者路径
| 操作 |
机制 |
down_read() |
atomic_long_fetch_add(READER_BIAS) fastpath |
down_write() |
cmpxchg 获取 WRITER_LOCKED |
| 竞争 |
wait_list + schedule |
| Lock stealing |
写者 handoff 后读者可「偷取」锁 |
| Optimistic spin |
类似 mutex,spin on writer owner |
5.4 PREEMPT_RT
#ifndef CONFIG_PREEMPT_RT 包裹主实现;RT 内核使用 rwbase_rt.c 将 rwsem 基于 rtmutex 实现。
六、实时互斥锁 rtmutex(rtmutex.c)
6.1 特性
- 优先级继承(PI):解决优先级反转
- 自适应自旋:owner 运行时 spin
- Futex 基础:用户态锁的 kernel 侧实现
- PREEMPT_RT:替代普通 mutex 作为睡眠锁
6.2 owner 编码
与 mutex 类似,bit 0 表示 waiters;fastpath 在 owner 无 waiters 时 CAS acquire/release。
6.3 PI 链
1 2 3 4
| rt_mutex_adjust_prio_chain() → 沿 pi_waiters 链传播 boost → dl_server 支持 SCHED_DEADLINE → max_lock_depth = 1024 限制链深度
|
6.4 rtmutex_api.c
通过 #include "rtmutex.c" 编译,导出 rt_mutex_lock/unlock、rt_mutex_init 等符号;CONFIG_DEBUG_LOCK_ALLOC 下提供 nested 变体。
七、计数信号量 semaphore(semaphore.c)
1 2 3 4 5
| struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list; };
|
| API |
行为 |
down() |
count–;为 0 则睡眠(已废弃,应用 down_interruptible) |
down_interruptible() |
可被信号中断,返回 -EINTR |
down_trylock() |
非阻塞,可在 IRQ 上下文 |
up() |
count++;唤醒 wait_list |
与 mutex 区别:无 owner 概念、可多次 down(计数>1)、调试能力弱;新代码优先 mutex。
八、Per-CPU 读写信号量(percpu-rwsem.c)
适用于 读者极多、写者极少 场景(如 mmap_lock 读者路径优化)。
1 2 3 4 5 6 7
| struct percpu_rw_semaphore { int __percpu *read_count; atomic_t block; struct rcu_sync rss; struct rcuwait writer; wait_queue_head_t waiters; };
|
读者 fastpath:
1 2 3 4 5 6
| preempt_disable() this_cpu_inc(*read_count) smp_mb() if (!atomic_read_acquire(&sem->block)) → 成功 else → dec, 唤醒 writer, 重试/睡眠 preempt_enable()
|
写者:atomic_xchg(&block, 1) 阻止新读者 → 等所有 per-CPU reader count 归零 → 写临界区 → rcu_sync + 解除 block。
九、乐观自旋 OSQ(osq_lock.c)
Optimistic Spin Queue:专为 sleeping lock 的 optimistic spin 设计的 MCS 变体。
1
| static DEFINE_PER_CPU_SHARED_ALIGNED(struct optimistic_spin_node, osq_node);
|
- 每 CPU 一个 spin node(sleeping lock 不在 IRQ 上下文 spin)
osq_lock() / osq_unlock() 维护全局 tail 队列
- mutex、rwsem 在 spin 前
osq_lock(),保证 同一时刻每 CPU 只有一个 optimistic spinner
十、WW-Mutex 死锁避免(ww_mutex.h)
Wait/Wound / Acquired 机制,解决 AB-BA 类死锁(多 mutex 不同获取顺序):
1 2 3 4 5 6
| Context A: 已持锁 1,等待锁 2 Context B: 已持锁 2,等待锁 1 → 经典死锁
WW-Mutex: - 后启动的 context 被 "wound"(强制 drop 已持锁,重试) - 或先启动的 "wait" 对方释放
|
实现要点(ww_mutex.h + mutex.c/rtmutex.c):
ww_acquire_ctx 记录 acquire 顺序
__ww_mutex_add_waiter() 按 stamp 排序等待队列
ww_mutex_lock() 检测冲突并 wound 等待者
DRM GPU 驱动广泛使用;PREEMPT_RT 下由 ww_rt_mutex.c 适配。
十一、Lockdep 锁依赖验证(lockdep.c)
11.1 检测能力
Lockdep 在运行时构建 锁依赖有向图,检测:
| 问题类型 |
说明 |
| 锁逆序(AB-BA) |
即使未死锁,历史上不同顺序也被报告 |
| 循环依赖 |
锁环 |
| IRQ 安全违规 |
在 IRQ 上下文使用非 IRQ-safe 锁 |
| 软 IRQ / Hard IRQ 混用 |
上下文不匹配 |
核心思想:证明观测到的所有锁序在数学上正确,而非仅检测当前是否死锁。
11.2 核心概念
1 2 3
| struct lockdep_map; struct lock_class; struct lock_chain;
|
Usage states(lockdep_states.h):
1 2
| LOCKDEP_STATE(HARDIRQ) LOCKDEP_STATE(SOFTIRQ)
|
每位锁类记录:在 HARDIRQ/SOFTIRQ/PROCESS 等上下文的使用与使能状态。
11.3 关键 API
1 2 3 4 5
| void lock_acquire(struct lockdep_map *lock, unsigned int subclass, int trylock, int read, unsigned long ip, ...); void lock_release(struct lockdep_map *lock, unsigned long ip); void lock_acquired(struct lockdep_map *lock, unsigned long ip); void lock_contended(struct lockdep_map *lock, unsigned long ip);
|
各锁 *_lock() 宏展开时调用上述函数(CONFIG_PROVE_LOCKING 时完整验证)。
11.4 内部保护
Lockdep 自身用 raw arch_spinlock(__lock)保护图结构,避免递归进入 lockdep:
1 2
| static arch_spinlock_t __lock;
|
11.5 proc/debug 接口
lockdep_proc.c:/proc/lockdep、/proc/lockdep_chains
CONFIG_LOCK_STAT:竞争统计 → perf lock
CONFIG_LOCK_EVENT_COUNTS:debugfs lock_event_counts/
十二、调试与测试组件
| 组件 |
文件 |
功能 |
| DEBUG_SPINLOCK |
spinlock_debug.c |
未初始化/死锁检测 |
| DEBUG_MUTEXES |
mutex-debug.c |
owner 校验、waiter 跟踪 |
| DEBUG_IRQFLAGS |
irqflag-debug.c |
IRQ flag 状态一致性 |
| LOCK_TORTURE_TEST |
locktorture.c |
多锁类型压力测试 |
| WW_MUTEX_SELFTEST |
test-ww_mutex.c |
WW-Mutex 单元测试 |
| PREEMPT_RT |
spinlock_rt.c, rwbase_rt.c |
spinlock→rtmutex 语义转换 |
特殊编译标志:
1 2 3
| KCOV_INSTRUMENT := n KCSAN_SANITIZE_lockdep.o := n CFLAGS_REMOVE_lockdep.o = $(CC_FLAGS_FTRACE)
|
十三、RK3588/ARM64 平台说明
13.1 架构 Kconfig 选择
| 配置 |
ARM64 |
说明 |
ARCH_USE_QUEUED_SPINLOCKS |
✅ select |
使用 qspinlock |
ARCH_USE_QUEUED_RWLOCKS |
✅ select |
使用 qrwlock |
QUEUED_SPINLOCKS |
def_y |
SMP 下自动启用 |
QUEUED_RWLOCKS |
def_y |
非 PREEMPT_RT |
MUTEX_SPIN_ON_OWNER |
def_y |
SMP + ARCH_SUPPORTS_ATOMIC_RMW |
RWSEM_SPIN_ON_OWNER |
def_y |
同上 |
LOCKDEP_SUPPORT |
✅ |
支持 lockdep |
ARCH_SUPPORTS_ATOMIC_RMW |
✅ |
LL/SC 或 LSE 原子操作 |
13.2 自旋锁实现路径
1 2 3 4 5 6
| arch/arm64/include/asm/spinlock.h → #include <asm/qspinlock.h> arch/arm64/include/asm/Kbuild → generic-y += qspinlock.h ← 使用 asm-generic 实现 arch/arm64/include/asm/spinlock_types.h → #include <asm-generic/qspinlock_types.h>
|
ARM64 无独立 qspinlock 汇编 fastpath,使用 include/asm-generic/qspinlock.h 的原子操作 fastpath + kernel/locking/qspinlock.c 慢路径。
13.3 big.LITTLE 影响
RK3588 4×A76 + 4×A76 大小核:
- qspinlock 排队 减少跨核缓存乒乓,对大小核混合竞争有益
- 乐观自旋 依赖
owner_on_cpu():任务迁移后可能提前结束 spin
- Lockdep 开销较大,生产内核通常
CONFIG_LOCKDEP=n
- PREEMPT_RT 非 RK3588 默认;启用后 spinlock 语义变为可睡眠
13.4 典型 defconfig 预期
CONFIG_SMP=y、CONFIG_QUEUED_SPINLOCKS=y
CONFIG_MUTEX_SPIN_ON_OWNER=y
CONFIG_LOCKDEP 通常 debug 配置启用
CONFIG_RT_MUTEXES=y(init/Kconfig 默认)
十四、典型加锁路径时序
14.1 mutex_lock 完整路径
14.2 qspinlock 竞争路径
1 2 3 4
| CPU0: spin_lock() → fastpath CAS 成功 CPU1: spin_lock() → fastpath 失败 → queued_spin_lock_slowpath() → 分配 qnodes[0] → MCS 入队 → spin on prev->locked CPU0: spin_unlock() → 释放 → CPU1 acquire
|
14.3 Lockdep 插桩路径
1 2 3 4 5 6
| mutex_lock() → mutex_acquire(&lock->dep_map, ...) ← lock_acquire() → ... 实际加锁 ... → lock_acquired(&lock->dep_map, ip) mutex_unlock() → lock_release(&lock->dep_map, ip)
|
十五、总结
| 组件 |
职责 |
| qspinlock/qrwlock |
SMP 可扩展自旋锁,MCS 排队减少缓存竞争 |
| mutex/rwsem |
进程上下文睡眠锁,handoff + 乐观自旋优化 |
| rtmutex |
PI + RT 内核基础 + futex 后端 |
| semaphore |
计数信号量(遗留,少用) |
| percpu-rwsem |
多读少写极致读性能 |
| osq_lock |
乐观自旋排队,每 CPU 单节点 |
| ww_mutex |
多对象锁顺序死锁避免 |
| lockdep |
运行时锁依赖正确性证明 |
kernel/locking/ 是内核并发控制的基石;RK3588 ARM64 默认启用 排队自旋锁 与 乐观自旋,在 8 核 big.LITTLE 上提供较好的锁扩展性。开发与调试阶段建议启用 Lockdep 捕获锁序问题,生产环境按需求权衡性能开销。
附录:源文件清单
| 文件 |
行数 |
kernel/locking/lockdep.c |
6611 |
kernel/locking/rwsem.c |
1716 |
kernel/locking/rtmutex.c |
1834 |
kernel/locking/mutex.c |
1151 |
kernel/locking/locktorture.c |
1117 |
kernel/locking/lockdep_proc.c |
730 |
kernel/locking/test-ww_mutex.c |
683 |
kernel/locking/rtmutex_api.c |
612 |
kernel/locking/qspinlock.c |
596 |
kernel/locking/ww_mutex.h |
569 |
kernel/locking/qspinlock_paravirt.h |
562 |
kernel/locking/spinlock.c |
415 |
kernel/locking/rwbase_rt.c |
298 |
kernel/locking/spinlock_rt.c |
280 |
kernel/locking/percpu-rwsem.c |
282 |
kernel/locking/semaphore.c |
279 |
kernel/locking/osq_lock.c |
232 |
kernel/locking/lock_events.c |
179 |
kernel/locking/spinlock_debug.c |
235 |
kernel/locking/mutex-debug.c |
118 |
kernel/locking/qrwlock.c |
92 |
kernel/locking/ww_rt_mutex.c |
101 |
| 头文件等 |
~700 |
| 合计 |
~19645 |
关联源码(目录外):
| 文件 |
说明 |
include/linux/spinlock.h |
自旋锁 API 与 lockdep 宏 |
include/linux/mutex.h |
mutex 类型与 API |
include/linux/rwsem.h |
rwsem API |
include/linux/lockdep.h |
Lockdep 公共接口 |
arch/arm64/include/asm/spinlock.h |
ARM64 锁类型 |
include/asm-generic/qspinlock.h |
通用 qspinlock fastpath |
kernel/Kconfig.locks |
锁相关 Kconfig |
lib/Kconfig.debug |
Lockdep/调试 Kconfig |
Documentation/locking/ |
官方设计文档 |
正在加载留言…