第4章 PREEMPT_RT补丁实战:在RK3568上打补丁、编译、验证实时性

好,咱们进入实战环节。

前面讲了那么多理论,什么抢占点、中断线程化、自旋锁替换……说实话,光听不练容易忘。我自己的经验是,亲手在板子上跑一遍,比看十遍文档都管用

这一章,我们就拿RK3568开刀。目标很明确:
给标准Linux内核打上PREEMPT_RT补丁,编译出一个实时内核,然后验证它的实时性到底提升了多少。

4.1 准备工作:环境与源码

先说说我习惯的开发环境。

  • 主机:Ubuntu 20.04 LTS(22.04也行,但20.04我踩坑最少)
  • 交叉编译器:gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu
  • 目标板:RK3568 EVB(或者你自己画的板子,只要芯片是RK3568)
  • 内核版本:Linux 5.10.y(我选这个是因为它和PREEMPT_RT补丁的匹配最稳定)
注意: 内核版本和补丁版本必须严格对应。比如5.10.100的内核,就要找5.10.100-rtXX的补丁。版本号差一位,打补丁时就会报一堆冲突。我曾经因为没仔细看版本号,浪费了一整个下午。

下载源码:

# 下载主线内核
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux
git checkout v5.10.100

# 下载对应的PREEMPT_RT补丁
wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.10/older/patch-5.10.100-rt56.patch.gz
gunzip patch-5.10.100-rt56.patch.gz

4.2 打补丁:这一步最考验耐心

打补丁本身不复杂,但细节决定成败

# 进入内核源码目录
cd linux

# 应用补丁
zcat ../patch-5.10.100-rt56.patch.gz | patch -p1

如果运气好,你会看到一堆 patching file ... 的提示,没有报错。但说实话,我很少有这么顺利的时候。

我的经验: 如果补丁打不上,先检查内核源码是不是干净的。用 git status 看看有没有未提交的修改。我建议每次打补丁前,都 git reset --hard 一下,确保源码是原版。

打完补丁后,确认一下:

# 查看内核是否已经支持PREEMPT_RT
grep "PREEMPT_RT" arch/arm64/configs/defconfig

如果没有任何输出,说明补丁没打进去。嗯,这时候别慌,检查一下补丁文件路径和内核版本。

4.3 配置内核:开启实时选项

补丁打好了,接下来就是配置内核。我个人习惯用 menuconfig,图形界面不容易漏选项。

make ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- menuconfig

进入菜单后,按以下路径开启:

  1. General setupPreemption Model → 选择 Fully Preemptible Kernel (Real-Time)
  2. Kernel FeaturesTimer frequency → 设置为 1000 Hz(实时性要求高的话,可以试试1000 Hz)
  3. Real-time sub-system → 确保里面的选项都是开启状态

关键点: 选完 Fully Preemptible Kernel 后,系统会自动把 CONFIG_PREEMPT_RT 设为 y。你可以在 .config 文件里确认:

grep "CONFIG_PREEMPT_RT" .config
CONFIG_PREEMPT_RT=y

另外,我建议把 CONFIG_HZ_1000 也打开。频率越高,调度器的响应越快,但也会带来一点额外的开销。对于工业控制来说,1000 Hz是标配。

4.4 编译内核:耐心等待

配置好了,开始编译。RK3568是ARM64架构,所以要用交叉编译器。

# 设置环境变量
export ARCH=arm64
export CROSS_COMPILE=aarch64-none-linux-gnu-

# 编译内核和设备树
make -j$(nproc) Image dtbs

# 编译模块
make -j$(nproc) modules

# 安装模块到临时目录
make INSTALL_MOD_PATH=./modules_install modules_install

第一次编译大概要20-30分钟,取决于你的电脑性能。我建议用 -j$(nproc) 充分利用多核。

