apollo cyber共享内存解析

通讯整体流程解析

代码解析

首先先看基础类state类(主要用于管理共享内存的状态),其定义如下所示:

class State {
 public:
  explicit State(const uint64_t& ceiling_msg_size);
  virtual ~State();

  void DecreaseReferenceCounts() {
    uint32_t current_reference_count = reference_count_.load();
    do {
      if (current_reference_count == 0) {
        return;
      }
    } while (!reference_count_.compare_exchange_strong(
        current_reference_count, current_reference_count - 1));
  }

  void IncreaseReferenceCounts() { reference_count_.fetch_add(1); }

  uint32_t FetchAddSeq(uint32_t diff) { return seq_.fetch_add(diff); }
  uint32_t seq() { return seq_.load(); }

  void set_need_remap(bool need) { need_remap_.store(need); }
  bool need_remap() { return need_remap_; }

  uint64_t ceiling_msg_size() { return ceiling_msg_size_.load(); }
  uint32_t reference_counts() { return reference_count_.load(); }

 private:
  std::atomic<bool> need_remap_ = {false};
  std::atomic<uint32_t> seq_ = {0};
  std::atomic<uint32_t> reference_count_ = {0};
  std::atomic<uint64_t> ceiling_msg_size_;
};

在看下实际存放消息的block定义,其类定义如下所示:

class Block {
  friend class Segment;

 public:
  Block();
  virtual ~Block();

  uint64_t msg_size() const { return msg_size_; }
  void set_msg_size(uint64_t msg_size) { msg_size_ = msg_size; }

  uint64_t msg_info_size() const { return msg_info_size_; }
  void set_msg_info_size(uint64_t msg_info_size) {
    msg_info_size_ = msg_info_size;
  }

  static const int32_t kRWLockFree;
  static const int32_t kWriteExclusive;
  static const int32_t kMaxTryLockTimes;

 private:
  bool TryLockForWrite();
  bool TryLockForRead();
  void ReleaseWriteLock();
  void ReleaseReadLock();

  // 加锁的个数
  std::atomic<int32_t> lock_num_ = {0};

  uint64_t msg_size_;	
  uint64_t msg_info_size_;
};

我们类中的几个成员函数TryLockForWrite、TryLockForRead、ReleaseWriteLock和ReleaseReadLock,首先现从TryLockForWrite分析起:

bool Block::TryLockForWrite() {
  // kRWLockFree定义为0
  int32_t rw_lock_free = kRWLockFree;
  // 如果lock_num_为0,则将lock_num_置为-1,则说明这个块加上了读锁
  if (!lock_num_.compare_exchange_weak(rw_lock_free, kWriteExclusive,
                                       std::memory_order_acq_rel,
                                       std::memory_order_relaxed)) {
    ADEBUG << "lock num: " << lock_num_.load();
    return false;
  }
  return true;
}

这里通过了原子操作保证了2个进程对其操作的线程安全(对于统一块内存在不同进程中使用原子操作也是原子的)。

TryLockForRead函数实现如下所示:

bool Block::TryLockForRead() {
  int32_t lock_num = lock_num_.load();
  // lock_num < kRWLockFree则说明加了写锁
  if (lock_num < kRWLockFree) {
    AINFO << "block is being written.";
    return false;
  }

  int32_t try_times = 0;
  // 对block加读锁,这里最大重试5次
  while (!lock_num_.compare_exchange_weak(lock_num, lock_num + 1,
                                          std::memory_order_acq_rel,
                                          std::memory_order_relaxed)) {
    ++try_times;
    if (try_times == kMaxTryLockTimes) {
      AINFO << "fail to add read lock num, curr num: " << lock_num;
      return false;
    }

    lock_num = lock_num_.load();
    // 这里说明这个内存块提前被写锁抢占了
    if (lock_num < kRWLockFree) {
      AINFO << "block is being written.";
      return false;
    }
  }

  return true;
}

释放锁的实现如下所示:

// 写锁释放,获取写锁的时候lock_num_为-1,释放则直接+1,这里也可以直接store(kRWLockFree)
void Block::ReleaseWriteLock() { lock_num_.fetch_add(1); }
// 读锁共享,释放的时候直接-1
void Block::ReleaseReadLock() { lock_num_.fetch_sub(1); }

从上面uml类图可以看出共享内存都继承与Segment类,我们这里先解析一下Segment的作用:

class Segment;
using SegmentPtr = std::shared_ptr<Segment>;

struct WritableBlock {
  uint32_t index = 0;
  Block* block = nullptr;
  uint8_t* buf = nullptr;
};
using ReadableBlock = WritableBlock;

class Segment {
 public:
  explicit Segment(uint64_t channel_id);
  virtual ~Segment() {}
  // 获取一个可以写的内存块
  bool AcquireBlockToWrite(std::size_t msg_size, WritableBlock* writable_block);
  // 释放一个可写的内存块
  void ReleaseWrittenBlock(const WritableBlock& writable_block);
  // 获取一个可读的内存块
  bool AcquireBlockToRead(ReadableBlock* readable_block);
  // 释放可读的内存块
  void ReleaseReadBlock(const ReadableBlock& readable_block);

 protected:
  virtual bool Destroy();
  virtual void Reset() = 0;
  virtual bool Remove() = 0;
  virtual bool OpenOnly() = 0;
  virtual bool OpenOrCreate() = 0;

  // 共享内存初始化标志
  bool init_;
  // 配置文件信息
  ShmConf conf_;
  // 对应通道的唯一标识
  uint64_t channel_id_;

  // 管理共享内存的状态信息
  State* state_;
  // 管理共享内存中的数据块
  Block* blocks_;
  // 指向映射的共享内存段指针
  void* managed_shm_;

  // 互斥锁
  std::mutex block_buf_lock_;

  // 用于快速定位和访问特定的数据块, 键是块索引
  std::unordered_map<uint32_t, uint8_t*> block_buf_addrs_;

 private:
  bool Remap();
  bool Recreate(const uint64_t& msg_size);
  uint32_t GetNextWritableBlockIndex();
};

接下来我们来看类的具体实现,先从AcquireBlockToWrite开始看起:

bool Segment::AcquireBlockToWrite(std::size_t msg_size,
                                  WritableBlock* writable_block) {

  RETURN_VAL_IF_NULL(writable_block, false);
  // 如果共享内存还没有创建则调用OpenOrCreate去创建共享内存块
  if (!init_ && !OpenOrCreate()) {
    AERROR << "create shm failed, can't write now.";
    return false;
  }

  bool result = true;
  if (state_->need_remap()) {
    result = Remap();
  }
    
  // 消息大小超过目前最大则重新分配共享内存 
  if (msg_size > conf_.ceiling_msg_size()) {
    AINFO << "msg_size: " << msg_size
          << " larger than current shm_buffer_size: "
          << conf_.ceiling_msg_size() << " , need recreate.";
    result = Recreate(msg_size);
  }

  if (!result) {
    AERROR << "segment update failed.";
    return false;
  }
  // 获取一块block返回,用于写入要发送的消息
  uint32_t index = GetNextWritableBlockIndex();
  writable_block->index = index;
  writable_block->block = &blocks_[index];
  writable_block->buf = block_buf_addrs_[index];
  return true;
}

接下来我们看下OpenOrCreate这个函数,从segment的类定义可以看出OpenOrCreate()是一个纯虚函数,实现在各自的派生类中,在看共享内存的实现之前我们先看下posix中跟共享内存相关的函数:

// 创建共享内存
// name 	: 共享内存块的名称
// oflag	: 打开共享内存标志(O_RDWR, O_CREAT等等)
// mode 	: 如果创建新的共享内存对象,则指定权限位
int shm_open(const char *name, int oflag, mode_t mode);

// 用于定义和调整共享内存的大小
// fd 		: 共享内存的文件描述符
// lenght 	: 新的大小
int ftruncate(int fd, off_t length)

// 将共享内存对象映射到调用进程的地址空间
// addr 	: 建议映射的起使地址
// length 	: 映射的区域大小
// prot 	: 保护标志(PROT_READ、PROT_WRITE等)
// flags 	: 映射类型标志
// fd		: 文件描述符
// offset	: 相对于文件开始的偏移量
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)

// 解除映射
// addr 	: 映射的起始地址
// length	: 映射区域的长度
int munmap(void *addr, size_t length)

// 删除一个共享内存对象的名称
// name	: 要删除的共享内存对象的名称
int shm_unlink(const char *name)

这里我们只关注posix接口的共享内存内部实现,其实现在PosixSegment类中ÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值