iceoryx源码阅读(六)——共享内存创建

从本文开始,我们开始讨论Roudi进程相关逻辑。我们先从共享内存的创建开始。

1 共享内存的组织

为了管理共享内存的分配,相关类图如下:

image

RouDiMemoryInterface为iceoryx为内存管理抽象出来的接口,对外暴露的方法主要有两类:

  • 创建、销毁(共享)内存;
  • 返回各类共享内存管理器。

iceoryx提供了一套实现,即IceOryxRouDiMemoryManager,Roudi App的其他模块只需要使用接口即可,以实现共享内存分配的可定制化。下面来看看,iceoryx中共享内存管理的默认实现。

2 共享内存创建

Iceoryx默认实现中,共享内存管理由三个层次组成,如图中的绿色背景所示,含义如下:

  • RoudiMemoryManager:所有内存区域;
  • MemoryProvider:单个内存区域,
  • MemoryBlock:单个内存块

共享内存创建流程从IceOryxRouDiMemoryManager::createAndAnnounceMemory开始,依次调用上述三个类型的相应方法,实现共享内存的创建和切分。

2.1 IceOryxRouDiMemoryManager::createAndAnnounceMemory

cxx::expected<RouDiMemoryManagerError> IceOryxRouDiMemoryManager::createAndAnnounceMemory() noexcept
{
    auto result = m_memoryManager.createAndAnnounceMemory();
    auto portPool = m_portPoolBlock.portPool();
    if (!result.has_error() && portPool.has_value())
    {
        m_portPool.emplace(*portPool.value());
    }
    return result;
}

可以看到,只是简单地调用了RoudiMemoryManager的同名成员函数。

2.2 RouDiMemoryManager::createAndAnnounceMemory

cxx::expected<RouDiMemoryManagerError> RouDiMemoryManager::createAndAnnounceMemory() noexcept
{
    if (m_memoryProvider.empty())
    {
        return cxx::error<RouDiMemoryManagerError>(RouDiMemoryManagerError::NO_MEMORY_PROVIDER_PRESENT);
    }

    for (auto memoryProvider : m_memoryProvider)
    {
        auto result = memoryProvider->create();
        if (result.has_error())
        {
            LogError() << "Could not create memory: MemoryProviderError = "
                       << MemoryProvider::getErrorString(result.get_error());
            return cxx::error<RouDiMemoryManagerError>(RouDiMemoryManagerError::MEMORY_CREATION_FAILED);
        }
    }

    for (auto memoryProvider : m_memoryProvider)
    {
        memoryProvider->announceMemoryAvailable();
    }

    return cxx::success<>();
}

核心逻辑就是依次调用MemoryProvider的create方法和announceMemoryAvailable方法。m_memoryProvider是一个容器,我们将在第三节讲解其组成。

2.3 MemoryProvider::create

终于到了创建共享内存的逻辑了,下面来看看这个方法的实现:

cxx::expected<MemoryProviderError> MemoryProvider::create() noexcept
{
    if (m_memoryBlocks.empty())
    {
        return cxx::error<MemoryProviderError>(MemoryProviderError::NO_MEMORY_BLOCKS_PRESENT);
    }

    if (isAvailable())
    {
        return cxx::error<MemoryProviderError>(MemoryProviderError::MEMORY_ALREADY_CREATED);
    }

    uint64_t totalSize = 0u;
    uint64_t maxAlignment = 1;
    for (auto* memoryBlock : m_memoryBlocks)
    {
        auto alignment = memoryBlock->alignment();
        if (alignment > maxAlignment)
        {
            maxAlignment = alignment;
        }

        // just in case the memory block doesn't calculate its size as multiple of the alignment
        // this shouldn't be necessary, but also doesn't harm
        auto size = cxx::align(memoryBlock->size(), alignment);
        totalSize = cxx::align(totalSize, alignment) + size;
    }

    auto memoryResult = createMemory(totalSize, maxAlignment);

    if (memoryResult.has_error())
    {
        return cxx::error<MemoryProviderError>(memoryResult.get_error());
    }

    m_memory = memoryResult.value();
    m_size = totalSize;
    auto maybeSegmentId = memory::UntypedRelativePointer::registerPtr(m_memory, m_size);

    if (!maybeSegmentId.has_value())
    {
        errorHandler(PoshError::MEMORY_PROVIDER__INSUFFICIENT_SEGMENT_IDS);
    }
    m_segmentId = maybeSegmentId.value();

    LogDebug() << "Registered memory segment " << iox::log::hex(m_memory) << " with size " << m_size << " to id "
               << m_segmentId;

    iox::posix::Allocator allocator(m_memory, m_size);

    for (auto* memoryBlock : m_memoryBlocks)
    {
        memoryBlock->m_memory = allocator.allocate(memoryBlock->size(), memoryBlock->alignment());
    }

    return cxx::success<void>();
}

