Linux RT 分析Linux 6.12.107

07 · per-CPU 数据:preempt_disable → local_lock + migrate_disable

结论:非 RT 保护 per-CPU 数据的方式是关抢占(或关中断),几乎零成本但制造不可抢占区。RT 把 local_lock_t 定义成 per-CPU 的 spinlock_t(rt_mutex),加锁 = migrate_disable() + spin_lock(this_cpu_ptr(lock)):任务被钉在当前 CPU 但仍可被抢占,同 CPU 想进同一临界区的任务睡眠并享受 PI。代价是每次加解锁贵一个量级,且「被钉住但可被抢占」的任务给负载均衡制造了一类新麻烦——调度器只能把别的任务推走,不能搬它。内核维护者自己说这是「临时的权宜之计,终极目标是消灭 migrate_disable()」。

第一层

先看结论:RT 用「per-CPU 睡眠锁 + 禁止迁移」替代「关抢占」来保护 per-CPU 数据:临界区可抢占了,但被钉住的任务会干扰负载均衡。

非 RTpreempt_disable 保护 per-CPU 数据
非 RT:preempt_disable 保护 per-CPU 数据
同一场景 · 换成 PREEMPT_RT 内核
PREEMPT_RTlocal_lock = per-CPU 睡眠锁 + migrate_disable
RT:local_lock = per-CPU 睡眠锁 + migrate_disable

第二层

先看结论migrate_disable 只加计数,亲和性在被调度出去时才懒惰收缩;CFS 靠 cpus_ptr 排除钉住的任务,RT 类改推当前任务,DL 类直接放弃。

非 RTlocal_lock 的映射与 lockdep
非 RT:local_lock 的映射与 lockdep
同一场景 · 换成 PREEMPT_RT 内核
PREEMPT_RTmigrate_disable 内部与对负载均衡的影响
RT:migrate_disable 内部与对负载均衡的影响

local_lock 两种实现

include/linux/local_lock_internal.h

API 非 RT(:9-119 RT(:121-170
local_lock_t 无 lockdep 时是空结构体(:11-16 typedef spinlock_t local_lock_t;:127
local_lock() preempt_disable():76-80 migrate_disable(); spin_lock(this_cpu_ptr(lock)):136-140
local_lock_irq() local_irq_disable():82-86 local_lock()不关中断:142
local_lock_irqsave() local_irq_save():88-92 flags = 0; local_lock():144-149
local_lock_nested_bh() 只做 lockdep 断言(:112-116 spin_lock(),不再 migrate_disable(外层 bh 已钉住)(:161-165

文档 Documentation/locking/locktypes.rst:158-206, 324-393 列出了 RT 上会「碎」的写法:local_lock_irq() 后再 raw_spin_lock() 不行(前者不关中断也不关抢占);get_cpu_ptr() + spin_lock() 要改成 migrate_disable(); this_cpu_ptr(); spin_lock():420-441);单靠 migrate_disable() 不防重入,必须用 local_lock:443-467)。

6.12.107 里 local_lock_t 共 26 处实例,关键的:mm/slub.c:396(cpu_slab)、mm/swap.c:55,64mm/memcontrol.c:1701mm/mlock.c:32mm/zsmalloc.c:262kernel/softirq.c:121RT 的 bh 锁本身)、net/core/skbuff.c:284(napi_alloc_cache)、include/linux/netdevice.h:3250(backlog process_queue)、net/core/filter.c:1679net/ipv4/tcp_sigpool.c:14include/net/sock.h:557include/linux/radix-tree.h:29drivers/char/random.c:215,500drivers/md/raid5.h:567drivers/block/zram/zcomp.h:34 等。

migrate_disable 内部

kernel/sched/core.c

对调度器的影响

调度类 行为 位置
CFS 负载均衡 can_migrate_task() 没有显式检查,靠 cpus_ptr 已收缩到单 CPU 使 cpumask_test_cpu(env->dst_cpu, p->cpus_ptr) 失败 → LBF_SOME_PINNED kernel/sched/fair.c:9467-9525
RT push 要推的任务被钉住 → 改推当前正在运行的任务find_lowest_rq(rq->curr) + stop_one_cpu_nowait(push_cpu_stop) kernel/sched/rt.c:2006-2045
RT pull 源任务被钉住 → get_push_task(src_rq) kernel/sched/rt.c:2364-2371
DL push 被钉住 → 直接放弃 kernel/sched/deadline.c:2947-2948
唤醒选核 is_migration_disabled(p) 时跳过 kernel/sched/core.c:3540
CPU 热插拔 rq_has_pinned_tasks() 非零时 CPU 不能下线 kernel/sched/core.c:7939-7952, 8328
set_task_cpu() WARN_ON_ONCE(is_migration_disabled(p)) kernel/sched/core.c:3291
__cant_migrate() might_sleep() 的迁移版断言 kernel/sched/core.c:8728-8757

get_push_task()kernel/sched/sched.h:2631-2648)对 migration_disabled 任务返回 NULL;6.12.107 没有 task_is_pushable() 这个后来的助手。

preempt_disable_nested()

include/linux/preempt.h:438-478:在非 RT 上只是 lockdep 断言(因为持 spinlock/在 softirq 里本来就关了抢占),在 RT 上是真正的 preempt_disable()——用于 seqcount 写侧、vmstat 这类需要 CPU 本地原子性的 RMW。用户:mm/vmstat.cmm/memcontrol.cinclude/linux/u64_stats_sync.h:156,163net/core/gen_estimator.c:93,98net/ipv4/inet_connection_sock.c:1202,1212fs/dcache.c:2440,2453

ARM64 注

性能影响

净影响:每次 local_lock 从约 1 ns 变成几十 ns;被钉住的任务让均衡短期失真;内核维护者视之为待消灭的权宜之计。

指标 非 RT RT 说明
单次 lock+unlock ~1 ns(计数) 函数调用 + rq 计数 + cmpxchg ×2:数十 ns;争用时 µs 级 网络/内存热路径累积
临界区期间本 CPU 独占,不可抢占 可抢占 延迟收益
同 CPU 争用 不存在(不可抢占) 睡眠 + PI 新增的一类等待
负载均衡 不受影响 被钉住的任务不能搬,短期不均;RT push 退化为推走当前任务(stopper 开销)
CPU 下线 正常 需等所有钉住任务离开

源码定位

主题 位置
local_lock 公共 API include/linux/local_lock.h:10-73
非 RT / RT 实现 include/linux/local_lock_internal.h:9-119 / 121-170
文档 Documentation/locking/locktypes.rst:158-206, 324-470, 525-533
migrate_disable/enable kernel/sched/core.c:2304-2401
设计说明 include/linux/preempt.h:371-436
task/rq 字段 include/linux/sched.h:888-891kernel/sched/sched.h:1306-1308, 1365-1374, 2594-2648
负载均衡 / push-pull kernel/sched/fair.c:9328-9525, 11835-12049kernel/sched/rt.c:1946-1957, 2006-2045, 2364-2371kernel/sched/deadline.c:2887-2896, 2947-2948, 3069-3076
preempt_disable_nested include/linux/preempt.h:438-478

调优要点

小结