07 与 CycloneDDS 及 ROS 2 集成

07 与 CycloneDDS 及 ROS 2 集成

本文重点:CycloneDDS 如何借助 iceoryx 实现同机零拷贝(ENABLE_SHM),以及 ROS 2 Humble 的完整链路 rmw_cyclonedds_cpp → cyclonedds → iceoryx
源码锚点(cyclonedds 0.10.x,随 ROS 2 Humble):ddsi_shm_transport.c/.hshm_monitor.cdds_loan.cdds_write.cq_init.cddsi_cfgelems.h

cyclonedds 根目录:/home/cp/work2/ros2Learn/ros2_humble/src/eclipse-cyclonedds/cyclonedds
rmw 根目录:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rmw_cyclonedds/rmw_cyclonedds_cpp


1. 整体链路

1
2
3
4
5
6
7
8
9
10
11
12
13
ROS 2 应用
│ rclcpp::LoanedMessage / publish

rmw_cyclonedds_cpp (rmw_node.cpp: borrow_loaned_message → dds_loan_sample)


cyclonedds ddsc 层 (dds_write.c: dds_write_impl_iox / dds_loan.c)
│ iox_pub_loan_aligned_chunk_with_user_header / iox_pub_publish_chunk

iceoryx_binding_c → iceoryx_posh 共享内存段

│ iox_listener 回调 (shm_monitor.c) → iox_sub_take_chunk → rhc_store
cyclonedds 读端 ← ← ← ← ← ┘

前提条件汇总(缺一不可,任一不满足则静默退回 UDP loopback):

