iceoryx源码阅读(六)——共享内存创建
从本文开始,我们开始讨论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:调用子类,即:
PosixShmMemoryProvider
的createMemory
实现,这里应该就是设计模式中的模板方法模式; - LINE 38 ~ LINE 44:将得到的共享内存首地址注册到一个map中,key作为返回的id,正如前面文章中,在Producer、Consumer和RoudiApp之间就是通过id和偏移量来定位共享内存中的数据。
- LINE 49 ~ LINE 54:遍历
MemoryBlock
,实现对整块共享内存的切分。
MemoryBlock
类是对共享内存的进一步切分,与本节共享内存分配的主题关系不是很密切,且内容较多,我们放在第三部分进行讲解。接下来,我们主要讲解子类PosixShmMemoryProvider
的createMemory
的实现。
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:初始化。
SharedMemory
和MemoryMap
类都是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操作系统,实现较为复杂,主要使用
CreateFileMapping
和OpenFileMapping
等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 小结
本文主要介绍了共享内存创建与划分。