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

1 共享内存的组织

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

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的子类来描述共享内存块,结构如下图所示:

其中,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 小结

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

iceoryx源码阅读(六)——共享内存创建的更多相关文章

  1. Spark常用函数(源码阅读六)

    源码层面整理下我们常用的操作RDD数据处理与分析的函数,从而能更好的应用于工作中. 连接Hbase,读取hbase的过程,首先代码如下: def tableInitByTime(sc : SparkC ...

  2. spdlog源码阅读 (2): sinks的创建和使用

    2. sink创建 2.1 还是rotating_file_sink 我们仍然以rotating_file_sink为例来说明在spdlog中sink的创建过程. 在spdlog-master/tes ...

  3. Struts2源码阅读(一)_Struts2框架流程概述

    1. Struts2架构图  当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpse ...

  4. spdlog源码阅读 (3): log_msg和BasicWriter

    4. log_msg和它的打手BasicWriter 在spdlog源码阅读 (2): sinks的创建和使用中,提到log_msg提供了存储日志的功能.那么到底在spdlog中它是怎么 起到这个作用 ...

  5. SparkConf加载与SparkContext创建(源码阅读一)

    即日起开始spark源码阅读之旅,这个过程是相当痛苦的,也许有大量的看不懂,但是每天一个方法,一点点看,相信总归会有极大地提高的.那么下面开始: 创建sparkConf对象,那么究竟它干了什么了类,从 ...

  6. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  7. 38 网络相关函数(六)——live555源码阅读(四)网络

    38 网络相关函数(六)——live555源码阅读(四)网络 38 网络相关函数(六)——live555源码阅读(四)网络 简介 12)makeSocketNonBlocking和makeSocket ...

  8. DM 源码阅读系列文章(六)relay log 的实现

    2019独角兽企业重金招聘Python工程师标准>>> 作者:张学程 本文为 DM 源码阅读系列文章的第六篇,在 上篇文章 中我们介绍了 binlog replication 处理单 ...

  9. LevelDB(v1.3) 源码阅读之 Arena(内存管理器)

    LevelDB(v1.3) 源码阅读系列使用 LevelDB v1.3 版本的代码,可以通过如下方式下载并切换到 v1.3 版本的代码: $ git clone https://github.com/ ...

  10. Redis源码阅读(六)集群-故障迁移(下)

    Redis源码阅读(六)集群-故障迁移(下) 最近私人的事情比较多,没有抽出时间来整理博客.书接上文,上一篇里总结了Redis故障迁移的几个关键点,以及Redis中故障检测的实现.本篇主要介绍集群检测 ...

随机推荐

  1. Games101:作业6

    说明 本次作业主要实现对上一次作业代码的重构以及使用BVH加速求交的交点判断和递归调用 代码框架的修改 有影响的改动就是框架中定义了两个结构体一个是光线ray,一个是交点Intersection 交点 ...

  2. windows系统命令行cmd查看显卡驱动版本号CUDA

    Win+R 输入cmd 进入命令行 输入 nvidia-smi

  3. 不到2000字,轻松带你搞懂STM32中GPIO的8种工作模式

    大家好,我是知微! 学习过单片机的小伙伴对GPIO肯定不陌生,GPIO (general purpose input output)是通用输入输出端口的简称,通俗来讲就是单片机上的引脚. 在STM32 ...

  4. #主席树,Dijkstra,哈希#CF464E The Classic Problem

    题目 边权均为2的幂次的无向图,求 \(S\) 到 \(T\) 的最短路 \(n,m\leq 10^5\) 分析 最短路直接考虑用 Dijkstra,它需要维护松弛操作和堆, 那么也就是要重载加号和小 ...

  5. #费马小定理#JZOJ 4015 数列

    题目 给出\(x_n=(ax_{n-1}^2+bx_{n-1}+c)\bmod m\) 给出\(x_0.a,b,c,n,m\),求\(x_n\) \(\text{Subtask 1:}n\leq 10 ...

  6. #Every-SG#HDU 3595 GG and MM

    题目 有\(n\)个游戏,每个游戏只要能进行就必须进行, 对于每个游戏有两堆石子,每次可以将数量多的中取出小堆石子数量的整数倍, 无法操作者为负,问先手是否必胜 分析 如果单个游戏最大操作次数为奇数次 ...

  7. 圈重点!一图读懂OpenHarmony技术日

     

  8. 直播预告丨OpenHarmony标准系统多媒体子系统之视频解读

    5月19日(周四)晚上19点,OpenHarmony开源开发者成长计划知识赋能第五期"掌握OpenHarmony多媒体的框架原理"的第五节直播课,即将开播! 深开鸿资深技术专家胡浩 ...

  9. 我只用了3步,实现了一个逼真的3D场景渲染

    给3D模型及环境场景渲染出兼具质感和真实感的材质效果,需要经历几步? 显然,目前的3D模型材质渲染技术,还无法实现简单几步就能搞定的标准化作业来量化,完成一个质量过关的3D模型渲染,一般需要: 1.准 ...

  10. MogDB SQLdiag 使用指南

    MogDB SQLdiag 使用指南 本文出处:https://www.modb.pro/db/411957 前提条件 需要保证用户提供训练数据. 如果用户通过提供的工具收集训练数据,则需要启用 WD ...