核心逻辑分为四段,如下:

  • LINE 13 ~ LINE 27:遍历MemoryBlock,计算所需共享内存的字节数和对齐字节数;
  • LINE 29 ~ LINE 29:调用子类,即:PosixShmMemoryProvidercreateMemory实现,这里应该就是设计模式中的模板方法模式;
  • LINE 38 ~ LINE 44:将得到的共享内存首地址注册到一个map中,key作为返回的id,正如前面文章中,在Producer、Consumer和RoudiApp之间就是通过id和偏移量来定位共享内存中的数据。
  • LINE 49 ~ LINE 54:遍历MemoryBlock,实现对整块共享内存的切分。

MemoryBlock类是对共享内存的进一步切分,与本节共享内存分配的主题关系不是很密切,且内容较多,我们放在第三部分进行讲解。接下来,我们主要讲解子类PosixShmMemoryProvidercreateMemory的实现。

2.4 PosixShmMemoryProvider::createMemory

顾名思义,PosixShmMemoryProvider类是共享内存的提供者,createMemory成员函数实现如下:

cxx::expected<void*, MemoryProviderError> PosixShmMemoryProvider::createMemory(const uint64_t size,
                                                                               const uint64_t alignment) noexcept
{
    if (alignment > posix::pageSize())
    {
        return cxx::error<MemoryProviderError>(MemoryProviderError::MEMORY_ALIGNMENT_EXCEEDS_PAGE_SIZE);
    }

    if (!posix::SharedMemoryObjectBuilder()
             .name(m_shmName)
             .memorySizeInBytes(size)
             .accessMode(m_accessMode)
             .openMode(m_openMode)
             .permissions(SHM_MEMORY_PERMISSIONS)
             .create()
             .and_then([this](auto& sharedMemoryObject) {
                 sharedMemoryObject.finalizeAllocation();
                 m_shmObject.emplace(std::move(sharedMemoryObject));
             }))
    {
        return cxx::error<MemoryProviderError>(MemoryProviderError::MEMORY_CREATION_FAILED);
    }

    auto baseAddress = m_shmObject->getBaseAddress();
    if (baseAddress == nullptr)
    {
        return cxx::error<MemoryProviderError>(MemoryProviderError::MEMORY_CREATION_FAILED);
    }

    return cxx::success<void*>(baseAddress);
}

逻辑很简单:

  • LINE 9 ~ LINE 22:调用SharedMemoryObjectBuilder::create方法,创建共享内存,并返回SharedMemoryObject对象,将其存到成员变量m_shmObject中。
  • LINE 24 ~ LINE 30:通过SharedMemoryObject获取共享内存裸指针并返回。

2.5 SharedMemoryObjectBuilder::create

到目前为止,我们还没看到调用系统调用来分配共享内存的代码,不过很快了。可以看到,共享内存的创建使用了层层包装。SharedMemoryObjectBuilder的成员函数create用于创建SharedMemoryObject对象,具体代码如下:

