一、概述
DDS中数据的传输通过传输层实现,DDS定义了一个传输API,并且可以运行实现该API的插件;这样DDS就不局限于特定的传输,应用程序可以根据需要选择或实现适合需求的传输层插件。传输层位于Fast-DDS层级模型的最底层,传输层可以使用多种不同的传输协议。Fast-DDS具有五种已实现的传输插件,分别是UDPv4、UDPv6、TCPv4、TCPv6和SHM。
Fast-DDS层级模型架构图
二、共享内存通信初始化
Fast-DDS共享内存通信初始化流程
关键概念:
SharedMemoryManager:通过实例化SharedMemoryManager对象,应用程序可以访问内部维护的共享内存资源。每个进程至少需要一个内存管理器。SharedMemoryManager为应用程序提供了创建共享内存段、在段中分配数据缓冲区、将缓冲区描述符推送到共享内存端口以及创建与端口相关联的监听器的功能;
Segment:持有一个固定大小的内存块,可以被不同进程访问;共享内存段有一个全局名称,任何知道该名称的进程都可以打开该segment并将其映射到其地址空间中;
SegmentId:唯一标识共享内存段的名称,由16个字符的UUID描述;
SharedMemBuffer:继承自Buffer,在共享内存段中分配的缓存区;
BufferNode:缓存区描述符,这些描述符就像指向缓存区的指针,可以在进程之间以最小的成本进行复制,描述符包含SegmentId和从段的底部到数据的偏移量offset;
Port:由port_id标识的通信信道,通过该通道,缓存区描述符可以被发送到其他进程。
三、主要流程分析
3.1 SharedMemTransport初始化
bool SharedMemTransport::init()
SharedMemTransport是Fast-DDS共享内存通信的一个实现,该类继承并实现Fast-DDS定义的传输API TransportInterface。在SharedMemTransport初始化时会通过SharedMemManager创建一个SharedMemManager实例,然后通过该实例进一步创建一个Segment实例;Segment持有一块用于进程间通信的固定大小的共享内存段。
bool SharedMemTransport::init()
{
// TODO(Adolfo): Calculate this value from UDP sockets buffers size.
static constexpr uint32_t shm_default_segment_size = 512 * 1024;
if (configuration_.segment_size() == 0)
{
configuration_.segment_size(shm_default_segment_size);
}
if (configuration_.segment_size() < configuration_.max_message_size())
{
logError(RTPS_MSG_OUT, "max_message_size cannot be greater than segment_size");
return false;
}
try
{
shared_mem_manager_ = SharedMemManager::create(SHM_MANAGER_DOMAIN);
shared_mem_segment_ = shared_mem_manager_->create_segment(configuration_.segment_size(),
configuration_.port_queue_capacity());
// Memset the whole segment to zero in order to force physical map of the buffer
auto buffer = shared_mem_segment_->alloc_buffer(configuration_.segment_size(),
(std::chrono::steady_clock::now() + std::chrono::milliseconds(100)));
memset(buffer->data(), 0, configuration_.segment_size());
buffer.reset();
if (!configuration_.rtps_dump_file().empty())
{
auto packets_file_consumer = std::unique_ptr<SHMPacketFileConsumer>(
new SHMPacketFileConsumer(configuration_.rtps_dump_file()));
packet_logger_ = std::make_shared<PacketsLog<SHMPacketFileConsumer>>();
packet_logger_->RegisterConsumer(std::move(packets_file_consumer));
}
}
catch (std::exception& e)
{
logError(RTPS_MSG_OUT, e.what());
return false;
}
return true;
}
创建SharedMemTransport实例
通过SharedMemManager的静态成员函数create()可以创建一个SharedMemManager实例,create()的实现如下:
static std::shared_ptr<SharedMemManager> create(
const std::string& domain_name)
{
return std::shared_ptr<SharedMemManager>(new SharedMemManager(domain_name));
}
构造SharedMemManager对象时会先对传入的domain_name进行校验,然后计算共享内存中用于存储共享内存描述符的额外的空间。
SharedMemManager(
const std::string& domain_name)
: segments_mem_(0)
, global_segment_(domain_name)
, watch_task_(SegmentWrapper::WatchTask::get())
{
static_assert(std::alignment_of<BufferNode>::value % 8 == 0, "SharedMemManager::BufferNode bad alignment");
if (domain_name.length() > SharedMemGlobal::MAX_DOMAIN_NAME_LENGTH)
{
throw std::runtime_error(
domain_name +
" too long for domain name (max " +
std::to_string(SharedMemGlobal::MAX_DOMAIN_NAME_LENGTH) +
" characters");
}
per_allocation_extra_size_ =
SharedMemSegment::compute_per_allocation_extra_size(std::alignment_of<BufferNode>::value,
domain_name);
}
创建Segment实例
通过SharedMemManager实例的create_segment()方法可以创建一个持有一块儿固定大小共享内存的Segment实例保存在SharedMemTransport对象的成员变量shared_mem_segment_中,共享内存的创建便发生在Segment构造时。
std::shared_ptr<Segment> create_segment(
uint32_t size,
uint32_t max_allocations)
{
return std::make_shared<Segment>(size + segment_allocation_extra_size(max_allocations), size, max_allocations,
global_segment_.domain_name());
}
注意,创建的共享内存的大小除了我们配置的大小外还需要额外的空间保存共享内存的描述符,在传输时只需要拷贝描述符来告诉其他进程数据在共享内存中的存储位置。
Segment(
uint32_t size,
uint32_t payload_size,
uint32_t max_allocations,
const std::string& domain_name)
: segment_id_()
, overflows_count_(0)
{
generate_segment_id_and_name(domain_name);
SharedMemSegment::remove(segment_name_.c_str());
try
{
segment_ = std::unique_ptr<SharedMemSegment>(
new SharedMemSegment(boost::interprocess::create_only, segment_name_.c_str(), size));
logInfo(RTPS_TRANSPORT_SHM, "malloc a shared memory with tag: "<<segment_name_<<", and size of this memory is "<<size<<", payload size: "<<payload_size);
}
catch (const std::exception& e)
{
logError(RTPS_TRANSPORT_SHM, "Failed to create segment " << segment_name_
<< ": " << e.what());
throw;
}
free_bytes_ = payload_size;
// Alloc the buffer nodes
logInfo(RTPS_TRANSPORT_SHM, "allocate shared memory with tag "<<segment_name_<<", and size of BufferNode is "<<max_allocations);
auto buffers_nodes = segment_->get().construct<BufferNode>
(boost::interprocess::anonymous_instance)[max_allocations]();
// All buffer nodes are free
for (uint32_t i = 0; i < max_allocations; i++)
{
buffers_nodes[i].status.exchange({0, 0, 0});
buffers_nodes[i].data_size = 0;
buffers_nodes[i].data_offset = 0;
free_buffers_.push_back(&buffers_nodes[i]);
}
}
初始化共享内存段
通过shared_mem_segment_从共享内存中申请内存并通过memset初始化
// Memset the whole segment to zero in order to force physical map of the buffer
auto buffer = shared_mem_segment_->alloc_buffer(configuration_.segment_size(),
(std::chrono::steady_clock::now() + std::chrono::milliseconds(100)));
memset(buffer->data(), 0, configuration_.segment_size());
buffer.reset();
四、参考链接
- 6. Transport Layer — Fast DDS 2.1.3 documentation
- 第 8 章 进程间通讯 - 8.3. 托管共享内存 - 《Boost C++ 库》 - 书栈网 · BookStack
- https://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/shared_memory/shared_memory.html
- https://github.com/eProsima/Fast-DDS/blob/v2.0.0/doc/design/shared-memory-transport/interprocess_shared_mem.md