上一节讲了initRuntime(APP_NAME),这节来讲publisher的创建、发布过程。
创建
struct RadarObject
{
double x = 0.0;
double y = 0.0;
double z = 0.0;
};
Publisher<RadarObject> publisher({"Radar", "FrontLeft", "Object"});
实例化一个Publisher的类,模板是RadarObject。追踪一下代码:
template <typename port_t>
inline BasePublisher<port_t>::BasePublisher(const capro::ServiceDescription& service,
const PublisherOptions& publisherOptions)
: m_port(iox::runtime::PoshRuntime::getInstance().getMiddlewarePublisher(service, publisherOptions))
port_t = PublisherPortUser。m_port即PublisherPortUser类,即最终调了PublisherPortUser类的构造。
看一下PoshRuntimeImpl::getMiddlewarePublisher()这个接口。
PublisherPortUserType::MemberType_t*
PoshRuntimeImpl::getMiddlewarePublisher(const capro::ServiceDescription& service,
const popo::PublisherOptions& publisherOptions,
const PortConfigInfo& portConfigInfo) noexcept
{
constexpr uint64_t MAX_HISTORY_CAPACITY =
PublisherPortUserType::MemberType_t::ChunkSenderData_t::ChunkDistributorDataProperties_t::MAX_HISTORY_CAPACITY;
auto options = publisherOptions;
...
if (options.nodeName.empty())
{
options.nodeName = m_appName;
}
IpcMessage sendBuffer;
sendBuffer << IpcMessageTypeToString(IpcMessageType::CREATE_PUBLISHER) << m_appName
<< static_cast<cxx::Serialization>(service).toString() << publisherOptions.serialize().toString()
<< static_cast<cxx::Serialization>(portConfigInfo).toString();
auto maybePublisher = requestPublisherFromRoudi(sendBuffer);
...
return maybePublisher.value();
}
PublisherPortUserType::MemberType_t为PublisherPortData类型。参数2,3为default。
该接口将一组序列化sendBuffer发送给RouDi,包括APP_NAME,service,publisherOptions,portConfigInfo。
并或取receiveBuffer的参数,生成一个PublisherPortData类型的指针。
发送用的 Interface
m_RoudiIpcInterface.send(msg) //发送用的是 m_RoudiIpcInterface
接收用的 Interface
m_AppIpcInterface.receive(answer) //接收用的是 m_AppIpcInterface
提取的参数为segmentId、offset,调用 getPtr 获取shared memory上存储chunk信息的位置,赋给ptr,然后强转为PublisherPortData * 类型。如下:
auto ptr = rp::BaseRelativePointer::getPtr(segmentId, offset);
PublisherPortUser构造的形参即PublisherPortData 类型,传给BasePort结构体转为了BasePortData *。
PublisherPortUser::PublisherPortUser(cxx::not_null<MemberType_t* const> publisherPortDataPtr) noexcept
: BasePort(publisherPortDataPtr)
, m_chunkSender(&getMembers()->m_chunkSenderData)
然后用BasePortData *这个指针构造了一个ChunkSender即m_chunkSender。最终给ChunkDistributor这个类的ChunkDistributorData *m_chunkDistrubutorDataPtr赋值。
类的包含关系如下:
publisher :Loan()
auto loanResult = publisher.loan();
template <typename T, typename H, typename BasePublisherType>
template <typename... Args>
inline cxx::expected<Sample<T, H>, AllocationError>
PublisherImpl<T, H, BasePublisherType>::loan(Args&&... args) noexcept
{
return std::move(loanSample().and_then([&](auto& sample) { new (sample.get()) T(std::forward<Args>(args)...); }));
}
注意 T = RadarObject,H = NoUserHeader。
template <typename T, typename H, typename BasePublisherType>
inline cxx::expected<Sample<T, H>, AllocationError> PublisherImpl<T, H, BasePublisherType>::loanSample() noexcept
{
static constexpr uint32_t USER_HEADER_SIZE{std::is_same<H, mepoo::NoUserHeader>::value ? 0U : sizeof(H)};
auto result = port().tryAllocateChunk(sizeof(T), alignof(T), USER_HEADER_SIZE, alignof(H));
return cxx::success<Sample<T, H>>(convertChunkHeaderToSample(result.value()));
}
port()返回的是m_port(PublisherPortUser类型),因此调了
cxx::expected<mepoo::ChunkHeader*, AllocationError>
PublisherPortUser::tryAllocateChunk(const uint32_t userPayloadSize,
const uint32_t userPayloadAlignment,
const uint32_t userHeaderSize,
const uint32_t userHeaderAlignment) noexcept
{
return m_chunkSender.tryAllocate(
getUniqueID(), userPayloadSize, userPayloadAlignment, userHeaderSize, userHeaderAlignment);
}
又调用了ChunkSender的tryAllocate(),这个接口相对复杂。基本操作如下:
1、 用传入的参数实例化了一个ChunkSettings类。
2、 判断上一次分配的chunk是否发送失败,如失败,则判断其大小是否合适,如合适则使用。
3、 如2未成功,则分配一个新的chunk:
auto getChunkResult = getMembers()->m_memoryMgr->getChunk(chunkSettings);
调用MemoryManager::getChunk,操作如下:
for (auto& memPool : m_memPoolVector) //从m_memPoolVector这个vector中取出MemPool
{
uint32_t chunkSizeOfMemPool = memPool.getChunkSize(); //MemPool中chunk块大小是否合适
if (chunkSizeOfMemPool >= requiredChunkSize)
{
chunk = memPool.getChunk(); //<1>
memPoolPointer = &memPool;
aquiredChunkSize = chunkSizeOfMemPool;
break;
}
}
auto chunkHeader = new (chunk) ChunkHeader(aquiredChunkSize, chunkSettings);
auto chunkManagement = new (m_chunkManagementPool.front().getChunk())
ChunkManagement(chunkHeader, memPoolPointer, &m_chunkManagementPool.front()); //<2>
return cxx::success<SharedChunk>(SharedChunk(chunkManagement));
<1>处get了一块chunk,返回的是chunkheader的首地址。
<2>要重点分析一下:
看一下RouDi在创建内存池时最后一步操作:在 /dev/shm/iceoryx_mgmt 上划分了 m_totalNumberOfChunks 个 ChunkManagement 。
void MemoryManager::generateChunkManagementPool(posix::Allocator& managementAllocator) noexcept
{
m_denyAddMemPool = true;
uint32_t chunkSize = sizeof(ChunkManagement);
m_chunkManagementPool.emplace_back(chunkSize, m_totalNumberOfChunks, managementAllocator, managementAllocator);
}
在publisher端,每获取一个chunk,同时获取一个ChunkManagement。初始化一个ChunkHeader,放在获取的chunk头部,
并作为参数初始化了一个ChunkManagement,然后构造了一个SharedChunk,返回。
if (getMembers()->m_chunksInUse.insert(chunk))
{
// END of critical section
chunk.getChunkHeader()->setOriginId(originId);
return cxx::success<mepoo::ChunkHeader*>(chunk.getChunkHeader());
}
将chunk插入m_chunksInUse链表,返回了一个chunkHeader *。
inline Sample<T, H>
PublisherImpl<T, H, BasePublisherType>::convertChunkHeaderToSample(mepoo::ChunkHeader* const header) noexcept
{
return Sample<T, H>(cxx::unique_ptr<T>(reinterpret_cast<T*>(header->userPayload()),
[this](auto* userPayload) {
auto chunkHeader = iox::mepoo::ChunkHeader::fromUserPayload(userPayload);
this->port().releaseChunk(chunkHeader);
}),
*this);
}
这段代码 header->userPayload() 是获取 chunk的userPayload 段,即把ChunkHeader去掉。强转为 T* 类型指针。然后把ChunkHeader从UsedChunkList上remove掉。
返回一个Sample<T,H>,其中 T = RadarObject。一个指向userPayload 的unique_ptr,与 *this = PublisherImpl 传入Sample的构造。
SmartChunk<PublisherInterface<T, H>, T, H>; // T = RadarObject
template <typename TransmissionInterface, typename T, typename H>
template <typename S, typename>
inline SmartChunk<TransmissionInterface, T, H>::SmartChunk(cxx::unique_ptr<T>&& smartChunkUniquePtr,
TransmissionInterface& producer) noexcept
: m_members({std::move(smartChunkUniquePtr), producer})
{
}
初始化了 SmartChunkPrivateData类型的m_members,其中producer = PublisherImpl ,smartChunkUniquePtr为RadarObject 类型指针。
至此 auto loanResult = publisher.loan()分析完毕。返回了一个 Sample 类型的实例。
下面分析publish():
auto& sample = loanResult.value();
// Sample can be held until ready to publish
sample->x = ct;
sample->y = ct;
sample->z = ct;
sample.publish();
这段操作比较迷惑,为甚么sample能这么赋值。原因是sample的父类重载了->。
void Sample<T, H>::publish() noexcept
{
if (BaseType::m_members.smartChunkUniquePtr)
{
BaseType::m_members.producerRef.get().publish(std::move(*(this)));
}
}
reference_wrapper producerRef 可知,get()获取到的是PublisherImpl。
inline void PublisherImpl<T, H, BasePublisherType>::publish(Sample<T, H>&& sample) noexcept
{
auto userPayload = sample.release(); // release the Samples ownership of the chunk before publishing
auto chunkHeader = mepoo::ChunkHeader::fromUserPayload(userPayload);
port().sendChunk(chunkHeader);
}
port()返回的是m_port(PublisherPortUser类型),最终调了deliverToAllStoredQueues(mepoo::SharedChunk chunk),主要进行了如下操作:
1 、从m_chunkDistrubutorDataPtr的m_queues获取substribute的queue
for (auto& queue : getMembers()->m_queues) // 从m_chunkDistrubutorDataPtr的m_queues获取substribute的queue
2 、将chunk push进queue
if (pushToQueue(queue.get(), chunk)) // 将chunk push进queue
3、计数加1
++numberOfQueuesTheChunkWasDeliveredTo; //
总结:
1、实例化一个publisher ,将service、app_name等信息发送给RouDi,根据返回信息构建了一个 PublisherPortUser,该实例包含一个m_chunkSender,用于申请、发送chunk。
2、用loan()创建一个sample,实际申请了一块chunk,并返回其首地址。
3、填充内容,发布。