cxx::expected<SharedMemoryObject, SharedMemoryObjectError> SharedMemoryObjectBuilder::create() noexcept
{
    auto printErrorDetails = [this] {
        auto logBaseAddressHint = [this](log::LogStream& stream) noexcept -> log::LogStream& {
            if (this->m_baseAddressHint)
            {
                stream << iox::log::hex(this->m_baseAddressHint.value());
            }
            else
            {
                stream << " (no hint set)";
            }
            return stream;
        };

        IOX_LOG(ERROR) << "Unable to create a shared memory object with the following properties [ name = " << m_name
                       << ", sizeInBytes = " << m_memorySizeInBytes
                       << ", access mode = " << asStringLiteral(m_accessMode)
                       << ", open mode = " << asStringLiteral(m_openMode)
                       << ", baseAddressHint = " << logBaseAddressHint
                       << ", permissions = " << iox::log::oct(static_cast<mode_t>(m_permissions)) << " ]";
    };

    auto sharedMemory = SharedMemoryBuilder()
                            .name(m_name)
                            .accessMode(m_accessMode)
                            .openMode(m_openMode)
                            .filePermissions(m_permissions)
                            .size(m_memorySizeInBytes)
                            .create();

    if (!sharedMemory)
    {
        printErrorDetails();
        IOX_LOG(ERROR) << "Unable to create SharedMemoryObject since we could not acquire a SharedMemory resource";
        return cxx::error<SharedMemoryObjectError>(SharedMemoryObjectError::SHARED_MEMORY_CREATION_FAILED);
    }

    auto memoryMap = MemoryMapBuilder()
                         .baseAddressHint((m_baseAddressHint) ? *m_baseAddressHint : nullptr)
                         .length(m_memorySizeInBytes)
                         .fileDescriptor(sharedMemory->getHandle())
                         .accessMode(m_accessMode)
                         .flags(MemoryMapFlags::SHARE_CHANGES)
                         .offset(0)
                         .create();

    if (!memoryMap)
    {
        printErrorDetails();
        IOX_LOG(ERROR) << "Failed to map created shared memory into process!";
        return cxx::error<SharedMemoryObjectError>(SharedMemoryObjectError::MAPPING_SHARED_MEMORY_FAILED);
    }

    Allocator allocator(memoryMap->getBaseAddress(), m_memorySizeInBytes);

    if (sharedMemory->hasOwnership())
    {
        IOX_LOG(DEBUG) << "Trying to reserve " << m_memorySizeInBytes << " bytes in the shared memory [" << m_name
                       << "]";
        if (platform::IOX_SHM_WRITE_ZEROS_ON_CREATION)
        {
            // this lock is required for the case that multiple threads are creating multiple
            // shared memory objects concurrently
            std::lock_guard<std::mutex> lock(sigbusHandlerMutex);
            auto memsetSigbusGuard = registerSignalHandler(Signal::BUS, memsetSigbusHandler);
            if (memsetSigbusGuard.has_error())
            {
                printErrorDetails();
                IOX_LOG(ERROR) << "Failed to temporarily override SIGBUS to safely zero the shared memory";
                return cxx::error<SharedMemoryObjectError>(SharedMemoryObjectError::INTERNAL_LOGIC_FAILURE);
            }

            // NOLINTJUSTIFICATION snprintf required to populate char array so that it can be used signal safe in
            //                     a possible signal call
            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
            IOX_DISCARD_RESULT(snprintf(
                &sigbusErrorMessage[0],
                SIGBUS_ERROR_MESSAGE_LENGTH,
                "While setting the acquired shared memory to zero a fatal SIGBUS signal appeared caused by memset. The "
                "shared memory object with the following properties [ name = %s, sizeInBytes = %llu, access mode = %s, "
                "open mode = %s, baseAddressHint = %p, permissions = %lu ] maybe requires more memory than it is "
                "currently available in the system.\n",
                m_name.c_str(),
                static_cast<unsigned long long>(m_memorySizeInBytes),
                asStringLiteral(m_accessMode),
                asStringLiteral(m_openMode),
                (m_baseAddressHint) ? *m_baseAddressHint : nullptr,
                std::bitset<sizeof(mode_t)>(static_cast<mode_t>(m_permissions)).to_ulong()));

            memset(memoryMap->getBaseAddress(), 0, m_memorySizeInBytes);
        }
        IOX_LOG(DEBUG) << "Acquired " << m_memorySizeInBytes << " bytes successfully in the shared memory [" << m_name
                       << "]";
    }

    return cxx::success<SharedMemoryObject>(
        SharedMemoryObject(std::move(*sharedMemory), std::move(*memoryMap), std::move(allocator), m_memorySizeInBytes));
}

