理解实时编程

背景

实时计算是许多机器人系统的关键特性,特别是对于自动驾驶汽车、航天器和工业制造等安全和任务关键型应用。我们设计和原型化ROS 2时考虑了实时性能限制,因为在ROS 1的早期阶段并没有考虑这一要求,现在对ROS 1进行重构以实现实时友好性已经变得困难。

This document <https://design.ros2.org/articles/realtime_background.html> 详细介绍了实时计算的要求和软件工程师的最佳实践。简而言之:

要创建一个实时计算机系统,我们的实时循环必须周期性地更新以满足截止时间。我们只能容忍这些截止时间上的小误差(最大允许的抖动)。为了做到这一点,我们必须避免执行路径中的非确定性操作,比如:页错误事件、动态内存分配/释放和无限期阻塞的同步原语。

实时计算通常用于解决控制问题的经典示例是平衡一个``倒立摆 <https://en.wikipedia.org/wiki/Inverted_pendulum>``。如果控制器阻塞时间意外延长,摆杆将倒下或变得不稳定。但是,如果控制器可靠地以比控制摆杆的电机操作速度更快的频率更新,摆杆将能够成功地根据传感器数据调整自己来保持平衡。

现在你已经了解了关于实时计算的一切,让我们尝试一下演示!

安装并运行演示

这个实时演示是为Linux操作系统设计的,因为ROS社区的许多进行实时计算的成员使用Xenomai或RT_PREEMPT作为他们的实时解决方案。由于演示中进行的许多优化性能的操作是特定于操作系统的,所以演示只能在Linux系统上构建和运行。所以,如果你是OSX或Windows用户,请不要尝试这部分!

此外,必须使用静态DDS API从源代码构建。目前唯一支持的实现是Connext

首先,请按照说明使用Connext DDS作为中间件来构建ROS 2。请参考 从源码构建ROS 2

运行测试

在运行之前,请确保至少有8GB的可用内存。使用内存锁定后,交换空间将无法使用。

执行 source your ROS 2 setup.bash

运行演示程序二进制文件,并将输出重定向。如果出现权限错误,您可能需要使用``sudo``:

pendulum_demo > output.txt

到底发生了什么?

首先,即使您重定向了stdout,您还是会在控制台上看到一些输出(来自stderr):

mlockall failed: Cannot allocate memory
Couldn't lock all cached virtual memory.
Pagefaults from reading pages not yet mapped into RAM will be recorded.

在演示程序的初始化阶段之后,它将尝试将所有缓存内存锁定到RAM,并使用``mlockall``来阻止将来的动态内存分配。这是为了防止页面错误将大量新内存加载到RAM中。(有关更多信息,请参阅`实时设计文章 <https://design.ros2.org/articles/realtime_background.html#memory-management>`__。)

当这种情况发生时,演示将继续进行。在演示生成的 output.txt 文件底部,您将看到执行过程中遇到的页面错误数量:

rttest statistics:
  - Minor pagefaults: 20
  - Major pagefaults: 0

如果我们希望这些页面错误消失,我们需要...

调整内存锁定的权限

/etc/security/limits.conf 中添加以下内容(使用 sudo):

<your username>    -   memlock   <limit in kB>

-1``的限制是无限制的。如果选择此选项,您可能需要在编辑文件后附加``ulimit -l unlimited

保存文件后,请退出并重新登录。然后重新运行``pendulum_demo``命令。

在输出文件中,您要么看到零页面错误(pagefaults),要么看到捕获的bad_alloc异常错误。如果发生这种情况,表示您没有足够的可用内存将进程分配的内存锁定到RAM中。您需要在计算机上安装更多的内存才能看到零页面错误!

输出概览

要查看更多输出,我们需要运行``pendulum_logger``节点。

在一个已经使用``install/setup.bash``的终端中,执行以下命令:

pendulum_logger

您应该会看到以下输出信息:

Logger node initialized.

在另一个已经使用setup.bash的终端中,再次执行``pendulum_demo``命令。

一旦此可执行文件启动,您应该看到另一个Shell不断地打印输出:

Commanded motor angle: 1.570796
Actual motor angle: 1.570796
Mean latency: 210144.000000 ns
Min latency: 4805 ns
Max latency: 578137 ns
Minor pagefaults during execution: 0
Major pagefaults during execution: 0

该演示控制一个非常简单的倒立摆模拟。摆模拟在其自己的线程中计算其位置。一个ROS节点模拟摆的电机编码器传感器并发布其位置。另一个ROS节点充当简单的PID控制器,计算下一个命令消息。

记录器节点定期打印摆的状态以及演示执行阶段的运行性能统计信息。

在``pendulum_demo``完成后,您将需要使用CTRL-C退出记录器节点。

延迟

在``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 messages
PendulumController received 987 messages

延迟字段显示了更新循环的最小、最大和平均延迟,单位为纳秒。这里的延迟指的是更新应该发生后经过的时间量。

实时系统的要求取决于应用程序,但在这个演示中,我们假设有一个1kHz(1毫秒)的更新循环,并且我们的目标是允许的最大延迟为更新周期的5%。

所以,我们这次运行的平均延迟非常好,但是最大延迟是不可接受的,因为它实际上超过了我们的更新循环!发生了什么事?

我们可能遇到了非确定性调度程序。如果你运行的是纯粹的Linux系统,并且没有安装RT_PREEMPT内核,那么你可能无法达到我们设定的实时目标,因为Linux调度程序不允许你在用户级别任意抢占线程。

请参阅`实时设计文章<https://design.ros2.org/articles/realtime_background.html#multithreaded-programming-and-synchronization>`__ 获取更多信息。

该演示尝试将演示的调度程序和线程优先级设置为适合实时性能。如果此操作失败,你将看到一个错误消息:“无法设置调度优先级和策略:操作不允许”。按照下一节中的说明可以获得稍微更好的性能:

设置调度器的权限

/etc/security/limits.conf 中添加以下内容(使用 sudo):

<your username>    -   rtprio   98

rtprio(实时优先级)字段的范围是0-99。但是,请不要将限制值设置为99,否则您的进程可能会干扰运行在最高优先级的重要系统进程(例如看门狗)。此演示将尝试以优先级98运行控制循环。

绘制结果

在演示运行后,您可以绘制收集到的延迟和页面错误统计信息。

由于代码已经使用 rttest 进行了工具化,因此可以使用一些有用的命令行参数:

命令

描述

默认值

-i

指定运行实时循环的迭代次数

1000

-u

使用默认单位微秒来指定更新周期。

使用后缀"s"表示秒,"ms"表示毫秒,

表示微秒的后缀为"us",表示纳秒的后缀为"ns"。

1 毫秒

-f

指定用于写入收集数据的文件名。

再次运行演示程序并指定要保存结果的文件名:

pendulum_demo -f pendulum_demo_results

然后在生成的文件上运行“rttest_plot”脚本:

rttest_plot pendulum_demo_results

该脚本将生成三个文件:

pendulum_demo_results_plot_latency.svg
pendulum_demo_results_plot_majflts.svg
pendulum_demo_results_plot_minflts.svg

您可以使用您选择的图像查看器查看这些图形。