1. iceOryx 是什么?
这是一头漂亮的"冰羚",它是一种用于汽车软件中的 ICP 通信中间件,由 Eclipse 基金会发布和维护。
通信中间件在汽车软件开发中占据越来越重要的地位,这是因为自动驾驶的发展带动了系统内部不同进程、线程之间巨量的数据交换,目前这样的通信量能达到 GB/s 以上,如何高效实现通信要求是每一个自动驾驶开发团队需要认真考虑的事情。
iceoryx 运用“零拷贝”技术实现进程间通信,具有快速、灵活和可靠的发布/订阅架构。
2. iceoryx 的特性
快速 | 灵活 | 可靠 |
---|---|---|
真正的零拷贝技术,基于共享内存实现 | 多操作系统支持 | 根据汽车软件需求而设计 |
消息通信时延<1us | 服务发现支持 | 基于静态内存和无锁算法的实现 |
数据通信效率达到 1GB/s | 易于集成,能集成到ROS2、AP Autosar 等框架 | 大量的C 库(STL 安全实现) |
综上所述,iceoryx 就是非常适合汽车软件开发的开源通信中间件,速度快易于集成。
所以,需要知名的软件中间件也已经可以集成 iceoryx 了。
- Eclipse Cyclone DDS,
- eCAL 来自 Continental,
- RTA-VRTE 来自 ETAS
- Apex.OS 来自 Apex.AI.
3. iceoryx 基础概念
iceoryx 的核心概念不多,只要掌握 RouDi、Runtime、Publisher、Subscriber、Topic 这些概念就可以进行基本通信功能的开发了。
3.1 RouDi
iceoryx 的守护进程,不同的应用需要与它进行连接才能正常通信。 重要提示:运行iceoryx程序前,一定要先启动守护进程 iox-roudi
代码语言:javascript复制# 在终端启动守护进程
iox-roudi
3.2 Runtime
每个需要在 iceoryx 框架下运行的应用,都需要初始化它的 runtime。
代码语言:javascript复制constexpr char APP_NAME[] = "frank909";
iox::runtime::PoshRuntime::initRuntime(APP_NAME);
3.3 Publisher
数据发送器,需要绑定 topic 使用。
代码语言:javascript复制//! [create publisher]
iox::popo::Publisher<FrankMsg> publisher({"Frank", "Example", "Msg"});
FrankMsg 是一个 Topic,一个 Publisher 创建时需要指定 Topic,用来指定要发送的数据。
3.4 Subscriber
数据接收器,需要绑定 topic 使用。
代码语言:javascript复制iox::popo::Subscriber<FrankMsg> subscriber({"Frank", "Example", "Msg"});
3.5 Topic
Topic 是数据载体,Publisher 发送一个 Topic,Subscriber 能够接收绑定的 Topic 而不需要关注 Topic 来自何处,Publisher 与 Subscriber 要正常通信就需要绑定同一个 Topic。
3.6 信息轮询与信号触发
如果需要实时获取 Topic 信息,则可以不断调用 subscriber 方法,但这样势必会增加计算资源造成算力浪费。
iceoryx 提供 2 种方式提升了数据获取效率:
- WaitSet 采用 react 设计模式开发,绑定对应的 subscribers,如果数据来了就触发通知
- Listenner 能够直接触发用户定制的 callback,起到数据来临时的回调作用
4. Zero-Copy 如何实现?
iceoryx 号称实现了真正的零拷贝。那我们为什么需要零拷贝呢?
想象一下,自动驾驶系统中感知模块获取的摄像头图片 1s 内要传输 30 次,1 张图片在内存中的容量是 3M,那么一个模块接收感知模块的数据通信量 1s 内就达到 80M/s,在自动驾驶中一般不止一个模块会订阅感知模块的图像信息,那么数据量很容易就达到 400M/s,这将造成非常严重的通信效率隐患,要知道在自动驾驶系统中还有其他大量传感器数据,比如体积更大的激光雷达点云数据,如果通信速度不能保证,那么算法的效率和结果也会得不到保障,所以,我们要提速。
零拷贝技术是提速的一种方式。
两个不同进程进行数据交换,因为进程的物理资源是独立的,所以,同样一份数据在两个进程之间交互就需要拷贝副本,这会产生对应的 CPU 及内存IO耗时,如果通信频繁数据量大,通信的时延就会越来越大。
所以,我们要减少数据的多次拷贝。我们只需要一份数据,然后传递它的指针,因为指针一般是 4个字节,所以,无比 3M 一张的图片,我传递一个指针给对方,通信时间就下来了,并且可以传递给多个进程,时间消耗并不会显著增加。
代码语言:javascript复制1. iceoryx 利用共享内存技术,它会预先开辟一些内存块,称之为 chunk,publisher 将数据写入
2. subscriber 通过指针到 chunk 获取信息,当 chunk 中被写入数据时,订阅相应 topic 的 subscriber 就会收到一个指针,不同的 subscriber 可以订阅同一个 topic 信息,它们彼此不知道对方
3. iceoryx 知道每个 chunk 对应的 subscriber 引用记录,每增加一个 subscriber 读取信息,对应的 reference 就 1,如果一个 subscriber 不消费了,reference 就-1,如果 reference 没有人消息了就被置为 0,它就被后台重置回收,避免造成资源浪费
5. 示例
5.1 iceoryx 安装
iceoryx 需要一些基本的依赖,这个以 Linux 环境为例。
代码语言:javascript复制sudo apt install gcc g cmake libacl1-dev libncurses5-dev pkg-config
然后下载源码仓库,并利用脚本编译。
代码语言:javascript复制git clone https://github.com/eclipse-iceoryx/iceoryx.git
# 方式1:编译必要组件
cd iceoryx
cmake -Bbuild -Hiceoryx_meta
cmake --build build
sudo cmake --build build --target install
# 方式2:编译全部组件
cd iceoryx
./tools/iceoryx_build_test.sh build-all
5.2 一个简单的 pub-sub 通讯示例
这个示例很简单,我会定义一个 Topic。
topic_data.hpp
代码语言:javascript复制#ifndef TOPIC_DATA_HPP
#define TOPIC_DATA_HPP
struct FrankMsg
{
long msg_count = 0;
};
#endif
然后围绕 Topic 验证它的发布-订阅效果。
然后定义一个 Publisher 用来发消息。
一个 iceoryx 应用一定要初始化 runtime 的,并且这个 runtime 的名字是唯一的。
代码语言:javascript复制1. 初始化 runtime
2. 创建 publisher,并绑定 FrankMsg 这个 Topic
3. 每隔 1s 发送数据,通过 publisher 的 loan 和数据的 publish() 方法配合。
在上面展示的代码片断中,我初始化了 runtime,然后定义了 topic,然后发送数据。
接收数据代码如下:
同样需要初始化 runtime,然后创建 subcriber 绑定 FrankMsg 这个 Topic。
代码语言:javascript复制1. 初始化 runtime
2. 创建 subscriber,并绑定 FrankMsg 这个 Topic
3. 每隔 1s 接收数据,通过 subscriber 的 take
4. 能够获取正常的数据,如果轮询时没有数据可以通过 error 结果中的iox::popo::ChunkReceiveResult::NO_CHUNK_AVAILABLE 获知。
核心代码这些就足够了,最后需要配置 CMakeLists.txt。
我们编译代码后得到 frankpublisher 和 franksubscriber 2 个 bin 文件,接下来可以验证效果了。
重要提示:运行iceoryx程序前,一定要先启动守护进程 iox-roudi
代码语言:javascript复制# 在终端启动守护进程
iox-roudi
先启动 frankpublisher 发送数据,再另外开窗口启动 franksubscriber。
可以发现通信正常,自此我们掌握了如何编写一个最简单的 iceoryx 通讯程序,后面留给我们的话题是如何把它更高效运用起来,比如跨进程传输大量的摄像头数据及激光点云数据。