看着代码有99行,但实际逻辑还是比较简单的:

  • LINE 3 ~ LINE 22:定义了一个Lambda表达式,用于打印错误信息,可以看到,后面会有两处用到这个Lambda表达式。

  • LINE 24 ~ LINE 37:调用SharedMemoryBuilder::create方法创建SharedMemory对象,这个方法中将会调用系统调用,打开一段共享内存。

  • LINE 39 ~ LINE 53:调用MemoryMapBuilder::create方法创建MemoryMap对象,将共享内存映射至进程虚拟地址空间。

  • LINE 55 ~ LINE 95:初始化。

SharedMemoryMemoryMap类都是C++中RAII机制的运用,以避免共享内存泄漏。关于RAII,读者可参考:https://blog.csdn.net/weixin_61432764/article/details/127343976 ,本文不做论述。下面来看看,构造这两类对象的方法。

2.6 SharedMemoryBuilder::create

SharedMemoryBuilder::create函数调用系统调用,创建共享内存,并返回共享内存管理类SharedMemory,具体逻辑如下:

cxx::expected<SharedMemory, SharedMemoryError> SharedMemoryBuilder::create() noexcept
{
    auto printError = [this] {
        std::cerr << "Unable to create shared memory with the following properties [ name = " << m_name
                  << ", access mode = " << asStringLiteral(m_accessMode)
                  << ", open mode = " << asStringLiteral(m_openMode)
                  << ", mode = " << std::bitset<sizeof(mode_t)>(static_cast<mode_t>(m_filePermissions))
                  << ", sizeInBytes = " << m_size << " ]" << std::endl;
    };


    // on qnx the current working directory will be added to the /dev/shmem path if the leading slash is missing
    if (m_name.empty())
    {
        std::cerr << "No shared memory name specified!" << std::endl;
        return cxx::error<SharedMemoryError>(SharedMemoryError::EMPTY_NAME);
    }

    if (!cxx::isValidFileName(m_name))
    {
        std::cerr << "Shared memory requires a valid file name (not path) as name and \"" << m_name
                  << "\" is not a valid file name" << std::endl;
        return cxx::error<SharedMemoryError>(SharedMemoryError::INVALID_FILE_NAME);
    }

    auto nameWithLeadingSlash = addLeadingSlash(m_name);

    // the mask will be applied to the permissions, therefore we need to set it to 0
    int sharedMemoryFileHandle = SharedMemory::INVALID_HANDLE;
    mode_t umaskSaved = umask(0U);
    {
        cxx::ScopeGuard umaskGuard([&] { umask(umaskSaved); });

        if (m_openMode == OpenMode::PURGE_AND_CREATE)
        {
            IOX_DISCARD_RESULT(posixCall(iox_shm_unlink)(nameWithLeadingSlash.c_str())
                                   .failureReturnValue(SharedMemory::INVALID_HANDLE)
                                   .ignoreErrnos(ENOENT)
                                   .evaluate());
        }

        auto result =
            posixCall(iox_shm_open)(
                nameWithLeadingSlash.c_str(),
                convertToOflags(m_accessMode,
                                (m_openMode == OpenMode::OPEN_OR_CREATE) ? OpenMode::EXCLUSIVE_CREATE : m_openMode),
                static_cast<mode_t>(m_filePermissions))
                .failureReturnValue(SharedMemory::INVALID_HANDLE)
                .suppressErrorMessagesForErrnos((m_openMode == OpenMode::OPEN_OR_CREATE) ? EEXIST : 0)
                .evaluate();
        if (result.has_error())
        {
            // if it was not possible to create the shm exclusively someone else has the
            // ownership and we just try to open it
            if (m_openMode == OpenMode::OPEN_OR_CREATE && result.get_error().errnum == EEXIST)
            {
                result = posixCall(iox_shm_open)(nameWithLeadingSlash.c_str(),
                                                 convertToOflags(m_accessMode, OpenMode::OPEN_EXISTING),
                                                 static_cast<mode_t>(m_filePermissions))
                             .failureReturnValue(SharedMemory::INVALID_HANDLE)
                             .evaluate();
                if (!result.has_error())
                {
                    constexpr bool HAS_NO_OWNERSHIP = false;
                    sharedMemoryFileHandle = result->value;
                    return cxx::success<SharedMemory>(SharedMemory(m_name, sharedMemoryFileHandle, HAS_NO_OWNERSHIP));
                }
            }

            printError();
            return cxx::error<SharedMemoryError>(SharedMemory::errnoToEnum(result.get_error().errnum));
        }
        sharedMemoryFileHandle = result->value;
    }

    const bool hasOwnership = (m_openMode == OpenMode::EXCLUSIVE_CREATE || m_openMode == OpenMode::PURGE_AND_CREATE
                               || m_openMode == OpenMode::OPEN_OR_CREATE);
    if (hasOwnership)
    {
        auto result = posixCall(ftruncate)(sharedMemoryFileHandle, static_cast<int64_t>(m_size))
                          .failureReturnValue(SharedMemory::INVALID_HANDLE)
                          .evaluate();
        if (result.has_error())
        {
            printError();

            posixCall(iox_close)(sharedMemoryFileHandle)
                .failureReturnValue(SharedMemory::INVALID_HANDLE)
                .evaluate()
                .or_else([&](auto& r) {
                    std::cerr << "Unable to close filedescriptor (close failed) : " << r.getHumanReadableErrnum()
                              << " for SharedMemory \"" << m_name << "\"" << std::endl;
                });

            posixCall(iox_shm_unlink)(nameWithLeadingSlash.c_str())
                .failureReturnValue(SharedMemory::INVALID_HANDLE)
                .evaluate()
                .or_else([&](auto&) {
                    std::cerr << "Unable to remove previously created SharedMemory \"" << m_name
                              << "\". This may be a SharedMemory leak." << std::endl;
                });

            return cxx::error<SharedMemoryError>(SharedMemory::errnoToEnum(result->errnum));
        }
    }

    return cxx::success<SharedMemory>(SharedMemory(m_name, sharedMemoryFileHandle, hasOwnership));
}
  • LINE 3 ~ LINE 9:错误输出Lambda函数;

  • LINE 12 ~ LINE 24:前置条件检测;

  • LINE 42 ~ LINE 74:调用iox_shm_open,创建或打开共享内存文件,iox_shm_open是系统调用的封装:

    • POSIX兼容操作系统,其实现为:
    int iox_shm_open(const char* name, int oflag, mode_t mode)
    {
    	return shm_open(name, oflag, mode);
    }
    
    • Windows操作系统,实现较为复杂,主要使用CreateFileMappingOpenFileMapping等Win32 API来实现。
  • LINE 76 ~ LINE 105:如果是新创建的共享内存,对POSIX兼容系统,需要调用POSIX接口ftruncate将文件设置为指定大小。对Windows系统,其实现为:

    int ftruncate(int fildes, off_t length)
    {
    	return 0;
    }
    
  • LINE 107 ~ LINE 107:构造SharedMemory对象并返回。

