4. PREEMPT_RT补丁
PREEMPT_RT 补丁是 Linux 内核实时化改造的事实标准。它通过一系列内核抢占、锁机制和中断管理的深度修改,将通用 Linux 内核转变为硬实时操作系统。理解 PREEMPT_RT 的背景、特性与配置,是搭建 Cyclictest 测试工具链、分析调度延迟的前提。
4.1 项目背景:为什么需要 PREEMPT_RT?
通用 Linux 内核(主线内核)在设计时优先考虑吞吐量和公平性,而非确定性的响应时间。在工业控制、机器人、音频处理、航空航天等场景中,任务必须在微秒级的时间窗口内完成,否则会导致设备损坏或数据丢失。主线内核存在以下实时性瓶颈:
- 不可抢占的内核路径: 系统调用、中断处理等路径中,内核持有锁或处于临界区时,高优先级任务无法抢占,导致调度延迟不可控。
- 中断关闭(irq off)区域: 内核代码中大量使用
local_irq_save()等原语关闭本地中断,导致外部事件无法及时响应。 - 自旋锁(spinlock)导致的优先级反转: 低优先级任务持有自旋锁时,高优先级任务只能忙等,无法被调度执行。
- 非抢占的 RCU 读端临界区: RCU(Read-Copy-Update)的某些实现会阻塞抢占,增加延迟抖动。
关键数据: 在未打 PREEMPT_RT 补丁的通用内核上,使用 Cyclictest 测得的调度延迟通常在 50µs ~ 200µs 之间,且存在长尾抖动(>1ms)。而经过 PREEMPT_RT 改造后,典型延迟可降至 5µs ~ 20µs,且最大延迟可控在 50µs 以内(取决于硬件平台)。
PREEMPT_RT 项目由 Thomas Gleixner 等人发起,最初作为独立补丁集维护,自 Linux 2.6 时代开始逐步将实时化特性合入主线。截至 Linux 6.x 版本,大部分 PREEMPT_RT 核心机制已进入主线,但仍有少量关键配置(如 PREEMPT_RT_FULL)需要额外补丁。
4.2 主要特性:PREEMPT_RT 如何实现硬实时?
PREEMPT_RT 补丁通过以下四大核心机制,将内核的不可抢占区域压缩到极致:
| 特性 | 实现方式 | 对调度延迟的影响 |
|---|---|---|
| 完全内核抢占 | 将自旋锁替换为可睡眠的 rt_mutex,允许高优先级任务在临界区中抢占低优先级任务。 |
消除自旋锁导致的忙等,降低优先级反转概率。 |
| 中断线程化 | 将硬件中断处理程序(ISR)下半部改为内核线程,受调度器管理,可被抢占。 | 中断不再直接抢占任务,减少中断延迟对实时任务的冲击。 |
| 高精度定时器 | 使用 hrtimer 替代传统时间轮,提供纳秒级定时精度。 |
定时器到期延迟从微秒级降至纳秒级,提升 Cyclictest 测量精度。 |
| RCU 抢占优化 | 引入 SRCU(Sleepable RCU)和 RCU_NOCB,允许 RCU 读端临界区被抢占。 |
消除 RCU 导致的不可抢占窗口,减少延迟抖动。 |
实践提示: 在配置实时内核时,务必确认
CONFIG_PREEMPT_RT_FULL=y 已启用。如果仅使用 CONFIG_PREEMPT=y(低延迟桌面内核),无法获得硬实时保障。
下图展示了 PREEMPT_RT 补丁对内核抢占模型的重构:
4.3 内核配置选项:如何启用 PREEMPT_RT?
在编译实时内核时,需要在内核配置菜单中启用以下关键选项。使用 make menuconfig 或 make xconfig 进行配置:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
CONFIG_PREEMPT_RT_FULL |
y | 启用完全实时抢占,这是 PREEMPT_RT 的核心开关。 |
CONFIG_HIGH_RES_TIMERS |
y | 启用高精度定时器(hrtimer),提供纳秒级定时精度。 |
CONFIG_NO_HZ_FULL |
y | 全无滴答模式,减少周期性定时器中断对实时任务的干扰。 |
CONFIG_IRQ_FORCED_THREADING |
y | 强制所有中断线程化,避免中断直接抢占实时任务。 |
CONFIG_RCU_NOCB_CPU |
y | 将 RCU 回调卸载到专用 CPU,减少 RCU 对实时 CPU 的干扰。 |
CONFIG_CPUSETS |
y | 支持 CPU 隔离,将实时任务绑定到专用 CPU 核心。 |
配置陷阱: 不要同时启用
CONFIG_PREEMPT_RT_FULL 和 CONFIG_PREEMPT(低延迟桌面模式)。两者互斥,且 CONFIG_PREEMPT 无法提供硬实时保障。在配置时,选择“Fully Preemptible Kernel (RT)”即可自动禁用冲突选项。
以下是一个典型的实时内核配置片段(.config 文件):
# 实时抢占核心
CONFIG_PREEMPT_RT_FULL=y
CONFIG_PREEMPT_RT=y
# 定时器与中断
CONFIG_HIGH_RES_TIMERS=y
CONFIG_NO_HZ_FULL=y
CONFIG_IRQ_FORCED_THREADING=y
# RCU 优化
CONFIG_RCU_NOCB_CPU=y
CONFIG_RCU_NOCB_CPU_DEFAULT_ALL=y
# CPU 隔离与调度
CONFIG_CPUSETS=y
CONFIG_SCHED_AUTOGROUP=n
CONFIG_RT_GROUP_SCHED=y
4.4 与主线内核的关系:补丁的演进与合入状态
PREEMPT_RT 补丁与主线内核的关系经历了三个阶段:
- 独立补丁阶段(Linux 2.6 ~ 4.x): PREEMPT_RT 作为外部补丁集维护,需要手动打补丁并重新编译内核。补丁集包含数百个补丁,涉及调度器、中断、锁、定时器等多个子系统。
- 逐步合入阶段(Linux 5.x ~ 6.x): 内核社区逐步将 PREEMPT_RT 的核心机制合入主线。例如,
rt_mutex、hrtimer、中断线程化框架等已成为主线内核的标准功能。 - 接近完全合入(Linux 6.x 及以后): 截至 Linux 6.12,PREEMPT_RT 补丁集已大幅缩减。主线内核已包含
PREEMPT_RT_FULL配置选项,但部分架构(如 ARM32、RISC-V)和驱动仍需要少量补丁。
下图展示了 PREEMPT_RT 补丁与主线内核的合入演进:
实用建议: 对于生产环境,建议使用
linux-rt 或 linux-rt-devel 分支的内核源码,这些分支由 PREEMPT_RT 维护者直接管理,补丁集与主线版本对齐。避免使用过旧的独立补丁集(如 4.x 时代的补丁),它们可能无法与现代硬件兼容。
4.5 验证 PREEMPT_RT 是否生效
在系统启动后,可以通过以下方法验证 PREEMPT_RT 补丁是否已正确启用:
- 检查内核抢占模式: 运行
cat /sys/kernel/realtime,如果输出1,表示 PREEMPT_RT 已启用。 - 查看内核配置: 运行
zcat /proc/config.gz | grep PREEMPT_RT,确认CONFIG_PREEMPT_RT_FULL=y。 - 检查中断线程化: 运行
ps -eo pid,comm,prio | grep irq,如果看到irq/xxx形式的线程,说明中断已被线程化。 - 运行 Cyclictest: 运行
cyclictest -t1 -p99 -i1000 -l10000,观察最大延迟是否在 50µs 以内(取决于硬件)。
验证示例:
$ cat /sys/kernel/realtime
1
$ zcat /proc/config.gz | grep PREEMPT_RT
CONFIG_PREEMPT_RT_FULL=y
CONFIG_PREEMPT_RT=y
$ ps -eo pid,comm,prio | grep irq
123 irq/50-eth0 50
456 irq/51-sata 50
$ cyclictest -t1 -p99 -i1000 -l10000
# /dev/cpu_dma_latency set to 0us
policy: fifo: loadavg: 0.00 0.01 0.00
T: 0 (12345) P:99 I:1000 C: 10000 Min: 2 Act: 5 Avg: 4 Max: 18
通过以上步骤,你可以确认系统已运行在 PREEMPT_RT 实时内核上,为后续的 Cyclictest 延迟测试和调度分析奠定基础。