16 · 全文小结:一句话、一条因果链、一个决策
结论先行(ARM64):PREEMPT_RT 不让系统更快,它让「最慢的一次」可预测。6.12.107 上它把最坏调度延迟从毫秒级压到几十微秒(调优后 5–50 µs),代价是热路径普遍变贵:吞吐型负载损失 0–40%,内核线程更多,能力边界缩小(无 THP、无 NUMA balancing、无 i915)。
一条因果链
12 个差异点不是并列的,而是从一个改变层层推出来的:
- 根本改变(01):内核几乎处处可抢占——不可抢占区只剩
raw_spinlock_t、hardirq 桩、显式preempt_disable()。 - 手段(02):要做到这一点,持锁必须能睡 →
spinlock_t/rwlock_t/mutex/rwsem全部换成带 PI 的 rt_mutex。 - 延伸(03–05):能睡的锁不能在 hardirq 里用 → 驱动逻辑、softirq、定时器回调统统搬进线程。
- 副作用(06–07):搬进线程后,「关抢占」这层隐含保护没了 → 分配器与 per-CPU 数据改用
local_lock+migrate_disable,SLUB 无锁快路径失效。 - 收尾(08–09):RCU 读端可抢占 + 回调线程化 + boost;printk 输出交给线程——6.12 的最后一块拼图。
- 边界(10):Kconfig 把不兼容的特性直接砍掉,把一批参数固定成常量。
- 量化与落地(11–14):延迟赢一个数量级、吞吐让出一到四成;要真正拿到 10 µs 级,还得固件/idle 收口 + CPU 分区(第 13 章 housekeeping)+ 优先级栅格 + 应用侧配合;第 14 章把这套方法落到自动驾驶 SoC 的具体分区样板与参数集。
收益与代价总表
| 差异点 | 延迟收益 | 吞吐 / 开销代价 | 关键源码 |
|---|---|---|---|
| 01 抢占模型 | 不可抢占区 ms → 几十 µs | 抢占更频繁(无 LAZY) | include/linux/preempt.h:157-162 |
| 02 锁 | 消除持锁不可抢占、无界反转 | 无争用 2–4 倍;争用睡眠/唤醒 | kernel/locking/spinlock_rt.c:46-52 |
| 03 中断 | hardirq 停留 → 几 µs;优先级可配 | 每中断多一次切换 | include/linux/interrupt.h:516-525 |
| 04 softirq | 2 ms 黑洞消失 | 小包 pps 60%–90% | kernel/softirq.c:156-264 |
| 05 定时器 | 普通回调不挡 RT 任务 | 普通任务定时器多一次切换 | kernel/time/hrtimer.c:1616-1623 |
| 06 内存 | 分配器可抢占 | 快路径失效;无 THP/NUMA | mm/slub.c:193-205 |
| 07 per-CPU | 临界区可抢占 | 锁开销;均衡失真 | include/linux/local_lock_internal.h:121-170 |
| 08 RCU | 读区不挡任务;无 IPI | GP 变长;expedited 变慢 | kernel/rcu/tree.c:105-109 |
| 09 printk | 日志风暴不再抖动 | 日志到达变晚 | kernel/printk/internal.h:24-35 |
| 10 Kconfig | — | 特性集缩小 | kernel/Kconfig.preempt:70-73 |
| 13 housekeeping | 隔离核只剩 arch timer / IPI | housekeeping 核承担全部家务,吞吐牺牲 | kernel/sched/isolation.c:118-253 |
| 14 自动驾驶 SoC | 控制回路抖动 ms → < 100 µs | 接入簇吞吐 -10%–30%,靠硬件合并补回 | arch/arm64/kernel/entry-common.c:270;drivers/cpuidle/Kconfig.arm:27-33 |
ARM64 上要额外记住的六件事
- arm64 不走通用入口:中断返回抢占在
arch/arm64/kernel/entry-common.c:270-302,preempt_count是 u64 union、need_resched在高 32 位。 - 开了伪 NMI(
irqchip.gicv3_pseudo_nmi=1)后,伪 NMI 返回不抢占、关中断改走ICC_PMR_EL1;硬死锁检测又依赖它——按需取舍。 - arch timer PPI 与 IPI 不可线程化,是 arm64 RT 上仅剩的常驻 hardirq。
- 6.12.107 没有任何串口驱动转成 nbcon,pl011/8250 仍是 legacy;panic 原子输出要等 6.13+。
- 固件噪声是 EL3 安全世界、PSCI 深度 idle(分层布局下 RT 拿不到 cluster 态)、DVFS/SCMI、SDEI——用
hwlatdetect抓。 - big.LITTLE:RT 线程钉大核、
sched_energy_aware=0;THP 缺失可用 16K/64K 页补偿。 - 安全缓解措施有代价:KPTI(仅
kpti_safe_list之外且 CSV3=0 的核,如 A75)、Spectre-BHB 入口序列(A72–A78 均受影响)每次 syscall/中断多几十到几百 ns;mitigations=/nospectre_bhb要过安全评审。 - 自动驾驶 SoC 的额外噪声:非一致性 DMA 的缓存维护、CMA 迁移、SMMU TLB 失效、GPU/NPU 驱动的锁假设、hypervisor vGIC——6.12.107 没有 MPAM,带宽 QoS 靠 SoC 寄存器。
决策:什么时候该用 RT
用 RT:你的指标是「最坏延迟」而不是「平均吞吐」——运动控制、工业总线(EtherCAT/PROFINET)、音频、软件无线电、高频采集;能接受 10%–40% 的吞吐损失;能做 CPU 分区并管理线程优先级。
不用 RT:指标是吞吐或平均延迟(Web、数据库、存储、通用计算);依赖 THP、NUMA balancing、或 GPU/NPU 厂商驱动尚未通过 RT 验证;没有人力做调优与 24 小时验收——这类场景用 PREEMPT_DYNAMIC + preempt=full 加 CPU 隔离往往更划算。
自动驾驶平台:控制/规划回路必须上 RT;感知(GPU/NPU 绑定)对 RT 不敏感但其驱动必须过 RT 验证;把两者放在不同核簇是第 13/14 章的核心建议。
用 RT 时必做的五件事:sched_rt_runtime_us=-1;isolcpus/nohz_full/rcu_nocbs/irqaffinity 分区;按业务重排 irq/* 与 rcuc 优先级;应用 mlockall + 预分配 + PI 互斥锁;24 小时 cyclictest 验收。
6.12.107 之后要留意的变化
| 机制 | 6.12.107 | 后续版本 | 影响 |
|---|---|---|---|
PREEMPT_LAZY |
无 | 6.13+ | 普通任务不再被激进抢占,RT 吞吐改善 |
ktimerd 线程 |
无 | 6.13+ | TIMER_SOFTIRQ 独立线程,可单独调优先级 |
hrtimer_setup() |
无 | 6.13+ | API 变化,行为不变 |
| SLUB hardirq 分配运行时检查 | 无(仅注释) | 6.15+ | 违规驱动会被立刻抓到 |
更多架构 ARCH_SUPPORTS_RT |
x86/arm64/riscv | 逐步扩展 | — |
未尽事项
- 本文数字为公开量级,建议按第 11 章在目标硬件上实测并回填。
- 网络栈的 RT 分支(04 章)值得单独做一次 pps 基准,量化 backlog 线程化与 busylock 的各自贡献。
- 6.13 的
PREEMPT_LAZY对「RT 吞吐损失」的改善幅度值得对照测试。