踩坑提醒: 编译时如果报 undefined reference to `xxx' 之类的链接错误,多半是工具链版本不对。我遇到过好几次,换回gcc 10.3就好了。

4.5 部署到RK3568

编译完成后,把内核镜像、设备树和模块拷贝到板子上。

# 假设SD卡挂载在 /mnt/sd
cp arch/arm64/boot/Image /mnt/sd/boot/
cp arch/arm64/boot/dts/rockchip/rk3568-evb.dtb /mnt/sd/boot/
cp -r modules_install/lib/modules/* /mnt/sd/lib/modules/

# 更新extlinux.conf或者boot.scr,指向新的内核
# 我习惯用extlinux,配置如下:
cat /mnt/sd/boot/extlinux/extlinux.conf
LABEL rt-kernel
    KERNEL /boot/Image
    FDT /boot/rk3568-evb.dtb
    APPEND root=/dev/mmcblk0p2 rootwait console=ttyFIQ0,1500000

重启板子,用 uname -a 确认内核版本:

# uname -a
Linux rk3568 5.10.100-rt56 #1 SMP PREEMPT_RT ...

看到 PREEMPT_RT 字样,说明补丁生效了。

4.6 验证实时性:用cyclictest说话

内核跑起来了,但实时性到底怎么样?不能光靠感觉。我一般用 cyclictest 来测量。

先安装rt-tests:

# 在板子上执行
apt-get install rt-tests
# 或者交叉编译后拷贝过去

运行测试:

# 创建一个实时线程,优先级80,运行10000次
cyclictest -t1 -p80 -n -i1000 -l10000 -m

参数说明:

  • -t1:1个测试线程
  • -p80:优先级80
  • -n:使用nanosleep
  • -i1000:间隔1000微秒(1ms)
  • -l10000:循环10000次
  • -m:锁定内存,防止页面交换

输出结果类似这样:

T: 0 (12345) P: 80 I: 1000 C: 10000 Min: 2 Max: 45 Avg: 4

解读一下:

  • Min:最小延迟2微秒
  • Max:最大延迟45微秒
  • Avg:平均延迟4微秒

对比一下标准内核: 同样的测试,在标准 CONFIG_PREEMPT 内核上,最大延迟通常在200-500微秒左右。而PREEMPT_RT内核能控制在50微秒以内。这个差距,在工业控制中就是「能用」和「不能用」的区别。

4.7 压力测试:看看极限在哪里

单线程测试通过后,我还会做压力测试。毕竟工业现场不可能只有一个任务在跑。

# 同时跑4个实时线程,优先级80、70、60、50
cyclictest -t4 -p80 -p70 -p60 -p50 -n -i1000 -l100000 -m

同时,在后台跑一些干扰任务:

# 开几个CPU密集型任务
stress --cpu 4 --io 2 --vm 2 --vm-bytes 128M --timeout 60s

这时候再看cyclictest的输出,如果最大延迟仍然在100微秒以内,说明你的实时内核是合格的。

我的经验: 如果压力测试下延迟飙升,先检查中断亲和性。把网卡、USB等中断绑定到非实时核上,能有效降低抖动。具体做法是修改 /proc/irq/xxx/smp_affinity

4.8 常见问题与避坑

最后,分享几个我踩过的坑:

  • 补丁打不上:99%的原因是内核版本和补丁版本不匹配。用 patch -p1 --dry-run 先模拟一下。
  • 编译报错:检查工具链版本。gcc 11以上对某些内核代码有兼容性问题。
  • 启动卡住:检查设备树和内核配置。RK3568的某些外设驱动在RT内核下需要额外配置。
  • 延迟不稳定:检查电源管理。把CPU调频策略设为 performance,关闭动态调频。

好了,这一章的内容就是这些。从打补丁到验证,每一步我都尽量把细节讲清楚。你跟着做一遍,应该能跑出一个稳定的实时内核。

下一章,我们会深入RK3568的硬件定时器,看看怎么用它来实现微秒级的精准控制。