iceoryx源码阅读(二)——共享内存管理
0 导引
本系列文章对iceoryx源码进行解读,索引如下:
-
iceoryx源码阅读(六)——共享内存创建
-
iceoryx源码阅读(七)——服务发现机制
-
iceoryx源码阅读(九)——等待与通知机制
基于共享内存通信的核心在于共享内存的管理,包括共享内存的分配、释放。
1 共享内存模型
iceoryx先将整块共享内存粗略划分为多个MemPool
,每个MemPool
进一步划分为若干个相同大小的Chunk
。每个MemPool
中的Chunk
数量及大小可以由用户配置,也可以使用默认配置。iceoryx中分配、回收共享内存的单位是Chunk
,这种内存分配方式借鉴Linux内核中的Slab
机制。共享内存的具体划分如下图所示:
其中,FreeIndices
用于存放空闲Chunk
索引的队列,以便于内存分配和释放。从上图中,共享内存管理的三级结构依次为MemoryManager
→ MemPool
→ Chunk
。
2 获取共享内存
2.1 MemoryManager::getChunk
职责:
获取指定大小的Chunk
(裸指针),并包装为SharedChunk
。
返回:
SharedChunk
实例,类似智能指针,可以在适当的是好释放Chunk
。
expected<SharedChunk, MemoryManager::Error> MemoryManager::getChunk(const ChunkSettings& chunkSettings) noexcept
{
void* chunk{nullptr};
MemPool* memPoolPointer{nullptr};
const auto requiredChunkSize = chunkSettings.requiredChunkSize();
uint32_t aquiredChunkSize = 0U;
for (auto& memPool : m_memPoolVector)
{
uint32_t chunkSizeOfMemPool = memPool.getChunkSize();
if (chunkSizeOfMemPool >= requiredChunkSize)
{
chunk = memPool.getChunk();
memPoolPointer = &memPool;
aquiredChunkSize = chunkSizeOfMemPool;
break;
}
}
if (m_memPoolVector.size() == 0)
{
IOX_LOG(FATAL) << "There are no mempools available!";
errorHandler(iox::PoshError::MEPOO__MEMPOOL_GETCHUNK_CHUNK_WITHOUT_MEMPOOL, ErrorLevel::SEVERE);
return err(Error::NO_MEMPOOLS_AVAILABLE);
}
else if (memPoolPointer == nullptr)
{
IOX_LOG(FATAL) << "The following mempools are available:" << [this](auto& log) -> iox::log::LogStream& {
this->printMemPoolVector(log);
return log;
} << "Could not find a fitting mempool for a chunk of size "
<< requiredChunkSize;
errorHandler(iox::PoshError::MEPOO__MEMPOOL_GETCHUNK_CHUNK_IS_TOO_LARGE, ErrorLevel::SEVERE);
return err(Error::NO_MEMPOOL_FOR_REQUESTED_CHUNK_SIZE);
}
else if (chunk == nullptr)
{
IOX_LOG(ERROR) << "MemoryManager: unable to acquire a chunk with a chunk-payload size of "
<< chunkSettings.userPayloadSize()
<< "The following mempools are available:" << [this](auto& log) -> iox::log::LogStream& {
this->printMemPoolVector(log);
return log;
};
errorHandler(iox::PoshError::MEPOO__MEMPOOL_GETCHUNK_POOL_IS_RUNNING_OUT_OF_CHUNKS, ErrorLevel::MODERATE);
return err(Error::MEMPOOL_OUT_OF_CHUNKS);
}
else
{
auto chunkHeader = new (chunk) ChunkHeader(aquiredChunkSize, chunkSettings);
auto chunkManagement = new (m_chunkManagementPool.front().getChunk())
ChunkManagement(chunkHeader, memPoolPointer, &m_chunkManagementPool.front());
return ok(SharedChunk(chunkManagement));
}
}
逐段代码分析:
- LINE 09 ~ LINE 19: 查找第一个
Chunk
大小超过应用所需大小的MemPool
,调用其成员方法getChunk
获取空闲Chunk
指针(具体实现在下节)。 - LINE 21 ~ LINE 50: 错误处理。
- LINE 53 ~ LINE 56: 创建
Chunk
管理相关对象,关于ChunkHeader
和ChunkManagement
的构成详见下一篇文章。
2.2 MemPool::getChunk
职责:
从MemPool
中获取未使用的Chunk
。
返回:
指向该Chunk
的指针。
void* MemPool::getChunk() noexcept
{
uint32_t l_index{0U};
if (!m_freeIndices.pop(l_index))
{
IOX_LOG(WARN) << "Mempool [m_chunkSize = " << m_chunkSize << ", numberOfChunks = " << m_numberOfChunks
<< ", used_chunks = " << m_usedChunks << " ] has no more space left";
return nullptr;
}
/// @todo iox-#1714 verify that m_usedChunk is not changed during adjustMInFree
/// without changing m_minFree
m_usedChunks.fetch_add(1U, std::memory_order_relaxed);
adjustMinFree();
return m_rawMemory.get() + l_index * m_chunkSize;
}
整体代码分析
上述代码逻辑很简单,首先从m_freeIndices
中弹出一个空闲Chunk
的索引,正在使用的Chunk数量增1,根据索引计算并返回Chunk
指针。
3 释放共享内存
3.1 SharedChunk::freeChunk
iceoryx中使用引用计数的方式对Chunk
进行管理,当引用计数为0时,释放Chunk
。下面的代码就是释放Chunk
,详见下文。
职责:
释放消息Chunk
和计数Chunk
,与智能指针设计如出一辙。
void SharedChunk::freeChunk() noexcept
{
m_chunkManagement->m_mempool->freeChunk(static_cast<void*>(m_chunkManagement->m_chunkHeader.get()));
m_chunkManagement->m_chunkManagementPool->freeChunk(m_chunkManagement);
m_chunkManagement = nullptr;
}
整体代码分析
这部分代码比较简单,这里不赘述了。
3.2 MemPool::freeChunk
职责:
从MemPool
中获取未使用的Chunk
。
返回:
指向该Chunk
的指针。
void MemPool::freeChunk(const void* chunk) noexcept
{
cxx::Expects(m_rawMemory.get() <= chunk
&& chunk <= m_rawMemory.get() + (static_cast<uint64_t>(m_chunkSize) * (m_numberOfChunks - 1U)));
auto offset = static_cast<const uint8_t*>(chunk) - m_rawMemory.get();
cxx::Expects(offset % m_chunkSize == 0);
uint32_t index = static_cast<uint32_t>(offset / m_chunkSize);
if (!m_freeIndices.push(index))
{
errorHandler(PoshError::POSH__MEMPOOL_POSSIBLE_DOUBLE_FREE);
}
m_usedChunks.fetch_sub(1U, std::memory_order_relaxed);
}
整体代码分析
根据相对首地址偏移和Chunk
大小计算索引,将索引推入空闲索引队列m_freeIndices
中,正在使用的Chunk
数减1。
4 SharedChunk
4.1 Chunk管理结构
iceoryx使用引用计数的方式实现对共享内存进行自动管理,设计与C++中的智能指针一致。SharedChunk用于封装管理结构块,管理结构块指向消息Chunk
,结构如下图所示:
其中,ChunkManagement中的成员含义如下:
4.2 SharedChunk实现
SharedChunk
非常重要,所以本节介绍其具体实现。
4.2.1 数据成员
SharedChunk
只有一个数据成员m_chunkManagement
,带参构造函数对其进行赋值。
class SharedChunk
{
public:
SharedChunk(ChunkManagement* const resource) noexcept;
private:
ChunkManagement* m_chunkManagement{nullptr};
};
ChunkManagement
由4个部分组成,这里需要指出的是,其引用计数初始值为1,意味着构建该对象时有一个引用。
struct ChunkManagement
{
referenceCounter_t m_referenceCounter{1U};
};
4.2.2 引用计数的维护
引用计数的维护主要涉及拷贝构造函数和赋值操作符,移动构造函数和移动赋值操作符,析构函数。具体如下所示:
SharedChunk::SharedChunk(const SharedChunk& rhs) noexcept
{
*this = rhs;
}
SharedChunk::SharedChunk(SharedChunk&& rhs) noexcept
{
*this = std::move(rhs);
}
SharedChunk::~SharedChunk() noexcept
{
decrementReferenceCounter();
}
SharedChunk& SharedChunk::operator=(const SharedChunk& rhs) noexcept
{
if (this != &rhs)
{
decrementReferenceCounter();
m_chunkManagement = rhs.m_chunkManagement;
incrementReferenceCounter();
}
return *this;
}
SharedChunk& SharedChunk::operator=(SharedChunk&& rhs) noexcept
{
if (this != &rhs)
{
decrementReferenceCounter();
m_chunkManagement = std::move(rhs.m_chunkManagement);
rhs.m_chunkManagement = nullptr;
}
return *this;
}
void SharedChunk::incrementReferenceCounter() noexcept
{
if (m_chunkManagement != nullptr)
{
m_chunkManagement->m_referenceCounter.fetch_add(1U, std::memory_order_relaxed);
}
}
void SharedChunk::decrementReferenceCounter() noexcept
{
if ((m_chunkManagement != nullptr)
&& (m_chunkManagement->m_referenceCounter.fetch_sub(1U, std::memory_order_relaxed) == 1U))
{
freeChunk();
}
}
这里重点讲解一下赋值操作符和移动赋值操作符:
- 赋值操作符:改变指向,当前对象引用计数降1,新对象引用计数增1。
- 移动赋值操作符:不再指向当前对象,故引用计数降1,新引用计数不变(转移语义)。
5 总结
本文学习了底层共享内存的分配、释放及管理结构,下文介绍基于共享内存的通信。