iceoryx源码阅读(二)——共享内存管理

0 导引

本系列文章对iceoryx源码进行解读,索引如下:

基于共享内存通信的核心在于共享内存的管理,包括共享内存的分配、释放。

1 共享内存模型

iceoryx先将整块共享内存粗略划分为多个MemPool,每个MemPool进一步划分为若干个相同大小的Chunk。每个MemPool中的Chunk数量及大小可以由用户配置,也可以使用默认配置。iceoryx中分配、回收共享内存的单位是Chunk,这种内存分配方式借鉴Linux内核中的Slab机制。共享内存的具体划分如下图所示:

image

其中,FreeIndices用于存放空闲Chunk索引的队列,以便于内存分配和释放。从上图中,共享内存管理的三级结构依次为MemoryManagerMemPoolChunk

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管理相关对象,关于ChunkHeaderChunkManagement的构成详见下一篇文章。

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,结构如下图所示:

image

其中,ChunkManagement中的成员含义如下:

image

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 总结

本文学习了底层共享内存的分配、释放及管理结构,下文介绍基于共享内存的通信。

posted @ 2024-05-10 00:49  爱新觉罗·L  阅读(355)  评论(0编辑  收藏  举报