第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补丁的匹配最稳定)
下载源码:
# 下载主线内核
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
进入菜单后,按以下路径开启:
- General setup → Preemption Model → 选择 Fully Preemptible Kernel (Real-Time)
- Kernel Features → Timer frequency → 设置为 1000 Hz(实时性要求高的话,可以试试1000 Hz)
- 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微秒以内,说明你的实时内核是合格的。
/proc/irq/xxx/smp_affinity。
4.8 常见问题与避坑
最后,分享几个我踩过的坑:
- 补丁打不上:99%的原因是内核版本和补丁版本不匹配。用
patch -p1 --dry-run先模拟一下。 - 编译报错:检查工具链版本。gcc 11以上对某些内核代码有兼容性问题。
- 启动卡住:检查设备树和内核配置。RK3568的某些外设驱动在RT内核下需要额外配置。
- 延迟不稳定:检查电源管理。把CPU调频策略设为
performance,关闭动态调频。
好了,这一章的内容就是这些。从打补丁到验证,每一步我都尽量把细节讲清楚。你跟着做一遍,应该能跑出一个稳定的实时内核。
下一章,我们会深入RK3568的硬件定时器,看看怎么用它来实现微秒级的精准控制。