2.7 MemoryMapBuilder::create

上一小节介绍的函数SharedMemoryBuilder::create只是在文件系统中创建(或打开)了一个文件,只有映射到进程虚拟地址空间,才能操作它,这就是本节要介绍的逻辑:

cxx::expected<MemoryMap, MemoryMapError> MemoryMapBuilder::create() noexcept
{
    int32_t l_memoryProtection{PROT_NONE};
    switch (m_accessMode)
    {
    case AccessMode::READ_ONLY:
        l_memoryProtection = PROT_READ;
        break;
    case AccessMode::READ_WRITE:
        // NOLINTNEXTLINE(hicpp-signed-bitwise) enum type is defined by POSIX, no logical fault
        l_memoryProtection = PROT_READ | PROT_WRITE;
        break;
    }
    // AXIVION Next Construct AutosarC++19_03-A5.2.3, CertC++-EXP55 : Incompatibility with POSIX definition of mmap
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) low-level memory management
    auto result = posixCall(mmap)(const_cast<void*>(m_baseAddressHint),
                                  m_length,
                                  l_memoryProtection,
                                  static_cast<int32_t>(m_flags),
                                  m_fileDescriptor,
                                  m_offset)

                      // NOLINTJUSTIFICATION cast required, type of error MAP_FAILED defined by POSIX to be void*
                      // NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast, performance-no-int-to-ptr)
                      .failureReturnValue(MAP_FAILED)
                      // NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast, performance-no-int-to-ptr)
                      .evaluate();

    if (result)
    {
        return cxx::success<MemoryMap>(MemoryMap(result.value().value, m_length));
    }

    constexpr uint64_t FLAGS_BIT_SIZE = 32U;
    auto flags = std::cerr.flags();
    std::cerr << "Unable to map memory with the following properties [ baseAddressHint = " << std::hex
              << m_baseAddressHint << ", length = " << std::dec << m_length << ", fileDescriptor = " << m_fileDescriptor
              << ", access mode = " << asStringLiteral(m_accessMode)
              << ", flags = " << std::bitset<FLAGS_BIT_SIZE>(static_cast<uint32_t>(flags)) << ", offset = " << std::hex
              << m_offset << std::dec << " ]" << std::endl;
    std::cerr.setf(flags);
    return cxx::error<MemoryMapError>(MemoryMap::errnoToEnum(result.get_error().errnum));
}

