17、实时系统配置:PREEMPT_RT内核编译、实时线程优先级设置、延迟测试与抖动分析
做Jetson实时系统配置,说白了就是让Linux从“尽力而为”变成“说到做到”。
我最早接触这个需求,是在一个无人机飞控项目上。当时用的普通内核,电机控制线程偶尔被调度走,结果就是——飞机在空中抖了一下。嗯,就那么一下,差点炸机。从那以后,我对实时性就特别敏感。
17.1 为什么需要PREEMPT_RT?
标准Linux内核是个“老好人”。它允许一个低优先级的任务长时间占用CPU,高优先级的实时任务只能等着。这在桌面系统上没问题,但在工业控制、机器人、自动驾驶上,这就是灾难。
PREEMPT_RT补丁做了什么?它把内核的大部分自旋锁换成了可抢占的互斥锁。中断处理也变成了线程。说白了,就是让高优先级任务能“插队”。
核心变化:
- 中断线程化:中断处理程序变成内核线程,可以被调度
- 自旋锁替换:大部分spin_lock变成rt_mutex
- 优先级继承:防止优先级反转
- 高精度定时器:HRTIMER默认开启
17.2 编译PREEMPT_RT内核(Jetson平台)
Jetson平台编译内核,和普通x86不太一样。你需要用NVIDIA提供的交叉编译工具链。我习惯在Ubuntu主机上交叉编译,然后刷到Jetson上。
17.2.1 获取源码
# 下载L4T内核源码(以Jetson Orin为例)
export JETSON_MODEL=orin
export L4T_VERSION=35.3.1
# 从NVIDIA官网下载内核源码包
wget https://developer.nvidia.com/embedded/l4t/r35_release_v3.1/sources/public_sources.tbz2
# 解压
tar -xjf public_sources.tbz2
cd Linux_for_Tegra/source
tar -xjf kernel_src.tbz2
# 进入内核目录
cd kernel/kernel-5.10
17.2.2 打PREEMPT_RT补丁
# 下载对应内核版本的PREEMPT_RT补丁
# 内核版本5.10.104-rt60
wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.10/patch-5.10.104-rt60.patch.xz
# 解压并打补丁
xzcat patch-5.10.104-rt60.patch.xz | patch -p1
# 检查是否有reject文件
find . -name "*.rej"
注意:我曾经遇到过补丁打不上,原因是NVIDIA对内核做了修改。你需要手动解决冲突。建议先备份原始源码。
17.2.3 配置内核
# 使用NVIDIA默认配置
make ARCH=arm64 tegra_defconfig
# 开启PREEMPT_RT
make ARCH=arm64 menuconfig
# 进入以下路径:
# General setup --->
# Preemption Model (Fully Preemptible Kernel (Real-Time)) --->
# (X) Fully Preemptible Kernel (Real-Time)
# 确认以下选项已开启:
# Kernel Features --->
# [*] High Resolution Timer Support
# [*] Tickless System (Dynamic Ticks)
# Timer frequency ---> 1000 Hz
17.2.4 编译与安装
# 设置交叉编译环境
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
# 编译内核和模块
make -j$(nproc) Image
make -j$(nproc) modules
# 编译设备树
make -j$(nproc) dtbs
# 安装模块到临时目录
make modules_install INSTALL_MOD_PATH=./modules_install
# 打包内核
cd arch/arm64/boot
# 生成Image文件,用于刷机或替换
我个人习惯把编译好的内核放到SD卡上,通过修改extlinux.conf来启动新内核。这样万一有问题,还能切回原内核。
17.3 实时线程优先级设置
内核准备好了,接下来就是应用层的事。实时线程的优先级设置,我用的是POSIX的sched_setscheduler接口。
17.3.1 设置SCHED_FIFO优先级
#include <sched.h>
#include <pthread.h>
void set_realtime_priority(pthread_t thread, int priority) {
struct sched_param param;
param.sched_priority = priority;
// 使用SCHED_FIFO实时调度策略
int ret = pthread_setschedparam(thread, SCHED_FIFO, ¶m);
if (ret != 0) {
// 处理错误,通常是没有CAP_SYS_NICE权限
perror("pthread_setschedparam");
}
}
// 使用示例
int main() {
pthread_t control_thread;
// 创建线程...
set_realtime_priority(control_thread, 80); // 优先级范围1-99
// ...
}
经验之谈:优先级不是越高越好。我曾经把控制线程设成99,结果它把网卡中断都抢占了,导致网络丢包。建议控制类线程设80-90,数据采集设70-80,普通任务设50以下。
17.3.2 内存锁定
实时线程还有一个坑——缺页中断。如果线程访问的内存页不在物理内存中,会发生缺页,导致延迟飙升。解决办法是锁定内存。
#include <sys/mman.h>
void lock_memory() {
// 锁定当前和未来的所有内存页
int ret = mlockall(MCL_CURRENT | MCL_FUTURE);
if (ret != 0) {
perror("mlockall");
}
}
// 在main函数开头调用
int main() {
lock_memory();
// 预分配并初始化所有实时线程需要的内存
// ...
set_realtime_priority(control_thread, 80);
// 进入实时循环
}
17.4 延迟测试与抖动分析
配置完了,怎么知道效果好不好?测延迟。我常用的工具是cyclictest,它是rt-tests套件的一部分。
17.4.1 安装rt-tests
# 在Jetson上编译安装
git clone git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git
cd rt-tests
make
sudo make install
# 或者直接apt安装(如果源里有)
sudo apt install rt-tests
17.4.2 运行cyclictest
# 基本测试:1个线程,间隔1000微秒,运行60秒
sudo cyclictest -t 1 -p 80 -i 1000 -l 60000 -m
# 参数说明:
# -t 1 : 1个测试线程
# -p 80 : 优先级80
# -i 1000 : 间隔1000微秒(1ms)
# -l 60000: 循环60000次(60秒)
# -m : 锁定内存
# 多线程测试:4个线程,不同优先级
sudo cyclictest -t 4 -p 80 -i 1000 -l 60000 -m -d 0
17.4.3 结果解读
cyclictest会输出统计信息。我一般关注三个指标:最小延迟、平均延迟、最大延迟。
# 输出示例
T: 0 (12345) P: 80 I: 1000 C: 60000 Min: 2 Act: 3 Avg: 4 Max: 18
# 含义:
# T: 线程ID
# P: 优先级
# I: 间隔(微秒)
# C: 计数
# Min: 最小延迟(微秒)
# Act: 当前延迟
# Avg: 平均延迟
# Max: 最大延迟(关键指标)
抖动分析:最大延迟和平均延迟的差值,就是抖动。差值越小,系统越稳定。我见过最好的Jetson Orin配置,最大延迟能控制在30微秒以内。如果超过100微秒,就要检查配置了。
17.4.4 绘制延迟分布图
只看最大值不够,我习惯把延迟数据导出来,画个分布图。这样能看出延迟的“尾巴”有多长。
# 导出延迟数据到文件
sudo cyclictest -t 1 -p 80 -i 1000 -l 60000 -m -h 100 -v > latency_data.txt
# -h 100 : 直方图,100个桶
# -v : 详细输出
# 然后用Python或Excel画图
# 我一般用Python的matplotlib,几行代码搞定
17.5 知识体系图
下面这张图,是我自己总结的实时系统配置流程。从内核到应用,每一步都影响最终延迟。
17.6 常见问题与避坑
做实时系统配置,坑不少。我踩过的,列几个出来:
- 优先级反转:低优先级任务持有锁,高优先级任务等锁。解决方案是使用优先级继承协议(pthread_mutexattr_setprotocol)。
- 中断风暴:某个设备产生大量中断,抢占CPU。我遇到过USB控制器的问题,后来把中断亲和性绑到非实时核上解决了。
- 动态调频:CPU频率缩放会导致延迟不稳定。建议设置performance governor。
- DMA缓冲区:DMA操作可能绕过CPU缓存,导致一致性开销。用dma_alloc_coherent分配一致性内存。
我的调试习惯:先用cyclictest跑个基准,然后逐步调整配置。每次只改一个参数,再跑测试。这样能清楚知道哪个改动有效果。
17.7 总结
实时系统配置,不是装个补丁就完事。内核、系统、应用三层都要调。PREEMPT_RT是基础,但真正的延迟控制,在于你怎么用。
我见过有人把优先级设到99,结果系统卡死。也见过有人用普通内核,靠精心设计的用户态调度,达到微秒级延迟。工具是死的,人是活的。
最后说一句:测试,测试,再测试。没有cyclictest数据,别说你的系统是实时的。
公众号:蓝海资料掘金营,微信deep3321