实时内核
Neardi Pi 3 使用 RK3576 Linux 6.1 SDK。如果项目对实时响应有要求,可以使用 SDK 里提供的实时性能补丁。补丁目录在:
docs/Patches/Real-Time-Performance/
├── PREEMPT_RT/
└── XENOMAI/
一般项目建议先用 PREEMPT_RT。它还是标准 Linux 的使用方式,维护成本低一些。Xenomai 更适合应用本身已经按 Xenomai API 开发,或者项目需要更严格实时框架的场景。
基础内核版本
当前 RK3576 Linux 6.1 SDK 的 kernel 版本是:
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 118
打补丁前,建议先确认 kernel 仓库是干净的,并且基于 SDK 发布分支:
cd kernel-6.1
git status
git log -n1
这里使用的基础分支是:
neardi-rk3576-linux-6.1-stan-rkr6.1-preempt-rt
合入 PREEMPT_RT 补丁
进入 kernel 目录后,按顺序合入 PREEMPT_RT 补丁:
cd kernel-6.1
git am ../docs/Patches/Real-Time-Performance/PREEMPT_RT/kernel-6.1/kernel-6.1.118/000*.patch
补丁打完后,可以用 git log -n6 看一下,顶部应该能看到 PREEMPT_RT 相关提交,例如:
patch-6.1.99-rt36 on rockckip base 5c295c763974
sched/isolation: remove HK_FLAG_TICK for nohz_full for PREEMPT_RT
mm: Kconfig: remove selection of MIGRATION for CMA to disable MIGRATION
ARM: configs: add rockchip_rt.config for PREEMPT_RT
arm64: configs: optimize latency for PREEMPT_RT
打开 Pi 3 的 RT 配置
Neardi Pi 3 对应 rk3576_neardi_pi3_mesa 板级配置。kernel 补丁合入后,还需要在板级 defconfig 里打开 rockchip_rt.config:
diff --git a/.chips/rk3576/rockchip_rk3576_neardi_lb200_defconfig b/.chips/rk3576/rockchip_rk3576_neardi_lb200_defconfig
--- a/.chips/rk3576/rockchip_rk3576_neardi_pi3_mesa_defconfig
+++ b/.chips/rk3576/rockchip_rk3576_neardi_pi3_mesa_defconfig
@@ -1,4 +1,4 @@
RK_UBOOT_SPL=y
RK_KERNEL_DTS_NAME="rk3576-neardi-pi3-linux"
RK_USE_FIT_IMG=y
-RK_KERNEL_CFG_FRAGMENTS="rk3576_neardi_pi3.config"
+RK_KERNEL_CFG_FRAGMENTS="rk3576_neardi_pi3.config rockchip_rt.config"
这样会保留 Neardi Pi 3 原来的 kernel 配置,再叠加 Rockchip 的实时内核配置片段。
编译和确认
后续按 SDK 正常流程编译固件即可。板子启动后,可以用下面命令确认内核版本和抢占配置:
uname -a
zcat /proc/config.gz | grep PREEMPT
如果能看到 CONFIG_PREEMPT_RT,说明 RT 补丁和配置已经生��效。
应用使用建议
实时内核准备好以后,应用也要配合使用。实时内核能降低调度延迟,但不会自动把所有应用线程都变成实时线程。
比较常见的做法是:
- 只把真正有实时要求的线程设为实时线程。
- 实时线程使用
SCHED_FIFO或SCHED_RR。 - 优先级要合理。Linux RT 优先级通常是
1到99,99最高,不建议所有线程都拉到很高。 - 有明确延迟要求时,把实时线程绑定到合适的 CPU 核心。
- 日志、文件读写、网络收发、复杂计算这类耗时操作,不要放在实时线程里。
临时验证现有程序时,可以先用 chrt 启动:
sudo chrt -f 50 ./your_app
正式项目里,更建议在应用线程里设置调度策略:
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
static void *rt_thread(void *arg)
{
/* 这里只放实时性要求高的逻辑。 */
return NULL;
}
int main(void)
{
pthread_t thread;
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
param.sched_priority = 50;
pthread_attr_setschedparam(&attr, ¶m);
if (pthread_create(&thread, &attr, rt_thread, NULL) != 0) {
perror("pthread_create");
return 1;
}
pthread_join(thread, NULL);
return 0;
}
如果线程需要固定跑在某个 CPU 上,可以在应用里绑核:
#include <pthread.h>
#include <sched.h>
void bind_current_thread_to_cpu(int cpu)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
如果是 UART、CAN 这类中断比较敏感的场景,也要看中断绑核。思路很简单:设备中断放到负载较低的核心,应用实时线程放到另一个合适的核心,尽量避免互相打断。Rockchip 的串口延迟排查案例里,就是把 UART 中断和应用 RT 线程分开,减少相互影响。
cat /proc/interrupts
cat /proc/irq/<irq-number>/smp_affinity
延迟测试
可以用 cyclictest 先看调度延迟。建议先跑空载,再跑压力场景。
空载测试:
sudo cyclictest -m -n -t 4 -p 99 -i 1000 -D 1h
压力测试:
stress-ng -c 4 --io 2 --vm 1 --vm-bytes 4M --timeout 1h
sudo cyclictest -m -n -t 4 -p 99 -i 1000 -D 1h
结果里重点看 Max。Min 和 Avg 可以参考,但真正影响控制周期的通常是最大延迟。
测试前也建议看一下 CPU 和 DDR 频率。频率太低或者省电策略太激进,可能会把延迟拉大:
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq
cat /sys/class/devfreq/dmc/governor
cat /sys/class/devfreq/dmc/cur_freq
排查思路
如果实测延迟还是偏大,可以按下面顺序排查:
- 先确认外部设备是不是按预期周期发数据。必要时用逻辑分析仪或示波器看。
- 确认有没有丢帧。可以在数据帧里加序号。
- 看设备中断本身有没有延迟。
- 看中断、内核 worker、应用 RT 线程是不是都挤在同一个 CPU 上。
- 看是不是太多应用线程都设成了 RT 优先级。
- 看 RT 线程里有没有阻塞操作或很慢的日志。
- 看 CPU / DDR 频率是否偏低。
- 问题出现时抓 ftrace 数据再分析。
抓 trace 时,kernel 需要打开 scheduler、IRQ、preempt 等 tracing 配置。抓到 trace.txt 后,可以用 Chrome tracing viewer 打开分析。
Xenomai 说明
Xenomai 补丁放在:
docs/Patches/Real-Time-Performance/XENOMAI/
Xenomai 建议在应用侧也已经准备好时再使用。相比 PREEMPT_RT,它通常会涉及应用运行方式、测试方式和部署流程的调整。如果只是希望普通 Linux 应用获得更低调度延迟,PREEMPT_RT 通常是更舒服的第一选择。