跳到主要内容

实时内核

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_FIFOSCHED_RR
  • 优先级要合理。Linux RT 优先级通常是 19999 最高,不建议所有线程都拉到很高。
  • 有明确延迟要求时,把实时线程绑定到合适的 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, &param);

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

结果里重点看 MaxMinAvg 可以参考,但真正影响控制周期的通常是最大延迟。

测试前也建议看一下 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 通常是更舒服的第一选择。