4、实时内核补丁(PREEMPT_RT)
在嵌入式 Linux 系统中,标准的 Linux 内核虽然提供了多种抢占模型,但在面对微秒级或亚毫秒级的硬实时任务时,其调度延迟和中断响应时间往往无法满足要求。PREEMPT_RT(Real-Time Linux)补丁集通过一系列内核机制改造,将 Linux 内核转变为完全可抢占的实时操作系统。本章将系统介绍 PREEMPT_RT 补丁的原理、获取方式、打补丁流程、配置选项以及实时性验证方法。
4.1 PREEMPT_RT 补丁介绍
PREEMPT_RT 补丁集由 Linux 基金会下的 Real-Time Linux(RTL)协作项目维护,其核心目标是将 Linux 内核改造为完全可抢占的实时操作系统。补丁主要从以下三个维度对内核进行深度改造:
| 改造维度 | 标准内核行为 | PREEMPT_RT 改造后 | 实时性收益 |
|---|---|---|---|
| 中断线程化 | 中断处理程序在关闭本地中断的上下文中执行,不可被抢占 | 中断处理程序变为内核线程(IRQ Thread),可被高优先级实时任务抢占 | 消除中断导致的优先级反转 |
| 自旋锁可抢占 | 自旋锁持有期间禁止内核抢占,导致优先级反转 | 自旋锁替换为 rt_mutex 或可睡眠锁(sleepable spinlock) | 锁持有时间不再阻塞高优先级任务 |
| 高精度定时器 | 定时器基于 jiffies,精度通常为 1~10 ms | 使用 hrtimer 框架,精度达到纳秒级 | 支持微秒级定时调度 |
4.2 补丁获取与打补丁
PREEMPT_RT 补丁以 .patch.xz 或 .patch.gz 格式发布,必须与对应版本的主线内核源码严格匹配。以下是获取与打补丁的标准流程。
4.2.1 补丁获取途径
- 官方仓库:
https://cdn.kernel.org/pub/linux/kernel/projects/rt/,按内核主版本号组织目录。 - 版本匹配规则: 补丁文件名格式为
patch-5.10.x-rtX.patch.xz,其中5.10.x必须与主线内核小版本号完全一致。 - 长期支持(LTS)版本: 推荐使用 LTS 内核(如 5.10、5.15、6.1)对应的 RT 补丁,社区维护周期长,稳定性高。
4.2.2 打补丁步骤
以下以 Linux 5.10.100 内核和对应的 PREEMPT_RT 补丁为例,展示完整的打补丁流程:
# 1. 下载主线内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar -xf linux-5.10.100.tar.xz
cd linux-5.10.100
# 2. 下载对应的 RT 补丁
wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.10/patch-5.10.100-rt57.patch.xz
# 3. 解压补丁
xz -d patch-5.10.100-rt57.patch.xz
# 4. 应用补丁(dry-run 先检查冲突)
patch -p1 --dry-run < patch-5.10.100-rt57.patch
# 5. 确认无冲突后正式打补丁
patch -p1 < patch-5.10.100-rt57.patch
# 6. 验证补丁是否成功
grep "PREEMPT_RT" kernel/Kconfig.preempt
# 输出应包含 "config PREEMPT_RT" 相关选项
patch 命令会报告大量 hunk 失败。此时必须下载与内核完全匹配的补丁版本,或使用 git am 方式管理补丁。
4.3 配置实时抢占选项
打补丁成功后,内核源码中会新增 PREEMPT_RT 相关的 Kconfig 选项。通过 make menuconfig 进入内核配置界面,按以下路径启用实时抢占:
Kernel Features
└> Preemption Model
└> (X) Fully Preemptible Kernel (Real-Time) [PREEMPT_RT]
选择 Fully Preemptible Kernel (Real-Time) 后,内核会自动启用以下关键实时特性:
| 配置选项 | 功能描述 | 推荐状态 |
|---|---|---|
CONFIG_PREEMPT_RT |
完全可抢占内核主开关 | 启用 |
CONFIG_HIGH_RES_TIMERS |
高精度定时器支持 | 启用(自动选中) |
CONFIG_PREEMPT_RT_FULL |
完整实时抢占特性集 | 启用 |
CONFIG_IRQ_FORCED_THREADING |
强制中断线程化 | 启用 |
CONFIG_RT_GROUP_SCHED |
实时任务组调度支持 | 按需启用 |
CONFIG_NO_HZ_FULL(全无滴答模式),因为该模式在实时场景下可能引入调度延迟抖动。使用 CONFIG_NO_HZ_IDLE 即可。
配置完成后,保存并退出,然后编译内核:
# 保存配置
make savedefconfig
cp defconfig .config
# 编译内核(以 ARM64 为例)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
# 编译内核模块
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_install INSTALL_MOD_PATH=/path/to/rootfs
4.4 验证实时性
内核启动后,需要从系统状态、调度延迟和中断响应三个层面验证实时性是否生效。
4.4.1 系统状态检查
通过 /proc 文件系统和内核日志确认 PREEMPT_RT 已启用:
# 检查内核抢占模型
cat /proc/sys/kernel/sched_rt_runtime_us
# 输出应为 950000(表示实时任务可占用 95% CPU 时间)
# 检查内核日志
dmesg | grep -i "preempt"
# 应输出类似: "Linux version 5.10.100-rt57 ... PREEMPT_RT"
# 检查中断线程化
ps -eo pid,comm | grep irq
# 应看到类似 [irq/50-eth0] 的中断线程
4.4.2 调度延迟测试
使用 cyclictest(来自 rt-tests 工具集)测量内核调度延迟:
# 安装 rt-tests(以 Buildroot 或 Yocto 为例)
# 目标板上运行
cyclictest -t1 -p 99 -n -i 1000 -l 100000
# 输出示例:
# T: 0 (1234) P: 99 I: 1000 C: 100000 Min: 2 Act: 5 Avg: 4 Max: 18
| 指标 | 含义 | 典型值(PREEMPT_RT) | 典型值(标准内核) |
|---|---|---|---|
| Min | 最小延迟(µs) | 1~3 | 2~10 |
| Max | 最大延迟(µs) | 10~50 | 100~5000 |
| Avg | 平均延迟(µs) | 3~10 | 10~100 |
Max 延迟超过 100 µs 且系统负载较低,说明 PREEMPT_RT 配置可能未生效,或存在驱动未适配中断线程化。此时应检查 dmesg 中是否有 "IRQ handler type mismatch" 等警告。
4.4.3 中断响应延迟测试
使用 hackbench 或 stress-ng 施加系统负载,同时运行 cyclictest 观察延迟变化:
# 在另一个终端施加 CPU 和内存压力
stress-ng --cpu 4 --io 2 --vm 2 --vm-bytes 128M --timeout 60s
# 同时运行 cyclictest
cyclictest -t5 -p 99 -n -i 1000 -l 60000 -m
# 观察最大延迟是否仍在可接受范围内(通常 < 200 µs)
threadirqs 内核启动参数强制线程化。