本文档系统阐述 ATOM01 部署框架的实时性能保障机制,涵盖从底层 PREEMPT_RT 内核安装、用户权限配置,到推理节点线程优先级调度、DDS 通信优化、内存锁定与 CPU 亲和性绑定的全栈优化策略。理解这些机制有助于排查实时性相关故障,并为进一步的性能调优提供依据。
Sources: README_CN.md
实时内核与系统级配置
PREEMPT_RT 实时内核
机器人控制对时延抖动极为敏感,普通 Linux 内核的调度不确定性可能导致控制周期漂移。ATOM01 部署框架要求主控运行经 PREEMPT_RT 补丁改造的实时内核。
框架针对两种主控提供了不同的内核部署路径。Orange Pi 5 Plus 运行 Ubuntu 22.04 与 5.10 内核,需手动安装仓库 assets 目录下预编译的实时内核 deb 包;RDK X5 运行 Ubuntu 22.04 与 6.1.83 内核,官方直接提供已预装实时内核的系统镜像,无需额外操作。
# Orange Pi 5 Plus 执行以下命令安装实时内核
cd assets
sudo apt install ./*.deb
cd ..
安装完成后重启设备,新内核即生效。
Sources: README_CN.md
用户实时权限与内存锁定
实时调度策略 SCHED_FIFO 与普通线程调度不同,需要显式为用户分配权限,否则 pthread_setschedparam 调用将因权限不足而失败。同时,为避免控制过程中进程内存页被交换到磁盘引发不可预期的延迟,需解除内存锁定限制。
编辑 /etc/security/limits.conf,在末尾追加以下两行,将用户名替换为实际登录用户(Orange Pi 5 Plus 默认为 orangepi,RDK X5 默认为 sunrise):
# Allow user to set real-time priorities
sunrise - rtprio 98
sunrise - memlock unlimited
重启后通过 ulimit -r 验证,输出为 98 即代表配置成功。在推理节点入口的 main 函数中,程序启动时会立即调用 mlockall(MCL_CURRENT | MCL_FUTURE) 将当前及未来分配的全部内存锁定在物理 RAM 中;若调用失败,日志将输出警告但不中断启动。
Sources: README_CN.md, inference_node.cpp
线程级实时调度架构
推理节点与底层驱动在启动时会将关键线程提升至 SCHED_FIFO 实时调度类,并赋予不同优先级,确保控制回路、推理计算、通信收发在 CPU 资源竞争中具备确定的抢占顺序。
以下 Mermaid 图展示了系统中关键实时线程的优先级层级与职责划分:
graph TD
subgraph 推理节点 ["推理节点 (inference_node)"]
A[main 线程<br/>SCHED_FIFO 50<br/>ROS2 Executor] --> B[control 线程<br/>SCHED_FIFO 70<br/>250Hz 动作下发]
A --> C[inference 线程<br/>SCHED_FIFO 70<br/>50Hz ONNX 推理]
A --> D[ThreadPool<br/>SCHED_FIFO 70<br/>并行电机读写]
end
subgraph 电机驱动 ["电机驱动 (motors)"]
E[CAN/CANFD RX 线程<br/>SCHED_FIFO 80<br/>总线接收]
F[CAN/CANFD TX 线程<br/>SCHED_FIFO 80<br/>总线发送]
end
subgraph IMU驱动 ["IMU 驱动 (imu)"]
G[Serial RX 线程<br/>SCHED_FIFO 80<br/>串口接收]
H[CAN RX 线程<br/>SCHED_FIFO 80<br/>CAN 接收]
end
B --> E
B --> F
C --> D
D --> E
D --> F
数字越大代表优先级越高。硬件通信线程拥有最高优先级 80,确保 CAN/串口数据不会丢失;控制与推理线程次之,为 70;主线程负责 ROS2 事件循环,优先级为 50。若任一关键线程设置实时优先级失败,程序将记录致命错误并主动退出,避免在非标称模式下继续运行。
Sources: inference_node.cpp, socket_canfd.cpp, serial_port.cpp
CPU 亲和性绑定
在多核 ARM 主控上,除了优先级调度,框架还通过 CPU 亲和性将通信线程绑定到特定核心,减少缓存失效与核间迁移带来的抖动。电机驱动的 CAN/CANFD 收发线程采用基于接口号的自动分核策略:根据接口名末尾数字(如 can0、can1),从最后一个可用核心倒序分配,确保多路 CAN 分散到不同核心执行。
int total_cores = std::thread::hardware_concurrency();
int cpu_id = total_cores - 1 - port_num; // port_num 来自接口名末尾数字
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_id, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
该机制在 socket_canfd.cpp 与 socket_can.cpp 中均有实现,TX 与 RX 线程各自独立绑定。
Sources: socket_canfd.cpp, socket_canfd.cpp
DDS 通信层优化
ROS2 默认的 DDS 中间件在复杂网络环境下可能产生较高的发现延迟与序列化开销。ATOM01 采用 rmw_fastrtps_cpp 作为 RMW 实现,并通过自定义 XML 配置文件对传输层、内存策略与发现机制进行针对性裁剪。
混合传输策略
assets/rt_fastdds_profile.xml 配置了两类传输通道:
| 传输通道 | 类型 | 用途 | 关键参数 |
|---|---|---|---|
shm_transport |
共享内存 (SHM) | 本机节点间零拷贝通信 | segment_size 8MB,maxMessageSize 512KB |
udp_transport |
UDPv4 | 跨网络或回环兜底 | 非阻塞发送,send/receiveBuffer 212992,接口白名单限制 |
通过 useBuiltinTransports=false 关闭默认传输,仅启用上述自定义通道。对于同一主控内的推理节点与手柄节点,数据优先走共享内存,规避了内核协议栈拷贝;接口白名单将 UDP 限制在 127.0.0.1、192.168.13.1、192.168.127.10,减少无效组播报文。
Sources: rt_fastdds_profile.xml
内存与 QoS 策略
实时系统必须避免动态内存分配带来的非确定性延迟。Fast DDS 配置中所有历史缓存均采用 PREALLOCATED_WITH_REALLOC 策略,在初始预分配的基础上允许可控的重新分配,避免每次发布/订阅都触发堆分配。
发布端额外配置为 SYNCHRONOUS 同步发布模式,配合 BEST_EFFORT 可靠性与 VOLATILE 持久性。这种组合在局域网/本机场景下以最低延迟为首要目标,不追求跨网络的强可靠重传。data_sharing 设为 AUTOMATIC,使共享内存传输在兼容时自动生效。
Sources: rt_fastdds_profile.xml
启动脚本中的 DDS 验证
tools/start_robot.sh 不仅负责设置环境变量,还内建了一套 verify_dds_effectiveness 检查逻辑,在节点启动后自动验证:
- 环境变量是否传递正确(
RMW_IMPLEMENTATION、FASTRTPS_DEFAULT_PROFILES_FILE) - XML 配置文件是否存在且语法合法
- 各节点进程环境变量中是否包含 DDS 配置路径
/dev/shm下是否存在活跃的 Fast DDS 共享内存文件ros2 node list发现延迟是否低于阈值(500ms 为优秀,1000ms 为良好)
该机制显著降低了因环境变量未传递或配置文件路径错误导致的隐性通信性能退化问题。
Sources: start_robot.sh, start_robot.sh
推理执行优化
控制周期与推理周期解耦
推理节点的核心循环分为两条独立线程。control 线程以固定 4ms 周期(250Hz)运行,负责调用 apply_action() 将最新计算出的关节目标值平滑下发到电机;inference 线程则以 decimation * dt 为周期运行,默认配置中 dt=0.004、decimation=5,即每 20ms(50Hz)执行一次 ONNX 模型推理。
这种解耦设计保证了两点:一是即使模型推理耗时偶尔波动,控制线程仍能以 250Hz 稳定输出,维持机器人姿态不失控;二是高频控制回路与低频推理回路可分别赋予相同的实时优先级(均为 70),由内核按 FIFO 顺序调度。当推理循环超时时,节点会明确打印警告,提示实际耗时与周期:
RCLCPP_WARN(this->get_logger(), "Inference loop overran! Took %lld us, but period is %lld us.", ...);
Sources: inference_node.cpp, inference_node.cpp, inference.yaml
ONNX Runtime 执行优化
模型加载阶段通过 Ort::SessionOptions 启用多项性能优化:
DisablePerSessionThreads():禁止 ONNX Runtime 创建独立线程池,避免与推理节点自有线程池争用EnableCpuMemArena()与EnableMemPattern():启用 CPU 内存池与连续内存模式,减少推理过程中的分配开销SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL):开启图级别全部优化,包括算子融合与常量折叠
输入输出张量通过 Ort::MemoryInfo::CreateCpu 直接绑定到预分配的 CPU 缓冲区,实现零拷贝推理。
Sources: inference_node.cpp
线程池并行化
RobotInterface 在构造时根据电机接口数量创建 ThreadPool,其工作线程同样设置为 SCHED_FIFO 70。该线程池用于并行执行多路 CAN 总线上的电机读写操作,将原本串行的 23 个电机访问拆分为与接口数相等的并行批次,显著降低单次控制周期内的 I/O 总耗时。
Sources: robot_interface.cpp, thread_pool.hpp
性能验证要点
完成所有配置后,建议按以下顺序验证实时性是否生效:
| 验证项 | 命令/方法 | 期望结果 |
|---|---|---|
| 实时权限 | ulimit -r |
输出 98 |
| 内核版本 | uname -r |
包含 rt 或 preempt_rt 标识 |
| 线程优先级 | `ps -eo pid,comm,rtprio | grep inference` |
| 内存锁定 | 查看启动日志 | 无 mlockall failed 警告 |
| DDS 共享内存 | ls /dev/shm/ | grep fastrtps |
存在相关文件 |
| 控制周期 | 查看推理节点日志 | 无持续 Inference loop overran 警告 |
若出现 Failed to set realtime priority 致命错误,应首先检查 /etc/security/limits.conf 是否配置正确,并确认已重新登录或重启使配置生效。
Sources: README_CN.md, inference_node.cpp
总结
ATOM01 的实时性能优化是一个从内核到应用的系统性工程:PREEMPT_RT 内核提供底层调度确定性,SCHED_FIFO 与优先级分层保证关键线程的抢占顺序,CPU 亲和性减少多核干扰,mlockall 杜绝内存交换抖动,Fast DDS 的共享内存与预分配策略压缩通信延迟,而控制/推理双线程解耦与 ONNX Runtime 图优化则确保模型推理不会阻塞高频控制回路。
若需要进一步了解推理节点内部的线程协作模型,请参阅 推理节点设计与线程模型;若要自定义电机驱动或扩展新的通信协议,请参阅 扩展新电机驱动类型。