FastDDS之共享内存

transport层负责为DDS用户数据收发和服务发现提供通信。本文以Fast DDS 2.14版本介绍共享内存。
在这里插入图片描述

使用DomainParticipant的GuidPrefix_t来判断对端是否在同一个主机运行。同一主机中两个participants的GuidPrefix_t的前四个字节是相同的。可以通过API is_on_same_host_as() 来检查这一条件。
使用共享内存的原因如下:

  1. 支持大消息,网络协议需要对数据进行分片,以符合特定协议和网络栈的要求,这会增加通信开销。共享内存传输允许复制完整的消息,唯一的大小限制是机器的内存容量。
  2. 减少内存拷贝:发送相同数据到不同端点时,SHM只需要给所有目的端点共享同一内存缓存,其他协议只需要为每个端点执行一次消息拷贝
  3. 减少操作系统负载:初始化设置完成之后,相比较网络通信,共享内存的系统调用更少,因此,使用SHM可以在性能和时间消耗上获得提升。

数据类型和概念

在这里插入图片描述

Segment

Segment 是一块共享内存,不同的进程都可以访问。 每个DomainParticipant都会配置和创建一个共享内存用于读写。注意:以更高权限的用户(例如,root)启动任何进程都可能导致通信问题,因为由非特权用户运行的进程可能无法将数据写入该内存段。每个segment都有一个16位的UUID唯一标识这一块内存,这个segment ID用于识别和访问每个DomainParticipant的段。Segment ID在构造SharedMemManager::Segment时通过调用generate_segment_id_and_name来生成:

void generate_segment_id_and_name(const std::string& domain_name) {
   
            static constexpr uint32_t MAX_COLLISIONS = 16;
            uint32_t collisions_count = MAX_COLLISIONS;

            do {
   
                // No collisions are most probable
                segment_id_.generate();
                ...
}

UUID具体的生成算法:

void generate(
            uint8_t* uuid,
            size_t len)
    {
   
        uint64_t now = std::chrono::high_resolution_clock::now().time_since_epoch().count();

        {
   
            // Two IDs cannot be generated in this process at the same exact time
            std::lock_guard<std::mutex> lock(gen_mutex_);

            while (now == last_gen_time_)
            {
   
                std::this_thread::sleep_for(std::chrono::microseconds(1));
                now = std::chrono::high_resolution_clock::now().time_since_epoch().count();
            }

            last_gen_time_ = now;
        }

        // Mersenne twister generator
        std::mt19937 gen;

        // The seed is derived from the ProcessID and the steady clock
        std::array<uint32_t, 4> seq;
        uint64_t pid = this_process_pid();
        seq[0] = static_cast<uint32_t>(pid);
        seq[1] = static_cast<uint32_t>(pid >> 32);
        seq[2] = static_cast<uint32_t>(now);
        seq[3] = static_cast<uint32_t>(now >> 32);

        std::seed_seq seed_seq(seq.begin(), seq.end());
        gen.seed(seed_seq);

        std::uniform_int_distribution<> dis(0, 255);

        for (size_t i = 0; i < len; i++)
        {
   
            uuid[i] = static_cast<uint8_t>(dis(gen));
        }
    }

Segment Buffer

Segment Buffer是在Segment中分配的具体的内存,用于存放DDS消息。Domainparticipant写入segment的每条消息都会放到不同的buffer中。对应到代码中就是SharedMemBuffer对象。每次发送时都会构造一个SharedMemBuffer对象,用于管理这条消息的内存

std::shared_ptr<Buffer> alloc_buffer(
                uint32_t size,
                const std::chrono::steady_clock::time_point& max_blocking_time_point)
        {
   
            ...
            try
            {
   
                buffer_node = pop_free_node();

                data = segment_->get().allocate(size);
                free_bytes_ -= size;

                buffer_node->data_offset = segment_->get_offset_from_address(data);
                buffer_node->data_size = size;

                auto validity_id = buffer_node->status.load(std::memory_order_relaxed).validity_id;

                new_buffer = std::make_shared<SharedMemBuffer>(segment_, segment_id_, buffer_node,
                                static_cast<uint32_t>(validity_id));
        ...

《TODO 插图》

Buffer Descriptor

指向特定segment中的一个segment buffer,包含了segment id和segment buffer中基于segment base的偏移。当通过共享内存方式在不同的domainparticipant之间通信时,只会分发这个Buffer Descriptor,而不是复制消息本身,共享内存的通信值发送Buffer Descriptor,避免的消息体的拷贝,接收方的domainparticipant可以通过Buffer Descriptor访问共享内存缓冲区中的消息,因为该描述符唯一的标识了segment ID和偏移量。代码中对应SharedMemGlobal::BufferDescriptor这个类,定义如下:

   struct BufferDescriptor
    {
   
        BufferDescriptor()
            : buffer_node_offset(0)
            , validity_id(0)
        {
   
        }

        BufferDescriptor(
                const SharedMemSegment::Id& segment_id,
                SharedMemSegment::Offset offset,
                uint32_t validity)
            : source_segment_id(segment_id)
            , buffer_node_offset(offset)
            , validity_id(validity)
        {
   
        }

        SharedMemSegment::Id source_segment_id;
        SharedMemSegment::Offset buffer_node_offset;
        uint32_t validity_id;
    };

在讲消息写入共享内存后,会构造出一个SharedMemGlobal::BufferDescriptor对象存入port的共享内存,然后通知对端通过读port中的SharedMemGlobal::BufferDescriptor进而再读取Segment Buffer中的数据,具体可以参考下面的数据发送部分。

Port

Port是另一块共享内存,用于收发Buffer Descriptor的通道。是一个环形buffer,任意domainparticipant都可以读写。有一个唯一的32位id标识Port,这个id在服务发现阶段会被分享给远端。每个DomainParticipant都会配置和创建一个port用于接收Buffer Descriptors,每个DomainParticipant会为接收Port创建一个Listener,所以当推送一个新的Buffer Descriptor到port的时候会收到通知。
《TODO 插图》

Port Health Check

每次DomainParticipant读或写一个Port的时候会执行Health Check检查它的正确性。这是因为有可能进程正在使用Port的时候crash了,此时Port是无效的。如果attach的Listener在给定的超时时间内没有响应,则认为该端口已经损坏,将会被销毁并重新创建。

在这里插入图片描述

SharedMemTransportDescriptor

除了从TransportDescriptorInterface中继承的字段外,SharedMemTransportDescriptor还包含以下字段:

Member Data type Default Accessor / Mutator Description
segment_size_ uint32_t 512*1024 segment_size() Size of the shared memory segment
port_queue_capacity_ uint32_t 512 port_queue_capacity() The size of the listening port
healthy_check_timeout_ms_ uint32_t 1000 healthy_check_timeout_ms() Timeout for the health check of ports
rtps_dump_file_ string “” rtps_dump_file() Full path of the protocol dump file.
default_reception_threads ThreadSettings default_reception_threads Default ThreadSettings for the reception threads.
reception_threads std::map<uint32_t, ThreadSettings> reception_threads ThreadSettings for the reception threads on specific ports.
dump_thread() ThreadSettings
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值