这段代码逻辑较简单:调用mmap系统调用,将文件映射到进程虚拟地址空间。

至此,共享内存创建完毕。通常,一段共享内存会被且分为多个内存块,这就是第三节要介绍的MemoryBlock及其子类。

3 MemoryBlock及其子类

用于描述一段内存段,包括长度、对齐方式等。

3.1 MemoryBlock类

一个RoudiMemoryManager由多个MemoryProvider组成,每个MemoryProvider创建一段共享内存。在MemoryProvider进一步且分为MemoryBlock。

对于默认配置而言,一个RoudiMemoryManager由一个MemoryProvider组成,然后在MemoryProvider进一步且分为MemoryBlock,每一个MemoryBlock有对应的功能,例如存放端口数据。

下面,我们从代码层面来具体看看共享内存的构成,如下:

DefaultRouDiMemory::DefaultRouDiMemory(const RouDiConfig_t& roudiConfig) noexcept
    : m_introspectionMemPoolBlock(introspectionMemPoolConfig())
    , m_segmentManagerBlock(roudiConfig)
    , m_managementShm(SHM_NAME, posix::AccessMode::READ_WRITE, posix::OpenMode::PURGE_AND_CREATE)
{
    m_managementShm.addMemoryBlock(&m_introspectionMemPoolBlock).or_else([](auto) {
        errorHandler(PoshError::ROUDI__DEFAULT_ROUDI_MEMORY_FAILED_TO_ADD_INTROSPECTION_MEMORY_BLOCK,
                     ErrorLevel::FATAL);
    });
    m_managementShm.addMemoryBlock(&m_segmentManagerBlock).or_else([](auto) {
        errorHandler(PoshError::ROUDI__DEFAULT_ROUDI_MEMORY_FAILED_TO_ADD_SEGMENT_MANAGER_MEMORY_BLOCK,
                     ErrorLevel::FATAL);
    });
}