条件 层次
cyclonedds 以 -DENABLE_SHM=ON 编译(定义 DDS_HAS_SHM 编译期
CYCLONEDDS_URISharedMemory/Enable=true 配置
iox-roudi 守护进程已运行 运行时
读写双方在同一台主机(同一 RouDi 域) 拓扑
QoS:KEEP_LAST + VOLATILE/TRANSIENT_LOCAL 等(§6) QoS
消息类型固定大小(POD)才能”真零拷贝”,否则序列化进共享内存 类型

2. 编译与配置开关

2.1 编译期:ENABLE_SHM

CMake 选项 ENABLE_SHM 打开后定义 DDS_HAS_SHMSharedMemory 配置组才会出现在 schema 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static struct cfgelem shmem_cfgelems[] = {
BOOL("Enable", NULL, 1, "false",
MEMBER(enable_shm),
FUNCTIONS(0, uf_boolean, 0, pf_boolean),
DESCRIPTION("<p>This element allows to enable shared memory in Cyclone DDS.</p>")),
STRING("Locator", NULL, 1, "",
MEMBER(shm_locator),
FUNCTIONS(0, uf_string, ff_free, pf_string),
DESCRIPTION(
"<p>Explicitly set the Iceoryx locator used by Cyclone to check whether "
"a pair of processes is attached to the same Iceoryx shared memory. The "
"default is to use one of the MAC addresses of the machine, which should "
"work well in most cases.</p>"
)),
STRING("Prefix", NULL, 1, "DDS_CYCLONE",
MEMBER(iceoryx_service),
FUNCTIONS(0, uf_string, ff_free, pf_string),
DESCRIPTION(
"<p>Override the Iceoryx service name used by Cyclone.</p>"
)),
ENUM("LogLevel", NULL, 1, "info",
MEMBER(shm_log_lvl),
...

四个配置项:Enable(默认 false)、Locator(同机判定地址,默认取 MAC)、Prefix(iceoryx CaPro service 名,默认 DDS_CYCLONE)、LogLevel

2.2 运行时:CYCLONEDDS_URI

1
2
3
4
5
6
7
8
9
# ROS 2 Humble 启用零拷贝的典型环境
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export CYCLONEDDS_URI='<CycloneDDS><Domain><SharedMemory><Enable>true</Enable><LogLevel>info</LogLevel></SharedMemory></Domain></CycloneDDS>'

# 终端 1:先起 RouDi(管理共享内存段与 mempool)
iox-roudi

# 终端 2/3:正常跑 ROS 2 节点
ros2 run demo_nodes_cpp talker

ROS 2 Humble 官方仓库 rmw_cyclonedds 附带 shared_memory_support.md,其配置方式与此一致。


3. 域初始化:iceoryx 作为”虚拟传输”

q_init.cenable_shm 为真时执行 iceoryx_init():注册 iceoryx runtime(进程名 iceoryx_rt_<pid>_<启动时间>),并通过 vnet(虚拟传输工厂) 挂一个 kind 为 NN_LOCATOR_KIND_SHEM(值 16,q_protocol.h:110)的传输:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int iceoryx_init (struct ddsi_domaingv *gv)
{
shm_set_loglevel(gv->config.shm_log_lvl);

char *sptr;
ddsrt_asprintf (&sptr, "iceoryx_rt_%"PRIdPID"_%"PRId64, ddsrt_getpid (), gv->tstart.v);
GVLOG (DDS_LC_SHM, "Current process name for iceoryx is %s\n", sptr);
iox_runtime_init (sptr);
free(sptr);

// FIXME: this can be done more elegantly when properly supporting multiple transports
if (ddsi_vnet_init (gv, "iceoryx", NN_LOCATOR_KIND_SHEM) < 0)
return -1;
ddsi_factory_find (gv, "iceoryx")->m_enable = true;

SHEM locator 的地址如何来:未显式配置 SharedMemory/Locator 时取本机第一个非 loopback 网卡的 MAC 地址——它唯一标识”这台机器”,用于后续同机判定:

1
2
3
4
5
6
7
8
memset (gv->loc_iceoryx_addr.address, 0, sizeof (gv->loc_iceoryx_addr.address));
if (ddsrt_eth_get_mac_addr (gv->interfaces[if_index].name, gv->loc_iceoryx_addr.address))
{
GVERROR ("Unable to get MAC address for iceoryx\n");
return -1;
}
gv->loc_iceoryx_addr.kind = NN_LOCATOR_KIND_SHEM;
gv->loc_iceoryx_addr.port = 0;

4. 发现:如何判定”同机可用 SHM”

  1. 发布:端点 QoS 未屏蔽 SHEM 时,SEDP 报文的 locator 列表里额外带上本机的 SHEM locator(q_ddsi_discovery.c:1190 起,判断 !(xqos->ignore_locator_type & NN_LOCATOR_KIND_SHEM))。
  2. 接收:建立 proxy 端点时,遍历对端 locator,若发现 kind 为 SHEM 且 地址(MAC)与本机 loc_iceoryx_addr 相同,即认定对端与本进程挂在同一个 iceoryx 共享内存上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void has_iceoryx_address_helper (const ddsi_xlocator_t *n, void *varg)
{
struct has_iceoryx_address_helper_arg *arg = varg;
if (n->c.kind == NN_LOCATOR_KIND_SHEM && memcmp (arg->loc_iceoryx_addr->address, n->c.address, sizeof (arg->loc_iceoryx_addr->address)) == 0)
arg->has_iceoryx_address = true;
}

static bool has_iceoryx_address (struct ddsi_domaingv * const gv, struct addrset * const as)
{
if (!gv->config.enable_shm)
return false;
else
{
struct has_iceoryx_address_helper_arg arg = {
.loc_iceoryx_addr = &gv->loc_iceoryx_addr,
.has_iceoryx_address = false
};
addrset_forall (as, has_iceoryx_address_helper, &arg);
return arg.has_iceoryx_address;
}
}

判定结果存入 pwr->is_iceoryx / prd->is_iceoryxddsi_proxy_endpoint.c:240、562)。匹配(match)时进一步与本地端点 QoS 合成最终决策,例如 writer 侧:

1
2
3
4
5
#ifdef DDS_HAS_SHM
const bool use_iceoryx = prd->is_iceoryx && !(wr->xqos->ignore_locator_type & NN_LOCATOR_KIND_SHEM);
#else
const bool use_iceoryx = false;
#endif

use_iceoryx 为真时,该 reader 视作”已确认所有 Heartbeat”(可靠性交给 iceoryx 队列,跳过 RTPS 重传机制)。发送端 nn_xpack_send1跳过对 SHEM locator 的真实网络发送

1
2
3
4
5
6
#ifdef DDS_HAS_SHM
// SHM_TODO: We avoid sending packet while data is SHMEM.
// I'm not sure whether this is correct or not.
if (!gv->mute && loc->c.kind != NN_LOCATOR_KIND_SHEM)
#else
if (!gv->mute)

5. 端点创建与数据路径

5.1 writer/reader 是否挂 iceoryx:QoS 闸门

创建 writer 时先过 dds_writer_support_shm(),不满足则把 ignore_locator_type |= NN_LOCATOR_KIND_SHEM(即该端点彻底退出 SHM 路径):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static bool dds_writer_support_shm(const struct ddsi_config* cfg, const dds_qos_t* qos, const struct dds_topic *tp)
{
if (NULL == cfg ||
false == cfg->enable_shm)
return false;

// check necessary condition: fixed size data type OR serializing into shared
// memory is available
if (!tp->m_stype->fixed_size && (!tp->m_stype->ops->get_serialized_size ||
!tp->m_stype->ops->serialize_into)) {
return false;
}

// only VOLATILE or TRANSIENT LOCAL
if(!(qos->durability.kind == DDS_DURABILITY_VOLATILE ||
qos->durability.kind == DDS_DURABILITY_TRANSIENT_LOCAL)) {
return false;
}

// only KEEP LAST
if(qos->history.kind != DDS_HISTORY_KEEP_LAST) {
return false;
}

reader 侧 dds_reader_support_shm()dds_reader.c:510)条件类似,另加 ignorelocal == DDS_IGNORELOCAL_NONE。通过后创建 iceoryx 端点,CaPro 三元组 = {Prefix 配置(默认 DDS_CYCLONE), 类型名, topic 名}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef DDS_HAS_SHM
if (wr->m_wr->has_iceoryx)
{
DDS_CLOG (DDS_LC_SHM, &wr->m_entity.m_domain->gv.logconfig, "Writer's topic name will be DDS:Cyclone:%s\n", wr->m_topic->m_name);
iox_pub_options_t opts = create_iox_pub_options(wqos);

// NB: This may fail due to icoeryx being out of internal resources for publishers
// In this case terminate is called by iox_pub_init.
// it is currently (iceoryx 2.0 and lower) not possible to change this to
// e.g. return a nullptr and handle the error here.
wr->m_iox_pub = iox_pub_init(&(iox_pub_storage_t){0}, gv->config.iceoryx_service, wr->m_topic->m_stype->type_name, wr->m_topic->m_name, &opts);
memset(wr->m_iox_pub_loans, 0, sizeof(wr->m_iox_pub_loans));
}
#endif

5.2 iceoryx_header_t:chunk 里的 DDS 元数据

DDS 语义(GUID、时间戳、statusinfo、keyhash、数据状态)通过 iceoryx 的 user-header 随 chunk 传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef enum {
IOX_CHUNK_UNINITIALIZED,
IOX_CHUNK_CONTAINS_RAW_DATA,
IOX_CHUNK_CONTAINS_SERIALIZED_DATA
} iox_shm_data_state_t;

struct iceoryx_header {
struct ddsi_guid guid;
dds_time_t tstamp;
uint32_t statusinfo;
uint32_t data_size;
unsigned char data_kind;
ddsi_keyhash_t keyhash;
iox_shm_data_state_t shm_data_state;
};

shm_data_state 区分 chunk 里是原始结构体(POD 零拷贝)还是 CDR 序列化字节(非固定类型的退化模式)。

5.3 shm_create_chunk:借 chunk

分配封装在 shm_create_chunk(),调用第 06 篇讲的 iox_pub_loan_aligned_chunk_with_user_header,user-header 即 iceoryx_header_t;拿不到 chunk 时非阻塞重试 10 次(每次 sleep 1ms)后放弃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void *shm_create_chunk(iox_pub_t iox_pub, size_t size) {
iceoryx_header_t *ice_hdr;
void *iox_chunk;

int32_t number_of_tries =
10; // try 10 times over at least 10ms, considering the wait time below

while (true) {
enum iox_AllocationResult alloc_result =
iox_pub_loan_aligned_chunk_with_user_header(
iox_pub, &iox_chunk, (uint32_t)size,
IOX_C_CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT,
sizeof(iceoryx_header_t), 8);

if (AllocationResult_SUCCESS == alloc_result)
break;

if (--number_of_tries <= 0) {
return NULL;
}

dds_sleepfor(DDS_MSECS(1));
}

iox_chunk_header_t *iox_chunk_header =
iox_chunk_header_from_user_payload(iox_chunk);
ice_hdr = iox_chunk_header_to_user_header(iox_chunk_header);
ice_hdr->data_size = (uint32_t)size;
ice_hdr->shm_data_state = IOX_CHUNK_UNINITIALIZED;
return iox_chunk;
}

5.4 写路径:dds_write_impl_iox

dds_write() 在 writer 有 iceoryx publisher 时走专用分支(dds_write.c:593-600)。核心逻辑:

  1. 拿 chunk:数据若本来就是 loan 出来的 chunk(deregister_pub_loan 命中)直接复用;否则新借 chunk 并填充——固定大小类型 memcpy(零序列化),否则 ddsi_sertype_serialize_into 序列化进共享内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static bool fill_iox_chunk(dds_writer *wr, const void *sample, void *iox_chunk, size_t sample_size)
{
bool has_fixed_size_type = wr->m_topic->m_stype->fixed_size;
bool ret = true;
iceoryx_header_t *iox_header = iceoryx_header_from_chunk(iox_chunk);
if (has_fixed_size_type) {
memcpy(iox_chunk, sample, sample_size);
iox_header->shm_data_state = IOX_CHUNK_CONTAINS_RAW_DATA;
} else {
size_t size = iox_header->data_size;
ret = ddsi_sertype_serialize_into(wr->m_wr->type, sample, iox_chunk, size);
if(ret) {
iox_header->shm_data_state = IOX_CHUNK_CONTAINS_SERIALIZED_DATA;
}
...
  1. 决定是否只走 iceoryx:没有网络 reader、VOLATILE、没有本地非 iceoryx 快路径 reader 时,可完全跳过 CDR 序列化与 RTPS:
1
2
3
4
const bool use_only_iceoryx =
no_network_readers &&
ddsi_wr->xqos->durability.kind == DDS_DURABILITY_VOLATILE &&
num_fast_path_readers == 0;
  1. 投递deliver_data_via_iceoryx() 把 GUID/时间戳等填进 iceoryx_header_tiox_pub_publish_chunk;混合场景(同时有远端 reader)则序列化一份走 UDP,chunk 照样发给同机 iceoryx reader(dds_write.c:277-286)。

5.5 读路径:shm_monitor + Listener 回调

每个 domain 持有一个 shm_monitorshm__monitor.h),内部是一个 iceoryx Listener。reader 创建后 attach:

1
2
3
4
5
6
7
8
9
10
11
12
dds_return_t shm_monitor_attach_reader(shm_monitor_t* monitor, struct dds_reader* reader)
{

if(iox_listener_attach_subscriber_event_with_context_data(monitor->m_listener,
reader->m_iox_sub,
SubscriberEvent_DATA_RECEIVED,
shm_subscriber_callback,
&reader->m_iox_sub_context) != ListenerResult_SUCCESS) {
DDS_CLOG(DDS_LC_SHM, &reader->m_rd->e.gv->logconfig, "error attaching reader\n");
return DDS_RETCODE_OUT_OF_RESOURCES;
}
++monitor->m_number_of_attached_readers;

数据到达时,iceoryx 后台线程执行 receive_data_wakeup_handler:循环 iox_sub_take_chunk → 按 chunk 头里的 GUID 反查(proxy)writer → 构造 ddsi_serdata_from_iox(serdata 直接引用 chunk,不拷贝)→ 塞进 reader history cache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while (true)
{
shm_lock_iox_sub(rd->m_iox_sub);
enum iox_ChunkReceiveResult take_result = iox_sub_take_chunk(rd->m_iox_sub, (const void** const)&chunk);
shm_unlock_iox_sub(rd->m_iox_sub);

// NB: If we cannot take the chunk (sample) the user may lose data.
// Since the subscriber queue can overflow and will evict the least recent sample.
...
const iceoryx_header_t* ice_hdr = iceoryx_header_from_chunk(chunk);

// Get writer or proxy writer
struct ddsi_entity_common * e = entidx_lookup_guid_untyped (gv->entity_index, &ice_hdr->guid);
...
// Create struct ddsi_serdata
struct ddsi_serdata* d = ddsi_serdata_from_iox(rd->m_topic->m_stype, ice_hdr->data_kind, &rd->m_iox_sub, chunk);
d->timestamp.v = ice_hdr->tstamp;
d->statusinfo = ice_hdr->statusinfo;
...
ddsi_make_writer_info(&wrinfo, e, xqos, d->statusinfo);
(void)ddsi_rhc_store(rd->m_rd->rhc, &wrinfo, d, tk);

同一 iox_sub 会被应用线程(take/return loan)和 listener 线程并发访问,因此所有操作都包在 shm_lock_iox_sub/shm_unlock_iox_subiox_sub_context_t.mutexddsi_shm_transport.c:33-43)里。


6. dds_loan_sample:应用级零拷贝 API

dds_loan_api.h 暴露三个关键函数(实现在 dds_loan.c):

API 语义
dds_is_shared_memory_available(entity) 该 reader/writer 是否挂上了 iceoryx(m_iox_sub/m_iox_pub != NULL
dds_is_loan_available(entity) 在前者基础上还要求 类型 fixed_size —— 真零拷贝的判定
dds_loan_sample(writer, &sample) 借一个共享内存 sample,用户就地填数据后 dds_write
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dds_return_t dds_loan_sample(dds_entity_t writer, void **sample) {
#ifndef DDS_HAS_SHM
(void)writer;
(void)sample;
return DDS_RETCODE_UNSUPPORTED;
#else
dds_return_t ret;
dds_writer *wr;

if (!sample)
return DDS_RETCODE_BAD_PARAMETER;

if ((ret = dds_writer_lock(writer, &wr)) != DDS_RETCODE_OK)
return ret;

// the loaning is only allowed if SHM is enabled correctly and if the type is
// fixed
if (wr->m_iox_pub && wr->m_topic->m_stype->fixed_size) {
*sample = dds_writer_loan_chunk(wr, wr->m_topic->m_stype->iox_size);
if (*sample == NULL) {
ret = DDS_RETCODE_ERROR; // could not obtain a sample
}
} else {
ret = DDS_RETCODE_UNSUPPORTED;
}
...

writer 内部有一个 m_iox_pub_loans[MAX_PUB_LOANS] 池登记未归还的 loan;dds_writederegister_pub_loan 命中即知道”这块数据本来就在共享内存里”,直接发布,全程零拷贝。未 write 的 loan 可用 dds_return_writer_loan 归还(否则 chunk 泄漏,见 §8)。


7. ROS 2 Humble 链路:rmw_cyclonedds_cpp

7.1 loan 能力探测

创建 publisher/subscription 时,rmw 层把”类型是否 POD(fixed type)”与 cyclonedds 的 loan 能力合并成 can_loan_messages

1
2
3
4
dds_delete_listener(listener);
pub->type_supports = *type_supports;
pub->is_loaning_available = is_fixed_type && dds_is_loan_available(pub->enth);
pub->sample_size = sample_size;

(subscription 同理,rmw_node.cpp:2817;随后赋给 rmw_publisher->can_loan_messages / rmw_subscription->can_loan_messages。)

7.2 借出与发布

rclcppLoanedMessage 最终落到 rmw_borrow_loaned_messageinit_and_alloc_sample(内部走 dds_data_allocator / iceoryx chunk):

1
2
3
4
5
6
7
8
9
10
// if the publisher can loan
if (cdds_publisher->is_loaning_available) {
auto sample_ptr = init_and_alloc_sample(cdds_publisher, cdds_publisher->sample_size);
RET_NULL_X(sample_ptr, return RMW_RET_ERROR);
*ros_message = sample_ptr;
return RMW_RET_OK;
} else {
RMW_SET_ERROR_MSG("Borrowing loan for a non fixed type is not allowed");
return RMW_RET_ERROR;
}

rmw_publish 对 loan 出来的消息直接构造 serdata 并把 chunk 标记为 RAW 后 dds_writecdr

1
2
3
4
5
6
7
8
// if the publisher allow loaning
if (cdds_publisher->is_loaning_available) {
auto d = new serdata_rmw(cdds_publisher->sertype, ddsi_serdata_kind::SDK_DATA);
d->iox_chunk = ros_message;
// since we write the loaned chunk here, set the data state to raw
shm_set_data_state(d->iox_chunk, IOX_CHUNK_CONTAINS_RAW_DATA);
if (dds_writecdr(cdds_publisher->enth, d) >= 0) {
return RMW_RET_OK;

非 POD 类型不能 loan,但 SHM 可用时序列化结果仍可写进共享内存(退化为”一次序列化 + 零网络拷贝”):

1
2
3
4
5
6
7
if (dds_is_shared_memory_available(pub->enth)) {
auto sample_ptr = init_and_alloc_sample(pub, serialized_message->buffer_length);
RET_NULL_X(sample_ptr, return RMW_RET_ERROR);
memcpy(sample_ptr, serialized_message->buffer, serialized_message->buffer_length);
shm_set_data_state(sample_ptr, IOX_CHUNK_CONTAINS_SERIALIZED_DATA);
d->iox_chunk = sample_ptr;
}

7.3 应用层写法(LoanedMessage)

1
2
3
4
// 消息须是 POD/固定大小(如自定义的定长数组图像消息)
auto loaned = publisher->borrow_loaned_message(); // rmw_borrow_loaned_message
loaned.get().data = ...; // 直接写共享内存
publisher->publish(std::move(loaned)); // rmw_publish(loan 路径)

“真零拷贝”的类型条件:ROS 消息经 rosidl 映射后必须固定大小——不能含 string、动态数组/sequence。像 sensor_msgs/msg/Image(含 string encoding 和动态 data)不满足,只能走”序列化进共享内存”的退化路径;工程上常用定长自定义消息规避。


8. 限制与常见坑

现象 源码依据 / 对策
QoS 不满足 静默退回网络路径,性能与预期不符 dds_writer_support_shm/dds_reader_support_shm:必须 KEEP_LAST、VOLATILE 或 TRANSIENT_LOCAL(且 TRANSIENT_LOCAL 的 durability_service 深度受 iceoryx history 上限约束,dds_writer.c:331 起);reader 不能 ignore_local
非固定大小类型 can_loan_messages == false,loan 接口报错 dds_is_loan_available 要求 fixed_size;数据仍可能经 serialize_into 进共享内存,但多一次序列化
iox-roudi 没起 进程启动时 iceoryx runtime 注册失败:反复打印等待 RouDi 的日志,超时后 iceoryx errorHandler 直接终止进程(不会优雅退回 UDP) iox_runtime_initq_init.c:1098)无失败返回路径;iceoryx 2.0 的 PoshRuntime 等待 RouDi 超时即 terminate(默认等待时长约 60s,待验证)。对策:SharedMemory/Enable=true 时必须保证 RouDi 先行启动
mempool 尺寸不匹配 dds_loan_sample / dds_write 返回 DDS_RETCODE_ERROR/OUT_OF_RESOURCES,iceoryx 日志报无合适 chunk shm_create_chunk 重试 10 次后返回 NULL;需在 RouDi 的 TOML 里配置足够大/足够多的 mempool(chunk 大小 ≥ payload + sizeof(iceoryx_header_t) + ChunkHeader 开销)
订阅队列溢出丢数据 高频发布 + 消费慢时旧样本被驱逐 shm_monitor.c:106-109 注释明示;iceoryx 队列深度上限(MAX_SUBSCRIBER_QUEUE_CAPACITY=256)也约束了 reader 的有效 history 深度
持有 chunk 过多 日志 TOO_MANY_CHUNKS_HELD_IN_PARALLEL 应用长期持有 take 出的 loaned message 导致;及时归还(dds_return_loan / 析构 LoanedMessage
跨主机误判 无(按 MAC 判定) 容器/虚拟网卡环境 MAC 可能相同或取不到,必要时用 SharedMemory/Locator 显式指定
一写多读混合 同 topic 既有同机 SHM reader 又有远端 reader 时仍会序列化 use_only_iceoryx 条件苛刻(无网络 reader + VOLATILE + 无快路径本地 reader),属预期行为

9. 快速验证清单

1
2
3
4
5
6
7
8
9
# 1. 确认 cyclonedds 编译带 SHM(ROS 2 Humble 二进制默认已启用 iceoryx 支持)
ldd $(ros2 pkg prefix rmw_cyclonedds_cpp)/lib/librmw_cyclonedds_cpp.so | grep iceoryx

# 2. 打开 SHM 日志观察是否真的走了共享内存
export CYCLONEDDS_URI='<CycloneDDS><Domain><SharedMemory><Enable>true</Enable><LogLevel>verbose</LogLevel></SharedMemory><Tracing><Verbosity>config</Verbosity><OutputFile>stdout</OutputFile></Tracing></Domain></CycloneDDS>'
# 日志中应出现 "My iceoryx address: ..."、"Writer's topic name will be DDS:Cyclone:<topic>"

# 3. 用 iceoryx 内省工具确认端口已注册(见 08 篇)
iox-introspection-client --all

下一篇

08-示例与性能工具.md:iceoryx_examples 代表性示例走读、iceperf 基准方法与排障工具。

文章互动

阅读 --

留言

0 条留言

正在加载留言…