Linux RT 分析Linux 6.12.107

02 · 锁:spinlock_t / rwlock_t / mutex 全部变成 rt_mutex

结论:RT 内核里 spinlock_trwlock_tlocal_lock_tmutexrw_semaphore 底层都是带优先级继承(PI)的 rt_mutex;只有 raw_spinlock_t 和 bit spinlock 还是真正的自旋锁。这消除了「持锁不可抢占」和「无界优先级反转」,代价是每次加解锁多做 migrate_disable/enable 与 RCU 读区进出,争用时要睡眠/唤醒(而非自旋),并且 spin_lock_irqsave() 在 RT 上根本不关中断——依赖这一点的驱动代码在 RT 上是错的。

第一层:两颗 CPU 争一把锁

先看结论:RT 把等锁从「烧 CPU 自旋」变成「睡眠 + 提升持锁者」,消灭了持锁不可抢占与无界优先级反转。

非 RTB 忙等烧 CPU,C 被 A 的 preempt_disable 挡住
非 RT:B 忙等烧 CPU,C 被 A 的 preempt_disable 挡住
同一场景 · 换成 PREEMPT_RT 内核
PREEMPT_RTB 睡眠并把 A 提升到自己的优先级,C 可以立即抢占 A
RT:B 睡眠并把 A 提升到自己的优先级,C 可以立即抢占 A

非 RT 的 qspinlock 是「关抢占 + 自旋」:等锁的 CPU 100% 忙等,持锁 CPU 上任何高优先级任务都进不来;如果持锁者被中断打断,所有等待者跟着延长。RT 的 rt_spin_lock() 在争用时把等待者挂到红黑树并睡眠,同时把持锁者提升到最高等待者的优先级——持锁者尽快跑完,等待者不浪费 CPU。

第二层:完整加解锁路径

先看结论:无争用时只多两次 cmpxchg 和 migrate_disable/enable;有争用时才进红黑树、PI 链和睡眠。spin_lock_irqsave() 不再关中断。

非 RTqspinlock 快路径 cmpxchg,慢路径 MCS 队列自旋
非 RT:qspinlock 快路径 cmpxchg,慢路径 MCS 队列自旋
同一场景 · 换成 PREEMPT_RT 内核
PREEMPT_RTrt_mutex 快路径 cmpxchg owner,慢路径入树 / PI 链 / 自适应自旋 / 睡眠
RT:rt_mutex 快路径 cmpxchg owner,慢路径入树 / PI 链 / 自适应自旋 / 睡眠

类型替换

RT 的 spin_lock() 做了什么

kernel/locking/spinlock_rt.c:46-52

static __always_inline void __rt_spin_lock(spinlock_t *lock)
{
    rtlock_might_resched();          /* 注解:这把锁可能睡眠 */
    rtlock_lock(&lock->lock);        /* cmpxchg(owner: NULL→current),失败走 rtlock_slowlock() */
    rcu_read_lock();                 /* 保持非 RT 下「持自旋锁隐含 RCU 读区」的语义 */
    migrate_disable();               /* 替代 preempt_disable:钉住 CPU 但可抢占 */
}

文件头(:3-19)明确写着:非 RT 自旋锁关抢占并最终关中断;RT 替代品显式禁止迁移、并在持锁期间进入 RCU 读区。

spin_lock_irqsave() 不关中断include/linux/spinlock_rt.h:96-101 就是 flags = 0; spin_lock(lock);spin_unlock_irqrestore() 忽略 flags(:119-123)。_bh 变体仍会 local_bh_disable():84-89),但那在 RT 上也是一把 per-CPU 锁(第 04 章)。spin_is_contended() 恒为 0(:147)。

慢路径:入树、PI、自旋还是睡眠

kernel/locking/rtmutex.c:1838-1904 rtlock_slowlock_locked()

  1. wait_lock 再试一次 try_to_take_rt_mutex():1846-1847)。
  2. current_save_and_set_rtlock_wait_state():1852):把任务状态保存后设为 TASK_RTLOCK_WAIT,因此在 TASK_INTERRUPTIBLE 里拿自旋锁不会丢唤醒(Documentation/locking/locktypes.rst:257-293 描述了 saved_state 协议)。
  3. task_blocks_on_rt_mutex():1856,定义 :1195-1304):按优先级插入红黑树;若成为最高等待者,rt_mutex_adjust_prio(lock, owner) 提升持锁者(:1264-1271),持锁者自己也被阻塞时沿链继续 rt_mutex_adjust_prio_chain():677-723)。
  4. 自旋还是睡眠:1873-1874):if (!owner || !rtmutex_spin_on_owner(lock, &waiter, owner)) schedule_rtlock();——只有最高等待者、且持锁者正在别的 CPU 上运行时才自旋(:1486-1521,停止条件:owner 被调度出去、自己不再是顶端等待者、need_resched()、vCPU 被抢占);UP 上永远直接睡(:1522-1529)。
  5. 释放:rt_spin_unlock()spinlock_rt.c:78-104)先 migrate_enable(),快路径 cmpxchg(owner: current→NULL),有等待者则 rt_mutex_slowunlock() 唤醒树最左节点并 deboost,最后 rcu_read_unlock()

