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 补丁对内核抢占模型的重构:

PREEMPT_RT 内核抢占模型对比 主线内核(非实时) 用户态任务(可抢占) 系统调用 / 内核路径 (不可抢占,持有自旋锁) 中断处理(ISR) (直接抢占所有任务) RCU 读端临界区 (不可抢占) 延迟窗口:50µs ~ 200µs+ PREEMPT_RT 内核(硬实时) 用户态实时任务(可抢占) 内核路径(可抢占) (rt_mutex 替代自旋锁) 中断线程化(irq thread) (受调度器管理,可被抢占) RCU 读端临界区(可抢占) (SRCU / RCU_NOCB) 延迟窗口:5µs ~ 20µs

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 补丁与主线内核的关系经历了三个阶段:

  1. 独立补丁阶段(Linux 2.6 ~ 4.x): PREEMPT_RT 作为外部补丁集维护,需要手动打补丁并重新编译内核。补丁集包含数百个补丁,涉及调度器、中断、锁、定时器等多个子系统。
  2. 逐步合入阶段(Linux 5.x ~ 6.x): 内核社区逐步将 PREEMPT_RT 的核心机制合入主线。例如,rt_mutex、hrtimer、中断线程化框架等已成为主线内核的标准功能。
  3. 接近完全合入(Linux 6.x 及以后): 截至 Linux 6.12,PREEMPT_RT 补丁集已大幅缩减。主线内核已包含 PREEMPT_RT_FULL 配置选项,但部分架构(如 ARM32、RISC-V)和驱动仍需要少量补丁。

下图展示了 PREEMPT_RT 补丁与主线内核的合入演进:

PREEMPT_RT 补丁合入主线演进 独立补丁阶段 Linux 2.6 ~ 4.x 数百个补丁 手动打补丁编译 逐步合入阶段 Linux 5.x ~ 6.x rt_mutex / hrtimer 中断线程化合入主线 接近完全合入 Linux 6.x+ PREEMPT_RT_FULL 少量架构补丁 当前状态:主线内核已包含 PREEMPT_RT_FULL 配置,但部分驱动和架构仍需补丁。 建议使用 RT 内核源码树(如 linux-rt-devel)获取最新补丁。
实用建议: 对于生产环境,建议使用 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 延迟测试和调度分析奠定基础。