IceOryxRouDiMemoryManager::IceOryxRouDiMemoryManager(const RouDiConfig_t& roudiConfig) noexcept
    : m_defaultMemory(roudiConfig)
{
    m_defaultMemory.m_managementShm.addMemoryBlock(&m_portPoolBlock).or_else([](auto) {
        errorHandler(PoshError::ICEORYX_ROUDI_MEMORY_MANAGER__FAILED_TO_ADD_PORTPOOL_MEMORY_BLOCK, ErrorLevel::FATAL);
    });
    m_memoryManager.addMemoryProvider(&m_defaultMemory.m_managementShm).or_else([](auto) {
        errorHandler(PoshError::ICEORYX_ROUDI_MEMORY_MANAGER__FAILED_TO_ADD_MANAGEMENT_MEMORY_BLOCK, ErrorLevel::FATAL);
    });
}

通过上面的代码段可以知道,iceoryx使用MemoryBlock的子类来描述共享内存块,结构如下图所示:

image

其中,m_portPoolBlock用于描述端口的共享内存,m_segmentManagerBlock用于描述通信负载的共享内存,三种共享内存块对应三种MemoryBlock子类。下面我们来看下MemoryBlock提供的接口及功能、三个子类对应的实现。

MemoryBlock类用于描述共享内存块的规格,主要提供以下接口。

  • size()接口:
/// @brief This function provides the size of the required memory for the underlying data. It is needed for the
/// MemoryProvider to calculate the total size of memory.
/// @return the required memory as multiple of the alignment
virtual uint64_t size() const noexcept = 0;

用于计算共享内存块所需的总内存量。

  • alignment()接口:
/// @brief This function provides the alignment of the memory for the underlying data. This information is needed
/// for the MemoryProvider
/// @return the alignment of the underlying data.
virtual uint64_t alignment() const noexcept = 0;

返回底层元素的字节对齐要求。

  • onMemoryAvailable()方法:
/// @brief This function is called once the memory is available and is therefore the earliest possibility to use the
/// memory.
/// @param [in] memory pointer to a valid memory block, the same one that the memory() member function would return
virtual void onMemoryAvailable(cxx::not_null<void*> memory) noexcept;

该方法在共享内存分配完成后调用,传入的参数为指向该共享内存块首地址的指针,默认实现为空。

3.2 PortPoolMemoryBlock类

存放端口相关数据共享内存,具体如下结构体:

struct PortPoolData
{
    FixedPositionContainer<popo::InterfacePortData, MAX_INTERFACE_NUMBER> m_interfacePortMembers;
    FixedPositionContainer<runtime::NodeData, MAX_NODE_NUMBER> m_nodeMembers;
    FixedPositionContainer<popo::ConditionVariableData, MAX_NUMBER_OF_CONDITION_VARIABLES> m_conditionVariableMembers;

    FixedPositionContainer<iox::popo::PublisherPortData, MAX_PUBLISHERS> m_publisherPortMembers;
    FixedPositionContainer<iox::popo::SubscriberPortData, MAX_SUBSCRIBERS> m_subscriberPortMembers;

    FixedPositionContainer<iox::popo::ServerPortData, MAX_SERVERS> m_serverPortMembers;
    FixedPositionContainer<iox::popo::ClientPortData, MAX_CLIENTS> m_clientPortMembers;
};

其中,PublisherPortData和SubscriberPortData为发布者和订阅者的端口数据,主要是消息队列,元素为Chunk的id和offset。

PortPoolMemoryBlock类的接口实现很简单:

uint64_t PortPoolMemoryBlock::size() const noexcept
{
    return sizeof(PortPoolData);
}

uint64_t PortPoolMemoryBlock::alignment() const noexcept
{
    return alignof(PortPoolData);
}

void PortPoolMemoryBlock::onMemoryAvailable(cxx::not_null<void*> memory) noexcept
{
    m_portPoolData = new (memory) PortPoolData;
}

