实时编程
实时计算是许多机器人系统的核心需求,尤其是安全关键和任务关键型应用,例如自动驾驶、航天器和工业制造。ROS 2 从设计之初就考虑了实时性能约束——这是 ROS 1 早期未涵盖的需求,而事后将 ROS 1 改造为实时友好的架构已经不现实。
这篇文档概述了实时计算的要求及软件工程师应遵循的最佳实践。核心要点如下:
构建实时系统,意味着实时循环必须在截止期限(deadline)内完成周期性更新,且只能容忍很小的误差(即允许的最大抖动)。为此,执行路径中必须避免一切非确定性操作,例如缺页中断、动态内存分配/释放,以及无限阻塞的同步原语。
倒立摆平衡是一个经典的控制问题,通常需要借助实时计算来解决。如果控制器意外阻塞过长时间,摆杆就会倒下或失稳。反之,只要控制器能以高于电机响应速度的频率稳定更新,就能及时处理传感器数据并保持平衡。
了解以上背景后,下面通过一个演示来体验实时编程。
安装并运行演示
Section titled “安装并运行演示”该演示仅支持 Linux。ROS 社区中许多做实时计算的成员使用 Xenomai 或 RT_PREEMPT 作为实时方案,而演示中为优化性能所做的大量操作都依赖于 Linux 特有的机制。因此,macOS 和 Windows 用户请跳过这部分。
此外,本演示必须使用静态 DDS API 从源码构建。目前唯一支持的实现是 ConnextDDS。
首先,按照 从源码构建 ROS 2 的说明,以 Connext DDS 作为中间件进行编译。
运行前请确保系统至少有 8GB 可用内存。由于进程会锁定内存,swap 将不起作用。
source 你的 ROS 2 setup.bash:
source ./install/setup.bash运行演示程序。如遇权限问题,可能需要加 sudo:
$ ros2 run pendulum_control pendulum_demoInitial major pagefaults: 518Initial minor pagefaults: 2139466No results filename given, not writing resultsrttest statistics:- Minor pagefaults: 0- Major pagefaults: 0Latency (time after deadline was missed): - Min: 1851 ns - Max: 166796 ns - Mean: 14229.182000 ns - Standard deviation: 12288.040996你可能会看到如下 stderr 输出:
mlockall failed: Cannot allocate memoryCouldn't lock all cached virtual memory.Pagefaults from reading pages not yet mapped into RAM will be recorded.演示程序在初始化完成后,会尝试通过 mlockall 将所有已缓存内存锁定在 RAM 中,并阻止后续的动态内存分配,以避免因大量新内存加载到 RAM 而触发缺页中断。(更多细节请参见实时设计文章。)
出现上述提示后,演示照常继续。你还可能看到类似如下的输出,反映执行期间发生的缺页中断数量:
rttest statistics: - Minor pagefaults: 20 - Major pagefaults: 0如果要消除这些缺页中断,需要做以下调整……
调整内存锁定权限
Section titled “调整内存锁定权限”在 /etc/security/limits.conf 中添加(使用 sudo):
<your username> - memlock <limit in kB>限制设为 -1 表示无限制。选择无限制时,可能还需要在编辑文件后配合执行 ulimit -l unlimited(以 root 身份)。
保存文件后,注销并重新登录,然后重新运行 pendulum_demo。
输出中将显示零缺页中断,或者一条 bad_alloc 异常的错误信息。如果是后者,说明可用内存不足以将进程分配的内存全部锁定到 RAM——只有增加物理内存才能实现零缺页中断。
要查看更多输出,需要先运行 pendulum_logger 节点。
在一个已 source install/setup.bash 的终端中,执行:
ros2 run pendulum_control pendulum_logger你应该会看到输出消息:
Logger node initialized.在另一个已 source 了 setup.bash 的终端中,再次运行 pendulum_demo。
该可执行文件启动后,前一个终端将不断打印输出:
Commanded motor angle: 1.570796Actual motor angle: 1.570796Mean latency: 210144.000000 nsMin latency: 4805 nsMax latency: 578137 nsMinor pagefaults during execution: 0Major pagefaults during execution: 0该演示控制着一个简单的倒立摆仿真。倒立摆在独立线程中计算自身位置。一个 ROS 节点模拟摆的电机编码器并发布位置数据,另一个 ROS 节点充当简单的 PID 控制器,负责计算下一条控制指令。
logger 节点周期性地打印摆的状态以及演示在执行阶段的运行时性能统计。
pendulum_demo 运行结束后,按 Ctrl-C 退出 logger 节点。
pendulum_demo 执行期间,你将看到收集到的最终统计信息:
rttest statistics: - Minor pagefaults: 0 - Major pagefaults: 0 Latency (time after deadline was missed): - Min: 3354 ns - Max: 2752187 ns - Mean: 19871.8 ns - Standard deviation: 1.35819e+08
PendulumMotor received 985 messagesPendulumController received 987 messages延迟字段以纳秒为单位展示更新循环的最小、最大和平均延迟。这里的延迟是指更新预期发生时刻之后的滞后时间。
实时系统的要求因应用而异。假设本演示的更新循环频率为 1kHz(1 毫秒),我们的目标是最大延迟不超过更新周期的 5%。
本次运行的平均延迟很理想,但最大延迟不可接受——它实际上已经超过了更新周期。原因何在?
这很可能是非确定性调度器造成的。如果运行的是普通 Linux 内核且未安装 RT_PREEMPT 补丁,就很难达到预期的实时目标,因为 Linux 调度器不允许在用户态任意抢占线程。
更多信息请参见实时设计文章。
演示会尝试将调度器和线程优先级设置为适合实时性能的值。如果操作失败,将看到错误信息:「Couldn’t set scheduling priority and policy: Operation not permitted」。按下一节的说明调整权限后可以获得更好的性能:
设置调度器权限
Section titled “设置调度器权限”在 /etc/security/limits.conf 中添加(使用 sudo):
<your username> - rtprio 98rtprio(实时优先级)的取值范围是 0–99。但切勿将其设为 99,否则进程可能干扰以最高优先级运行的关键系统进程(例如看门狗)。本演示将尝试以优先级 98 运行控制循环。
演示运行结束后,可以绘制其中收集的延迟和缺页中断统计信息。
由于代码使用 rttest 进行插桩,因此支持以下命令行参数:
| 命令 | 描述 | 默认值 |
|---|---|---|
-i | 指定实时循环运行的迭代次数 | 1000 |
-u | 指定更新周期,默认单位为微秒。使用后缀 “s” 表示秒,“ms” 表示毫秒,“us” 表示微秒,“ns” 表示纳秒 | 1ms |
-f | 指定写入收集数据的文件名 |
使用文件名再次运行演示以保存结果:
ros2 run pendulum_control pendulum_demo -f pendulum_demo_results然后对结果文件运行 rttest_plot 脚本:
$ ros2 run rttest rttest_plot pendulum_demo_resultsWriting results to file: pendulum_demo_results...该脚本将生成以下文件:
pendulum_demo_results_plot_latency.svgpendulum_demo_results_plot_latency_hist.svgpendulum_demo_results_plot_majflts.svgpendulum_demo_results_plot_minflts.svg可以用任意图片查看器打开这些图表。