等待者排序(:393-409):先按 prio,两者都是 SCHED_DEADLINE 时按 deadline 早者优先。PI 链深度上限 max_lock_depth = 1024kernel/locking/rtmutex_api.c:14/proc/sys/kernel/max_lock_depth 可调,超限返回 -EDEADLKrtmutex.c:700-718)。同优先级「横向偷锁」只允许非 RT 优先级的任务(:435-446),避免给 RT 任务引入无界延迟。

rwlock / rwsem:读者偏向,写者不公平

kernel/locking/rwbase_rt.c:4-42:读者走原子计数快路径(READER_BIAS = 1<<31),写者先拿 rtmutex、再减去 READER_BIAS 把读者赶进慢路径、等所有读者离开。RT 的 rwsem/rwlock 不是写者公平的——写者可能饥饿,但写者受 PI 保护;之所以不做多读者继承,是因为 SCHED_DEADLINE 无法支持(:33-39)。locktypes.rst:313-315 直言:一个被抢占的低优先级读者会让高优先级写者饿死。

嵌套规则(RT 特有)

Documentation/locking/locktypes.rst:524-531:RT 把 spinlock_t/rwlock_t/local_lock 从「自旋」类降到「睡眠」类,因此它们不能在 raw_spinlock_t 临界区内获取;嵌套顺序只能是 睡眠锁 → spinlock_t/rwlock_t/local_lock → raw_spinlock_t/bit spinlock。反过来(raw 在 spinlock_t 里)允许。PROVE_RAW_LOCK_NESTINGlib/Kconfig.debug:1398)可以在非 RT 内核上提前检查。

一个直接推论(locktypes.rst:470-489):持 raw_spinlock_t不能 kmalloc()(RT 的分配器会睡),但持普通 spinlock_t 时可以。

ARM64 注

性能影响

净影响:无争用锁开销约 2–4 倍,争用锁从自旋变睡眠;这是 RT 吞吐下降的第二大来源,也是延迟有界的基础。

场景 非 RT qspinlock RT rt_mutex 备注
无争用 lock+unlock ~1 次原子操作 + preempt 计数(~20 cycles) cmpxchg ×2 + migrate_disable/enable + RCU 进出(约 2–4 倍) 热路径锁(网络、块层)密度高时可见
争用(持锁者在跑) 自旋,等待时间 = 剩余临界区 自适应自旋(仅顶端等待者) 相近
争用(持锁者被抢占/睡眠) 自旋到持锁者被再次调度(可能很久) 睡眠 + PI 提升持锁者 RT 显著更优,这是 PI 的价值
唤醒成本 2 次上下文切换(几 µs) 高争用锁吞吐下降
spin_lock_irqsave 关中断 不关中断 依赖关中断保护的代码必须改 raw 或 local_lock_irq
rwlock 读多写少 公平 读者偏向;写者可能饿 写延迟不确定性
spin_is_contended() 有效 恒 0 依赖它做 yield 的代码退化

源码定位

主题 位置
RT spinlock_t 定义 include/linux/spinlock_types.h:45-61include/linux/rtmutex.h:23-27
RT spin_lock*() 映射 include/linux/spinlock.h:454-455include/linux/spinlock_rt.h:42-45, 84-101, 119-123, 147
rt_spin_lock/unlock/trylock kernel/locking/spinlock_rt.c:38-137
慢路径与自适应自旋 kernel/locking/rtmutex.c:1828-1904, 1486-1529
PI 链遍历 / 优先级调整 kernel/locking/rtmutex.c:526-539, 677-723, 1195-1304kernel/sched/core.c:7123
max_lock_depth kernel/locking/rtmutex_api.c:14kernel/sysctl.c:1937-1945
等待者排序 kernel/locking/rtmutex.c:388-427
RT_MUTEX_HAS_WAITERS kernel/locking/rtmutex_common.h:156rtmutex.c:67-103, 194-208
rwbase_rt include/linux/rwbase_rt.h:8-37kernel/locking/rwbase_rt.c:4-300
RT rwsem / rwlock 粘合 kernel/locking/rwsem.c:1402-1516kernel/locking/spinlock_rt.c:164-260
RT mutex include/linux/mutex_types.h:56-67include/linux/mutex.h:100-123kernel/locking/rtmutex_api.c:498-531
锁类型文档 Documentation/locking/locktypes.rst:46-50, 229-255, 305-318, 470-534

调优要点

小结