本章深入讲解 iceoryx 在实现零拷贝进程间通信时的内存管理机制。内容涵盖共享内存的架构与布局、MePoo(内存池集合)、Chunk(数据块)头与生命周期、分配策略(包括 BumpAllocator)、以及 RouDi 与参与进程之间如何协调内存访问与通知。由于内容较多,分为三次介绍。
学习目标:
- 掌握 MePoo 的结构以及如何计算 MePoo 的总占用(
requiredFullMemorySize())。 - 跟踪创建并映射共享内存的代码路径(
shm_open→ftruncate→mmap)。 - 理解 Chunk 的生命周期:分配(allocate)、借出(loan)、发布(publish)与释放(free)。
- 学会使用 C/C++ API 创建内存池、分配 Chunk 并处理运行时错误。
4.1 内存角色与设计目标
iceoryx 以高性能与确定性为设计目标,使用共享内存作为数据平面以实现零拷贝通信。为支持这一点,系统分为三个逻辑平面(详见第三章):
- 控制平面:用于 RouDi 与参与者之间的元数据与管理消息交换(在 Linux 上通常通过 Unix Domain Sockets 实现)。控制平面不承载数据块本身。
- 通知平面:用于唤醒或通知订阅者(通常使用 unnamed semaphore 或条件通知器)。
- 数据平面:存放实际数据的共享内存区域(MePoo)。
设计约束包括:
- 启动时要预分配内存并完成分区,避免运行时进行不可预测的动态分配。
- Chunk 的分配与释放需要尽量无锁或使用轻量原语,以满足实时系统的需求。
- 共享内存内不能使用进程间的绝对指针,使用相对指针(RelativePointer)以保证跨进程地址可重定位。
4.2 Segment 与 MemPool(内存池)
4.2.0 术语说明
iceoryx 的内存管理采用两层结构。在深入细节之前,需要先理解几个容易混淆的术3语。
在代码和文档中经常出现 "MePoo" 这个词,它是 Mem ory Pool 的缩写,而不是一个独立的架构层级。相关的类和结构体包括:
MePooConfig:配置结构,用于定义多个内存池的参数(大小和数量)MePooSegment:共享内存段类,封装了共享内存对象和内存管理器MemoryManager:内存管理器,负责创建和管理多个 MemPool 实例MemPool:内存池类,管理固定大小的 Chunk
类关系图:
封装
封装
使用配置
管理
读取配置
1 0..* MePooConfig
+vector<Entry> m_mempoolConfig
+addMemPool(Entry entry)
MePooSegment
-PosixSharedMemoryObject m_sharedMemoryObject
-MemoryManager m_memoryManager
-PosixGroup m_readerGroup
-PosixGroup m_writerGroup
+getMemoryManager() : MemoryManager
+getSegmentSize() : uint64_t
MemoryManager
-vector<MemPool> m_memPoolVector
+configureMemoryManager(MePooConfig)
+addMemPool(...)
+getChunk(ChunkSettings) : SharedChunk
MemPool
-uint32_t m_chunkSize
-uint32_t m_chunkCount
-MpmcLoFFLi m_freeIndices
-Atomic<uint32_t> m_usedChunks
+getChunk() : void
+freeChunk(void*)
PosixSharedMemoryObject
配置层:定义内存池参数
容器层:封装共享内存和管理器
管理层:统一管理多个内存池
执行层:管理固定大小的 Chunk
关系说明:
- 组合关系(*--) :
MePooSegment拥有PosixSharedMemoryObject和MemoryManager,它们的生命周期绑定 - 依赖关系(...>) :
MePooSegment和MemoryManager在初始化时读取MePooConfig的配置 - 聚合关系(*--) :
MemoryManager管理多个MemPool实例
4.2.1 两层架构
iceoryx 的内存管理实际上只有两层:Segment(共享内存段)和 MemPool(内存池)。
Segment(共享内存段)
Segment 是物理容器,对应一个独立的 POSIX 共享内存对象。在 TOML 配置文件中,每个 [[segment]] 块定义一个 Segment。
创建过程:
Segment 通过 shm_open() 系统调用创建,生成一个共享内存文件(如 /dev/shm/iceoryx_segment_0)。每个 Segment 可以设置独立的访问权限,包括 reader 组和 writer 组,这为多租户场景下的安全隔离提供了基础。
封装实现:
在代码中,Segment 由 MePooSegment 模板类实例化。所谓"封装"是指这个类将底层的共享内存对象和内存管理器组合在一起,作为成员变量:
cpp
template <typename SharedMemoryObjectType, typename MemoryManagerType>
class MePooSegment {
SharedMemoryObjectType m_sharedMemoryObject; // 封装的共享内存对象
MemoryManagerType m_memoryManager; // 封装的内存管理器
PosixGroup m_readerGroup; // reader 访问组
PosixGroup m_writerGroup; // writer 访问组
};
这里的 m_sharedMemoryObject 成员封装了 PosixSharedMemoryObject,它负责:
- 调用
shm_open()创建共享内存 - 调用
ftruncate()设置大小 - 调用
mmap()映射到进程地址空间 - 提供
getBaseAddress()方法获取映射地址 - 在析构时自动
munmap()和shm_unlink()
而 MePooSegment 则在更高层次上:
- 根据配置计算所需的共享内存大小
- 设置 POSIX ACL 访问权限(reader/writer 组)
- 在共享内存中初始化
MemoryManager - 注册相对指针基址,使跨进程引用成为可能
一个 Segment 内部包含多个 MemPool,这些 MemPool 由 MemoryManager 统一管理。
MemPool(内存池)
MemPool 是逻辑管理单元,每个池负责管理固定大小的 Chunk。在 TOML 配置中,每个 [[segment.mempool]] 块定义一个 MemPool,指定 Chunk 的大小(size)和数量(count)。
例如,一个典型的配置可能包含三个池:256B 池、2KB 池和 8KB 池。当应用请求分配 Chunk 时,MemoryManager 会选择能容纳该大小的最小池进行分配,这种策略可以有效减少内存碎片。
层级关系
Segment(共享内存段)
├─ MemoryManager(管理多个 MemPool)
│ ├─ MemPool 0(256B × 128 个 Chunk)
│ ├─ MemPool 1(2KB × 64 个 Chunk)
│ └─ MemPool 2(8KB × 16 个 Chunk)
└─ 共享内存数据区
4.2.2 配置示例
以下是一个完整的 TOML 配置示例,展示了如何定义 Segment 和 MemPool:
toml
[[segment]] # 定义一个共享内存段
[[segment.mempool]] # 该段内的第一个内存池
size = 256 # Chunk 大小 256 字节
count = 128 # 数量 128 个
[[segment.mempool]] # 该段内的第二个内存池
size = 2048 # Chunk 大小 2 KiB
count = 64 # 数量 64 个
[[segment.mempool]] # 该段内的第三个内存池
size = 8192 # Chunk 大小 8 KiB
count = 16 # 数量 16 个
这个配置创建了一个 Segment,其中 MemoryManager 管理三个不同大小的 MemPool。
4.2.3 代码结构
iceoryx 中与内存池相关的主要类结构如下:
cpp
// MePooConfig:配置结构,定义多个内存池的参数
struct MePooConfig {
vector<Entry, MAX_NUMBER_OF_MEMPOOLS> m_mempoolConfig;
void addMemPool(Entry entry);
};
// MePooSegment:共享内存段类
template <typename SharedMemoryObjectType, typename MemoryManagerType>
class MePooSegment {
SharedMemoryObjectType m_sharedMemoryObject; // POSIX 共享内存对象
MemoryManagerType m_memoryManager; // 内存管理器
};
// MemoryManager:管理多个 MemPool 的分配和回收
class MemoryManager {
vector<MemPool, MAX_NUMBER_OF_MEMPOOLS> m_memPoolVector;
void addMemPool(...);
expected<SharedChunk, Error> getChunk(const ChunkSettings& settings);
};
4.2.4 完整分层视图
从配置文件到实际内存布局的完整流程包含四个层次:
💾 存储层(Chunk 数据)
🎛️ MemPool 层(内存池管理)
🗂️ Segment 层(共享内存段)
📝 配置层(TOML)
内存池实例
解析配置
初始化
映射到
映射到
映射到
创建池
roudi_config.toml
────────────────
\[segment\]
\[segment.mempool\]
size = 256
count = 128
\[segment.mempool\]
size = 2048
count = 64
MePooSegment
────────────────
/dev/shm/iceoryx_segment_0
PosixSharedMemoryObject
────────────────
• shm_open()
• ftruncate()
• mmap()
访问控制
────────────────
• Reader Group
• Writer Group
• POSIX ACL
MemoryManager
────────────────
统一管理多个 MemPool
MemPool 0
────────────
256 B × 128
────────────
✓ LoFFLi 无锁链表
✓ 原子计数器
✓ CAS 操作
MemPool 1
────────────
2 KiB × 64
────────────
✓ LoFFLi 无锁链表
✓ 原子计数器
✓ CAS 操作
MemPool 2
────────────
8 KiB × 16
────────────
✓ LoFFLi 无锁链表
✓ 原子计数器
✓ CAS 操作
共享内存物理布局
总大小:约 295 KiB
📦 Pool 0 区域 32,768 字节
元数据 128 B
Chunk 0
256 B
Chunk 1
256 B
... 126 个 Chunk
📦 Pool 1 区域 131,072 字节
元数据 256 B
Chunk 0
2 KiB
Chunk 1
2 KiB
... 62 个 Chunk
📦 Pool 2 区域 131,072 字节
元数据 128 B
Chunk 0
8 KiB
... 15 个 Chunk
层次说明:
- 配置层:TOML 文件定义 Segment 和 MemPool 的参数
- Segment 层 :
MePooSegment封装共享内存对象和访问控制 - MemPool 层 :
MemoryManager管理多个不同大小的 MemPool - 存储层:共享内存中的实际物理布局,包含所有 Chunk 数据
数据流:
- 配置解析 → Segment 创建 → MemoryManager 初始化 → MemPool 实例化 → 物理内存映射
4.2.5 MemPool 的内部组成
每个 MemPool 在物理上包含两个主要部分:
管理元数据
MemPool 负责维护自己的状态信息:
- LoFFLi (Lock-Free Free List) :使用无锁空闲链表(MpmcLoFFLi)记录每个 Chunk 的空闲/占用状态
- 每个节点只占 8 字节(索引 + ABA 计数器)
- 支持多生产者多消费者(MPMC)的无锁并发访问
- 使用 CAS (Compare-And-Swap) 原子操作保证线程安全
- 统计计数器:原子计数器追踪已用 Chunk 数和历史最小空闲数
- 元数据结构紧凑,通常只占用几百字节
数据存储区
存储区包含所有 Chunk 的实际数据:
- Chunk 按固定大小连续排列,便于通过索引快速定位
- 每个 Chunk 包含头部(ChunkHeader)和用户数据区(Payload)
- 使用相对指针(RelativePointer)进行跨进程引用,避免绝对地址失效
4.2.6 Segment 大小计算
Segment 的总大小等于其内部所有 MemPool 的大小之和。每个 MemPool 的大小由 MemoryManager::requiredFullMemorySize() 方法计算,包括以下部分:
- 管理元数据开销:包括位图、free-list 头节点、控制结构等,按平台对齐要求对齐
- Chunk 数据区大小 :
number_of_chunks × (aligned_chunk_size + chunk_header_size)
设计考虑
每个 Segment (MePooSegment) 配置一个 MemoryManager 实例,该 MemoryManager 统一管理该 Segment 内的所有 MemPool。这种设计具有以下优势:
- 统一管理 :MemoryManager 维护一个
vector<MemPool>(m_memPoolVector),集中管理多个不同大小的内存池 - 智能分配:根据请求的 Chunk 大小,MemoryManager 自动选择最合适的 MemPool 进行分配
- 有序配置:MemPool 必须按 Chunk 大小递增顺序添加,便于二分查找最优池
- 元数据分离:每个 MemPool 独立管理自己的 LoFFLi 空闲链表,避免跨大小类的碎片问题
计算示例
假设配置一个包含 100 个 Chunk 的 MemPool,参数如下:
- Payload 大小:1024 字节
- Chunk 头部:32 字节
- 对齐要求:8 字节
计算过程:
- 单个 Chunk 原始大小:32 + 1024 = 1056 字节
- 对齐后大小:roundUp(1056, 8) = 1056 字节
- 所有 Chunk 总大小:100 × 1056 = 105,600 字节
- 加上 MemoryManager 元数据(假设 256 字节)
- 最终大小:105,856 字节(对齐后约 106 KB)
实际实现中,requiredFullMemorySize() 会自动处理所有对齐和填充计算。
4.3 共享内存创建流程
RouDi 在系统启动时负责创建和初始化共享内存段。整个流程基于 POSIX 共享内存机制,通过系统调用序列 shm_open → ftruncate → mmap 完成。
4.3.1 创建流程图
失败
成功
失败
成功
失败
成功
RouDi 启动
步骤 1:计算总大小
遍历所有 MemPool 配置
调用 requiredFullMemorySize
计算每个池所需空间
汇总:元数据 + Chunk 数据区
- 对齐填充
得到 totalSize
步骤 2:创建共享内存对象
shm_open('/iceoryx_segment_0',
O_CREAT | O_RDWR, 0600)
fd 有效?
错误:名称冲突
或权限不足
获得文件描述符 fd
步骤 3:设置大小
ftruncate(fd, totalSize)
设置成功?
错误:空间不足
或配额限制
共享内存大小已设置
步骤 4:映射到进程地址空间
mmap(nullptr, totalSize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)
映射成功?
错误:地址空间不足
或权限问题
获得映射地址 addr
步骤 5:初始化数据结构
使用 BumpAllocator
划分内存区域
placement new 创建
MemoryManager 实例
初始化每个 MemPool:
• LoFFLi 空闲链表
• 原子计数器
• ChunkHeader
注册相对指针基址
共享内存创建完成
创建失败
流程说明:
- 计算阶段:遍历配置,累加所有 MemPool 的空间需求
- 创建阶段:通过 POSIX API 创建共享内存对象
- 配置阶段:设置共享内存大小并映射到当前进程
- 初始化阶段:在共享内存中构建数据结构
- 错误处理:每个关键步骤都有失败检查和错误分支
4.3.2 创建步骤详解
步骤 1:计算总大小
首先汇总所有 MemPool 的 requiredFullMemorySize,加上全局头部和对齐填充,得到 Segment 的总大小 totalSize。
步骤 2:创建共享内存对象
调用 shm_open() 创建或打开共享内存对象,返回文件描述符。在 Linux 系统上,这会在 /dev/shm/ 目录下创建一个文件。
cpp
int fd = shm_open("/iceoryx_segment_0", O_CREAT | O_RDWR, 0600);
步骤 3:设置大小
使用 ftruncate() 将共享内存对象的大小设置为计算出的 totalSize:
cpp
ftruncate(fd, totalSize);
步骤 4:映射到进程地址空间
通过 mmap() 将共享内存映射到当前进程的地址空间:
cpp
void* addr = mmap(nullptr, totalSize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
步骤 5:初始化数据结构
在映射的内存区域上使用 placement new 初始化各种控制结构:
- MemoryManager 实例
- 每个 MemPool 的 LoFFLi (Lock-Free Free List)
- Chunk 头部
4.3.3 实现位置
相关代码分布在以下文件中:
posix_memory_map.cpp:封装了mmap()、shm_open()等底层系统调用memory_provider.cpp:提供内存分配和初始化的高层接口mempool_collection_memory_block.cpp:实现size()方法,计算所需总大小
(待续)