private: /// @brief string representation of the service IdString_t m_serviceString; /// @brief string representation of the instance IdString_t m_instanceString; /// @brief string representation of the event IdString_t m_eventString;
struct ChunkHeader { using UserPayloadOffset_t = uint32_t;
/// @brief constructs and initializes a ChunkHeader /// @param[in] chunkSize is the size of the chunk the ChunkHeader is constructed /// @param[in] chunkSettings are the settings like user-payload size and user-header alignment ChunkHeader(const uint32_t chunkSize, const ChunkSettings& chunkSettings) noexcept;
// copy/move ctors/assignment operators are deleted since the calculations for the user-header and user-payload // alignment are dependent on the address of the this pointer ChunkHeader(const ChunkHeader&) = delete; ChunkHeader(ChunkHeader&&) = delete;
/// @brief From the 1.0 release onward, this must be incremented for each incompatible change, e.g. /// - data width of members changes /// - members are rearranged /// - semantic meaning of a member changes static constexpr uint8_t CHUNK_HEADER_VERSION{1U};
/// @brief The runtime that is needed for each application to communicate with the RouDi daemon class PoshRuntime { public: PoshRuntime(const PoshRuntime&) = delete; PoshRuntime& operator=(const PoshRuntime&) = delete; PoshRuntime(PoshRuntime&&) = delete; PoshRuntime& operator=(PoshRuntime&&) = delete; virtual ~PoshRuntime() noexcept = default;
/// @brief returns active runtime /// /// @return active runtime static PoshRuntime& getInstance() noexcept;
/// @brief creates the runtime with given name /// /// @param[in] name used for registering the process with the RouDi daemon /// /// @return active runtime static PoshRuntime& initRuntime(const RuntimeName_t& name) noexcept;
template <typename... T> class IOX_NO_DISCARD expected;
/// @brief expected implementation from the C++20 proposal with C++11. The interface /// is inspired by the proposal but it has changes since we are not allowed to /// throw an exception. /// @param ErrorType type of the error which can be stored in the expected /// /// @code /// cxx::expected<int, float> callMe() { /// bool l_errorOccured; /// // ... do stuff /// if ( l_errorOccured ) { /// return cxx::error<float>(55.1f);
protected: using Queue = IndexQueue<Capacity>; using BufferIndex = typename Queue::value_t;
// remark: actually m_freeIndices do not have to be in a queue, it could be another // multi-push multi-pop capable lockfree container (e.g. a stack or a list) Queue m_freeIndices;
// required to be a queue for LockFreeQueue to exhibit FIFO behaviour Queue m_usedIndices;
/// @brief /// Thread safe producer and consumer queue with a safe overflowing behavior. /// SoFi is designed in a FIFO Manner but prevents data loss when pushing into /// a full SoFi. When SoFi is full and a Sender tries to push, the data at the /// current read position will be returned. SoFi is a Thread safe without using /// locks. When the buffer is filled, new data is written starting at the /// beginning of the buffer and overwriting the old.The SoFi is especially /// designed to provide fixed capacity storage. When its capacity is exhausted, /// newly inserted elements will cause elements either at the beginning /// to be overwritten.The SoFi only allocates memory when /// created , capacity can be is adjusted explicitly. /// /// @param[in] ValueType DataType to be stored, must be trivially copyable /// @param[in] CapacityValue Capacity of the SoFi template <class ValueType, uint64_t CapacityValue> class SoFi { static_assert(std::is_trivially_copyable<ValueType>::value, "SoFi can handle only trivially copyable data types"); /// @brief Check if Atomic integer is lockfree on platform /// ATOMIC_INT_LOCK_FREE = 2 - is always lockfree /// ATOMIC_INT_LOCK_FREE = 1 - is sometimes lockfree /// ATOMIC_INT_LOCK_FREE = 0 - is never lockfree static_assert(2 <= ATOMIC_INT_LOCK_FREE, "SoFi is not able to run lock free on this data type");
/// @brief The smart_lock class is a wrapping class which can be used to make /// an arbitrary class threadsafe by wrapping it with the help of the /// arrow operator. /// IMPORTANT: If you generate a threadsafe container with smart_lock, /// only the container is threadsafe not the containing /// elements! /// @code /// #include <algorithm> /// #include <vector> /// #include "smart_lock.hpp" /// /// int main() { /// concurrent::smart_lock<std::vector<int>> threadSafeVector; /// threadSafeVector->push_back(123); /// threadSafeVector->push_back(456); /// threadSafeVector->push_back(789); /// size_t vectorSize = threadSafeVector->size(); /// /// { /// auto guardedVector = threadSafeVector.getScopeGuard(); /// auto iter = std::find(guardVector->begin(), guardVector->end(), 456); /// if (iter != guardVector->end()) guardVector->erase(iter); /// } /// } /// @endcode template <typename T, typename MutexType = ::std::mutex> class smart_lock
/// @brief Wrapper class for unix domain socket class UnixDomainSocket : public DesignPattern::Creation<UnixDomainSocket, IpcChannelError> { public: struct NoPathPrefix_t { }; static constexpr NoPathPrefix_t NoPathPrefix{}; static constexpr uint64_t NULL_TERMINATOR_SIZE = 1U; static constexpr uint64_t MAX_MESSAGE_SIZE = platform::IOX_UDS_SOCKET_MAX_MESSAGE_SIZE - NULL_TERMINATOR_SIZE; /// @brief The name length is limited by the size of the sockaddr_un::sun_path buffer and the IOX_SOCKET_PATH_PREFIX static constexpr size_t LONGEST_VALID_NAME = sizeof(sockaddr_un::sun_path) - 1;
using UdsName_t = cxx::string<LONGEST_VALID_NAME>; using Message_t = cxx::string<MAX_MESSAGE_SIZE>;
mmap 同一个共享内存对象,进程 A 可能映射到 0x7f11...,进程 B 映射到 0x7f42...。若 A 在共享内存里存了一个裸指针(绝对虚拟地址),B 解引用它就是未定义行为。而 iceoryx 的端口结构、队列、free-list、Chunk 引用全部活在共享内存里且互相指来指去——所以”指针”必须改存与某个已知基址的偏移,读取时在本进程重算绝对地址。
/// @brief pointer class to use when pointer and pointee are located in different shared memory segments /// We can have the following scenario: /// Pointer p is stored in segment S1 and points to object X of type T in segment S2. /// /// Shared Memory S1: p S2: X /// |___________________^ /// App1 a1 b1 c1 d1 /// App2 a2 b2 c2 d2 /// /// Now it is no longer true in general that both segments will be offset by the same difference in App2 and therefore /// relocatable pointers are no longer sufficient. /// Relative pointers solve this problem by incorporating the information from where they need to measure differences /// (i.e. relative to the given address). This requires an additional registration mechanism to be used by all /// applications where the start addresses and the size of all segments to be used are registered. Since these start /// address may differ between applications, each segment is identified by a unique id, which can be provided upon /// registration by the first application. In the figure, this means that the starting addresses of both segments(a1, a2 /// and c1, c2) would have to be registered in both applications. /// Once this registration is done, relative pointers can be constructed from raw pointers similar to relocatable /// pointers. /// @note It should be noted that relocating a memory segment will invalidate relative pointers, i.e. relative pointers /// are NOT relocatable. This is because the registration mechanism cannot be automatically informed about the copy of a /// whole segment, such a segment would have to be registered on its own (and the original segment deregistered).
存储上只有两个 8 字节成员——段 id 与段内偏移,这就是它可以原样躺在共享内存里被任何进程读取的原因:
/// @brief Allows registration of memory segments with their start pointers and size. /// This class is used to resolve relative pointers in the corresponding address space of the application. /// Up to CAPACITY segments can be registered with MIN_ID = 1 to MAX_ID = CAPACITY - 1 /// id 0 is reserved and allows relative pointers to behave like normal pointers /// (which is equivalent to measure the offset relative to 0). template <typename id_t, typename ptr_t, uint64_t CAPACITY = 10000U> class PointerRepository {
要点:
注册表是进程本地的静态单例(BaseRelativePointer::getRepository() 内的函数静态变量),不在共享内存里;每个进程 mmap 完各段后各自调用 registerPtr(id, 本进程基址, size)。段 id 全局一致,基址各进程各异——这正是跨进程解引用成立的机制。
id 0 保留:偏移直接当裸指针用(相当于基址为 0),使同一套代码可同时处理进程内/共享内存两种情形。
/// @brief Smart pointer type that allows objects using it to able to be copied by memcpy /// without invalidating the pointer. /// This applies only if it points to memory owned by the object itself /// (i.e. not to memory outside of the object). /// This is useful to improve copy-efficiency and allow the types build with relocatable /// pointers only to be stored in shared memory. /// It is useable like a raw pointer of the corresponding type and can be implicily /// converted to one. /// /// @tparam T the native type wrapped by the relocatable_ptr, i.e. relocatable_ptr<T> /// has native type T and corresponds to a raw pointer of type T*. /// /// @note It is advisable to use relocatable_ptr only for storage (e.g. member variables), /// not to pass them around as function arguments or as return value. /// There should be no need for this, since as pass-around type /// regular pointers do the job just fine and do not incur /// the slight runtime overhead of a relocatable_ptr. /// There should be no memory overhead on 64 bit systems. /// @note relocatable_ptr is not trivially copyable since in general the copy constructor /// requires additional logic. Hence obects that contain it re not trivially /// copyable in the C++ sense. However, if the pointees of a host object containing the /// relocatable ptr are all located inside the object and the obect is otherwise trivially /// copyable it can be safely copied by memcpy. /// @todo specialize for another pointer class for this use case once it is fully defined/understood template <typename T> class relocatable_ptr
template <typename SegmentType> inline typename SegmentManager<SegmentType>::SegmentMappingContainer SegmentManager<SegmentType>::getSegmentMappings(const posix::PosixUser& user) noexcept { // get all the groups the user is in auto groupContainer = user.getGroups(); ... for (const auto& groupID : groupContainer) { for (const auto& segment : m_segmentContainer) { if (segment.getWriterGroup() == groupID) { // a user is allowed to be only in one writer group, as we currently only support one memory manager per // process if (!foundInWriterGroup) { mappingContainer.emplace_back(segment.getWriterGroup().getName(), ...); foundInWriterGroup = true; } else { errorHandler(Error::kMEPOO__USER_WITH_MORE_THAN_ONE_WRITE_SEGMENT); ...
uint32_t m_chunkSize{0U}; /// needs to be 32 bit since loffli supports only 32 bit numbers /// (cas is only 64 bit and we need the other 32 bit for the aba counter) uint32_t m_numberOfChunks{0U};
private: // the order of these members must be changed carefully and if this happens, the m_chunkHeaderVersion // needs to be adapted in order to be able to detect incompatibilities between publisher/subscriber // or record&replay, m_chunkSize and m_chunkHeaderVersion should therefore neither changed the type, // nor the position
// size of the whole chunk, including the header uint32_t m_chunkSize{0U}; uint8_t m_chunkHeaderVersion{CHUNK_HEADER_VERSION}; // reserved for future functionality and used to indicate the padding bytes; currently not used and set to `0` uint8_t m_reserved{0}; // currently just a placeholder uint16_t m_userHeaderId{NO_USER_HEADER}; popo::UniquePortId m_originId{popo::InvalidPortId}; uint64_t m_sequenceNumber{0U}; uint32_t m_userHeaderSize{0U}; uint32_t m_userPayloadSize{0U}; uint32_t m_userPayloadAlignment{1U}; UserPayloadOffset_t m_userPayloadOffset{sizeof(ChunkHeader)};
if (userHeaderSize == 0U) { if (userPayloadAlignment <= alignof(mepoo::ChunkHeader)) { // the most simple case with no user-header and the user-payload adjacent to the ChunkHeader m_userPayloadOffset = sizeof(ChunkHeader); } else { // the second most simple case with no user-header but the user-payload alignment // exceeds the ChunkHeader alignment and is therefore not necessarily adjacent uint64_t addressOfChunkHeader = reinterpret_cast<uint64_t>(this); uint64_t headerEndAddress = addressOfChunkHeader + sizeof(ChunkHeader); uint64_t alignedUserPayloadAddress = iox::cxx::align(headerEndAddress, static_cast<uint64_t>(userPayloadAlignment)); uint64_t offsetToUserPayload = alignedUserPayloadAddress - addressOfChunkHeader; ... m_userPayloadOffset = static_cast<UserPayloadOffset_t>(offsetToUserPayload);
// this is safe since the alignment of the user-payload is larger than the one from the ChunkHeader ... auto addressOfBackOffset = alignedUserPayloadAddress - sizeof(UserPayloadOffset_t); auto backOffset = reinterpret_cast<UserPayloadOffset_t*>(addressOfBackOffset); *backOffset = m_userPayloadOffset; } }
ChunkHeader* ChunkHeader::fromUserPayload(void* const userPayload) noexcept { if (userPayload == nullptr) { return nullptr; } uint64_t userPayloadAddress = reinterpret_cast<uint64_t>(userPayload); // the back-offset is always stored in front of the user-payload, no matter if a user-header is used or not or if // the user-payload has a custom alignment auto backOffset = reinterpret_cast<UserPayloadOffset_t*>(userPayloadAddress - sizeof(UserPayloadOffset_t)); return reinterpret_cast<ChunkHeader*>(userPayloadAddress - *backOffset); }
struct ChunkManagement { using base_t = ChunkHeader; using referenceCounterBase_t = uint64_t; using referenceCounter_t = std::atomic<referenceCounterBase_t>;
iox::rp::RelativePointer<base_t> m_chunkHeader; referenceCounter_t m_referenceCounter{1U}; /// @todo optimization: check if this can be replaced by an offset relative to the this pointer iox::rp::RelativePointer<MemPool> m_mempool; iox::rp::RelativePointer<MemPool> m_chunkManagementPool; };
跨进程安全的三个要素:
std::atomic<uint64_t> 计数器位于所有参与进程都以读写方式映射的管理段中,x86/ARM 上无锁原子(fetch_add/fetch_sub)跨进程有效——原子性由 CPU 缓存一致性保证,与进程无关,只要求同一物理内存。
template <typename T, typename H = mepoo::NoUserHeader> class Publisher : public PublisherImpl<T, H> { public: using PublisherImpl<T, H>::PublisherImpl; };
/// @brief The Sample class is a mutable abstraction over types which are written to loaned shared memory. /// These samples are publishable to the iceoryx system. template <typename T, typename H = cxx::add_const_conditionally_t<mepoo::NoUserHeader, T>> class Sample : public SmartChunk<PublisherInterface<T, H>, T, H> { ... /// @brief Publish the sample via the publisher from which it was loaned and automatically /// release ownership to it. /// @details Only available for non-const type T. template <typename S = T, typename = ForPublisherOnly<S, T>> void publish() noexcept;
using ChunkQueueData_t = SubscriberPortData::ChunkQueueData_t; using ChunkDistributorData_t = ChunkDistributorData<DefaultChunkDistributorConfig, ThreadSafePolicy, ChunkQueuePusher<ChunkQueueData_t>>; using ChunkSenderData_t = ChunkSenderData<MAX_CHUNKS_ALLOCATED_PER_PUBLISHER_SIMULTANEOUSLY, ChunkDistributorData_t>;
/// @brief Allocate a chunk, the ownership of the SharedChunk remains in the PublisherPortUser for being able to /// cleanup if the user process disappears ... cxx::expected<mepoo::ChunkHeader*, AllocationError> tryAllocateChunk(const uint32_t userPayloadSize, const uint32_t userPayloadAlignment, const uint32_t userHeaderSize = 0U, const uint32_t userHeaderAlignment = 1U) noexcept; ... void sendChunk(mepoo::ChunkHeader* const chunkHeader) noexcept; ... void offer() noexcept; ... bool hasSubscribers() const noexcept;
/// @brief /// Thread safe producer and consumer queue with a safe overflowing behavior. /// SoFi is designed in a FIFO Manner but prevents data loss when pushing into /// a full SoFi. When SoFi is full and a Sender tries to push, the data at the /// current read position will be returned. SoFi is a Thread safe without using /// locks. ... template <class ValueType, uint64_t CapacityValue> class SoFi
// drop the chunk if one is returned by an overflow if (pushRet.has_value()) { pushRet.value().releaseToSharedChunk(); // tell the ChunkDistributor that we had an overflow and dropped a sample hasQueueOverflow = true; }
/// @brief Logical disjunction of a certain number of Triggers /// /// The WaitSet stores Triggers and allows the user to wait till one or more of those Triggers are triggered. It works /// over process borders. With the creation of a WaitSet it requests a condition variable from RouDi and destroys it /// with the destructor. Hence the lifetime of the condition variable is bound to the lifetime of the WaitSet. /// @param[in] Capacity the amount of events/states which can be attached to the waitset template <uint64_t Capacity = MAX_NUMBER_OF_ATTACHMENTS_PER_WAITSET> class WaitSet
IceOryxRouDiComponents::IceOryxRouDiComponents(const RouDiConfig_t& roudiConfig) noexcept : rouDiMemoryManager(roudiConfig) , portManager([&]() -> IceOryxRouDiMemoryManager* { // this temporary object will create a roudi IPC channel // and close it immediatelly // if there was an outdated roudi IPC channel, it will be cleaned up // if there is an outdated IPC channel, the start of the apps will be terminated runtime::IpcInterfaceBase::cleanupOutdatedIpcChannel(roudi::IPC_CHANNEL_ROUDI_NAME);
// run the threads m_monitoringAndDiscoveryThread = std::thread(&RouDi::monitorAndDiscoveryUpdate, this); posix::setThreadName(m_monitoringAndDiscoveryThread.native_handle(), "Mon+Discover");
if (roudiStartupParameters.m_runtimesMessagesThreadStart == RuntimeMessagesThreadStart::IMMEDIATE) { startProcessRuntimeMessagesThread(); }
PoshRuntimeImpl::PoshRuntimeImpl(cxx::optional<const RuntimeName_t*> name, const RuntimeLocation location) noexcept : PoshRuntime(name) , m_ipcChannelInterface(roudi::IPC_CHANNEL_ROUDI_NAME, *name.value(), runtime::PROCESS_WAITING_FOR_ROUDI_TIMEOUT) , m_ShmInterface([&] { // in case the runtime is located in the same process like RouDi the shm is already opened; ... return location == RuntimeLocation::SAME_PROCESS_LIKE_ROUDI ? cxx::nullopt : cxx::optional<SharedMemoryUser>({m_ipcChannelInterface.getShmTopicSize(), m_ipcChannelInterface.getSegmentId(), m_ipcChannelInterface.getSegmentManagerAddressOffset()}); }()) { }
if (capro::CaproMessageType::SUB == caProMessage.m_type) { const auto ret = m_chunkSender.tryAddQueue( static_cast<PublisherPortData::ChunkQueueData_t*>(caProMessage.m_chunkQueueData), caProMessage.m_historyCapacity); if (!ret.has_error()) { responseMessage.m_type = capro::CaproMessageType::ACK; } }
findProcess(name) .and_then([&](auto& process) { // process is already in list (i.e. registered) // depending on the mode we clean up the process resources and register it again // if it is monitored, we reject the registration and wait for automatic cleanup // otherwise we remove the process ourselves and register it again ... // process exists, we expect that the existing process crashed LogWarn() << "Application " << name << " crashed. Re-registering application";
// remove the existing process and add the new process afterwards, we do not send ack to new process constexpr TerminationFeedback TERMINATION_FEEDBACK{TerminationFeedback::DO_NOT_SEND_ACK_TO_PROCESS}; if (!this->searchForProcessAndRemoveIt(name, TERMINATION_FEEDBACK))
if (lastChunkChunkHeader && (lastChunkChunkHeader->chunkSize() >= requiredChunkSize)) { auto sharedChunk = lastChunkUnmanaged.cloneToSharedChunk(); if (getMembers()->m_chunksInUse.insert(sharedChunk)) { auto chunkSize = lastChunkChunkHeader->chunkSize(); lastChunkChunkHeader->~ChunkHeader(); new (lastChunkChunkHeader) mepoo::ChunkHeader(chunkSize, chunkSettings); lastChunkChunkHeader->setOriginId(originId); return cxx::success<mepoo::ChunkHeader*>(lastChunkChunkHeader); } ... } else { // BEGIN of critical section, chunk will be lost if the process terminates in this section // get a new chunk auto getChunkResult = getMembers()->m_memoryMgr->getChunk(chunkSettings);
if (!getChunkResult.has_error()) { auto& chunk = getChunkResult.value();
// if the application allocated too much chunks, return no more chunks if (getMembers()->m_chunksInUse.insert(chunk)) { // END of critical section chunk.getChunkHeader()->setOriginId(originId); return cxx::success<mepoo::ChunkHeader*>(chunk.getChunkHeader()); }
// Torn writes are problematic since RouDi needs to cleanup all chunks when an application crashes. If the size is // larger than 8 bytes on a 64 bit system, torn writes happens and the data is only partially written when the // application crashes at the wrong time. RouDi would then read corrupt data and try to access invalid memory. static_assert(sizeof(ShmSafeUnmanagedChunk) <= 8U, "The ShmSafeUnmanagedChunk size must not exceed 64 bit to prevent torn writes!"); ... static_assert(std::is_trivially_copyable<ShmSafeUnmanagedChunk>::value, "The ShmSafeUnmanagedChunk must be trivially copyable to prevent Frankenstein objects when the copy ctor " "works on half dead objects!");
// drop the chunk if one is returned by an overflow if (pushRet.has_value()) { pushRet.value().releaseToSharedChunk(); // tell the ChunkDistributor that we had an overflow and dropped a sample hasQueueOverflow = true; }
订阅侧 SubscriberOptions::historyRequest。RouDi 转发 SUB 消息时携带该值,发布者 tryAddQueue() 在挂接队列的同时把 history 里最后 requestedHistory 个 chunk 立即补发给新订阅者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const auto currChunkHistorySize = getMembers()->m_history.size();
if (requestedHistory > getMembers()->m_historyCapacity) { LogWarn() << "Chunk history request exceeds history capacity! Request is " << requestedHistory << ". Capacity is " << getMembers()->m_historyCapacity << "."; }
// if the current history is large enough we send the requested number of chunks, else we send the // total history const auto startIndex = (requestedHistory <= currChunkHistorySize) ? currChunkHistorySize - requestedHistory : 0u; for (auto i = startIndex; i < currChunkHistorySize; ++i) { pushToQueue(queueToAdd, getMembers()->m_history[i].cloneToSharedChunk()); }
/// @brief DDS Gateway implementation for the iceoryx to DDS direction. template <typename channel_t = gw::Channel<popo::UntypedSubscriber, dds::data_writer_t>, typename gateway_t = gw::GatewayGeneric<channel_t>> class Iceoryx2DDSGateway : public gateway_t { public: /// @brief Creates a gateway with DDS set as interface Iceoryx2DDSGateway() noexcept;
/// @note This must always be the first member and always 1 bytes in order to prevent issues with endianess when /// deserialized or incorrectly detected versions due to different size uint8_t datagramVersion{DATAGRAM_VERSION}; /// @note This must always be 1 byte in order to prevent issues with endianess when deserialized Endianess endianness{Endianess::UNDEFINED}; uint16_t userHeaderId{0xFFFF}; uint32_t userHeaderSize{0U}; uint32_t userPayloadSize{0U}; uint32_t userPayloadAlignment{0U};
局限:DDS2IceoryxGateway::discover() 是空实现(源码注释 “requires dds discovery which is currently not implemented in the used dds stack”),因此 DDS→iox 方向只能靠 TOML 静态配置服务列表,无法自动发现远端 DDS topic。
static struct cfgelem shmem_cfgelems[] = { BOOL("Enable", NULL, 1, "false", MEMBER(enable_shm), FUNCTIONS(0, uf_boolean, 0, pf_boolean), DESCRIPTION("<p>This element allows to enable shared memory in Cyclone DDS.</p>")), STRING("Locator", NULL, 1, "", MEMBER(shm_locator), FUNCTIONS(0, uf_string, ff_free, pf_string), DESCRIPTION( "<p>Explicitly set the Iceoryx locator used by Cyclone to check whether " "a pair of processes is attached to the same Iceoryx shared memory. The " "default is to use one of the MAC addresses of the machine, which should " "work well in most cases.</p>" )), STRING("Prefix", NULL, 1, "DDS_CYCLONE", MEMBER(iceoryx_service), FUNCTIONS(0, uf_string, ff_free, pf_string), DESCRIPTION( "<p>Override the Iceoryx service name used by Cyclone.</p>" )), ENUM("LogLevel", NULL, 1, "info", MEMBER(shm_log_lvl), ...
四个配置项:Enable(默认 false)、Locator(同机判定地址,默认取 MAC)、Prefix(iceoryx CaPro service 名,默认 DDS_CYCLONE)、LogLevel。
static int iceoryx_init (struct ddsi_domaingv *gv) { shm_set_loglevel(gv->config.shm_log_lvl);
char *sptr; ddsrt_asprintf (&sptr, "iceoryx_rt_%"PRIdPID"_%"PRId64, ddsrt_getpid (), gv->tstart.v); GVLOG (DDS_LC_SHM, "Current process name for iceoryx is %s\n", sptr); iox_runtime_init (sptr); free(sptr);
// FIXME: this can be done more elegantly when properly supporting multiple transports if (ddsi_vnet_init (gv, "iceoryx", NN_LOCATOR_KIND_SHEM) < 0) return -1; ddsi_factory_find (gv, "iceoryx")->m_enable = true;
SHEM locator 的地址如何来:未显式配置 SharedMemory/Locator 时取本机第一个非 loopback 网卡的 MAC 地址——它唯一标识”这台机器”,用于后续同机判定:
1 2 3 4 5 6 7 8
memset (gv->loc_iceoryx_addr.address, 0, sizeof (gv->loc_iceoryx_addr.address)); if (ddsrt_eth_get_mac_addr (gv->interfaces[if_index].name, gv->loc_iceoryx_addr.address)) { GVERROR ("Unable to get MAC address for iceoryx\n"); return -1; } gv->loc_iceoryx_addr.kind = NN_LOCATOR_KIND_SHEM; gv->loc_iceoryx_addr.port = 0;
#ifdef DDS_HAS_SHM // SHM_TODO: We avoid sending packet while data is SHMEM. // I'm not sure whether this is correct or not. if (!gv->mute && loc->c.kind != NN_LOCATOR_KIND_SHEM) #else if (!gv->mute)
// check necessary condition: fixed size data type OR serializing into shared // memory is available if (!tp->m_stype->fixed_size && (!tp->m_stype->ops->get_serialized_size || !tp->m_stype->ops->serialize_into)) { return false; }
// only VOLATILE or TRANSIENT LOCAL if(!(qos->durability.kind == DDS_DURABILITY_VOLATILE || qos->durability.kind == DDS_DURABILITY_TRANSIENT_LOCAL)) { return false; }
// only KEEP LAST if(qos->history.kind != DDS_HISTORY_KEEP_LAST) { return false; }
#ifdef DDS_HAS_SHM if (wr->m_wr->has_iceoryx) { DDS_CLOG (DDS_LC_SHM, &wr->m_entity.m_domain->gv.logconfig, "Writer's topic name will be DDS:Cyclone:%s\n", wr->m_topic->m_name); iox_pub_options_t opts = create_iox_pub_options(wqos);
// NB: This may fail due to icoeryx being out of internal resources for publishers // In this case terminate is called by iox_pub_init. // it is currently (iceoryx 2.0 and lower) not possible to change this to // e.g. return a nullptr and handle the error here. wr->m_iox_pub = iox_pub_init(&(iox_pub_storage_t){0}, gv->config.iceoryx_service, wr->m_topic->m_stype->type_name, wr->m_topic->m_name, &opts); memset(wr->m_iox_pub_loans, 0, sizeof(wr->m_iox_pub_loans)); } #endif
// NB: If we cannot take the chunk (sample) the user may lose data. // Since the subscriber queue can overflow and will evict the least recent sample. ... const iceoryx_header_t* ice_hdr = iceoryx_header_from_chunk(chunk);
// Get writer or proxy writer struct ddsi_entity_common * e = entidx_lookup_guid_untyped (gv->entity_index, &ice_hdr->guid); ... // Create struct ddsi_serdata struct ddsi_serdata* d = ddsi_serdata_from_iox(rd->m_topic->m_stype, ice_hdr->data_kind, &rd->m_iox_sub, chunk); d->timestamp.v = ice_hdr->tstamp; d->statusinfo = ice_hdr->statusinfo; ... ddsi_make_writer_info(&wrinfo, e, xqos, d->statusinfo); (void)ddsi_rhc_store(rd->m_rd->rhc, &wrinfo, d, tk);
if ((ret = dds_writer_lock(writer, &wr)) != DDS_RETCODE_OK) return ret;
// the loaning is only allowed if SHM is enabled correctly and if the type is // fixed if (wr->m_iox_pub && wr->m_topic->m_stype->fixed_size) { *sample = dds_writer_loan_chunk(wr, wr->m_topic->m_stype->iox_size); if (*sample == NULL) { ret = DDS_RETCODE_ERROR; // could not obtain a sample } } else { ret = DDS_RETCODE_UNSUPPORTED; } ...
// if the publisher can loan if (cdds_publisher->is_loaning_available) { auto sample_ptr = init_and_alloc_sample(cdds_publisher, cdds_publisher->sample_size); RET_NULL_X(sample_ptr, return RMW_RET_ERROR); *ros_message = sample_ptr; return RMW_RET_OK; } else { RMW_SET_ERROR_MSG("Borrowing loan for a non fixed type is not allowed"); return RMW_RET_ERROR; }
// if the publisher allow loaning if (cdds_publisher->is_loaning_available) { auto d = new serdata_rmw(cdds_publisher->sertype, ddsi_serdata_kind::SDK_DATA); d->iox_chunk = ros_message; // since we write the loaned chunk here, set the data state to raw shm_set_data_state(d->iox_chunk, IOX_CHUNK_CONTAINS_RAW_DATA); if (dds_writecdr(cdds_publisher->enth, d) >= 0) { return RMW_RET_OK;
非 POD 类型不能 loan,但 SHM 可用时序列化结果仍可写进共享内存(退化为”一次序列化 + 零网络拷贝”):
1 2 3 4 5 6 7
if (dds_is_shared_memory_available(pub->enth)) { auto sample_ptr = init_and_alloc_sample(pub, serialized_message->buffer_length); RET_NULL_X(sample_ptr, return RMW_RET_ERROR); memcpy(sample_ptr, serialized_message->buffer, serialized_message->buffer_length); shm_set_data_state(sample_ptr, IOX_CHUNK_CONTAINS_SERIALIZED_DATA); d->iox_chunk = sample_ptr; }
# 2. 打开 SHM 日志观察是否真的走了共享内存 export CYCLONEDDS_URI='<CycloneDDS><Domain><SharedMemory><Enable>true</Enable><LogLevel>verbose</LogLevel></SharedMemory><Tracing><Verbosity>config</Verbosity><OutputFile>stdout</OutputFile></Tracing></Domain></CycloneDDS>' # 日志中应出现 "My iceoryx address: ..."、"Writer's topic name will be DDS:Cyclone:<topic>"