3.3 MemPoolSegmentManagerMemoryBlock类

Chunk的管理结构中,我们知道,对于每一个Chunk实例,都有一个ChunkManagement实例与之对应。ChunkManagement实例就是存放在MemPoolSegmentManagerMemoryBlock描述的共享内存区域中。

因此,这就和Chunk的配置有关了。其构造函数会传入Chunk的配置,如下:

MemPoolSegmentManagerMemoryBlock::MemPoolSegmentManagerMemoryBlock(const mepoo::SegmentConfig& segmentConfig) noexcept
    : m_segmentConfig(segmentConfig)
{
}

用户可以提供配置,默认配置如下:

/// this is the default memory pool configuration if no one is provided by the user
MePooConfig& MePooConfig::setDefaults() noexcept
{
    m_mempoolConfig.push_back({128, 10000});
    m_mempoolConfig.push_back({1024, 5000});
    m_mempoolConfig.push_back({1024 * 16, 1000});
    m_mempoolConfig.push_back({1024 * 128, 200});
    m_mempoolConfig.push_back({1024 * 512, 50});
    m_mempoolConfig.push_back({1024 * 1024, 30});
    m_mempoolConfig.push_back({1024 * 1024 * 4, 10});

    return *this;
}

MemPoolSegmentManagerMemoryBlock的三个接口实现较为复杂,下面通过三个小节进行详细介绍。

3.3.1 size函数的实现

uint64_t MemPoolSegmentManagerMemoryBlock::size() const noexcept
{
    const uint64_t segmentManagerSize = sizeof(mepoo::SegmentManager<>);
    return cxx::align(segmentManagerSize, mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT)
           + mepoo::SegmentManager<>::requiredManagementMemorySize(m_segmentConfig);
}

首先是SegmentManager,这是用于管理Chunk共享内存的类。

接着,根据Chunk的配置计算ChunkManagement所需内存量,具体如下:

template <typename SegmentType>
uint64_t SegmentManager<SegmentType>::requiredManagementMemorySize(const SegmentConfig& config) noexcept
{
    uint64_t memorySize{0u};
    for (auto segment : config.m_sharedMemorySegments)
    {
        memorySize += MemoryManager::requiredManagementMemorySize(segment.m_mempoolConfig);
    }
    return memorySize;
}

uint64_t MemoryManager::requiredManagementMemorySize(const MePooConfig& mePooConfig) noexcept
{
    uint64_t memorySize{0U};
    uint64_t sumOfAllChunks{0U};
    for (const auto& mempool : mePooConfig.m_mempoolConfig)
    {
        sumOfAllChunks += mempool.m_chunkCount;
        memorySize += cxx::align(MemPool::freeList_t::requiredIndexMemorySize(mempool.m_chunkCount),
                                 MemPool::CHUNK_MEMORY_ALIGNMENT);
    }

    memorySize += cxx::align(sumOfAllChunks * sizeof(ChunkManagement), MemPool::CHUNK_MEMORY_ALIGNMENT);
    memorySize +=
        cxx::align(MemPool::freeList_t::requiredIndexMemorySize(sumOfAllChunks), MemPool::CHUNK_MEMORY_ALIGNMENT);

    return memorySize;
}

5~7行:遍历所有Segment配置,对每个Segment配置计算所需内存;
16~25行:遍历MemPool配置,计算总的Chunk个数sumOfAllChunks,据此计算存储这么多个Chunk所对应的ChunkManagement和FreeIndex所需内存大小。这里,第19行为什么这么写,还不是很理解。

3.3.2 alignment()函数的实现

uint64_t MemPoolSegmentManagerMemoryBlock::alignment() const noexcept
{
    const uint64_t segmentManagerAlignment = alignof(mepoo::SegmentManager<>);
    return algorithm::maxVal(segmentManagerAlignment, mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT);
}

4 小结

本文主要介绍了共享内存创建与划分。

posted @ 2024-04-09 00:20  爱新觉罗·L  阅读(356)  评论(0编辑  收藏  举报