通讯整体流程解析
代码解析
首先先看基础类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类中ÿ