面试可以直接用的IPC小项目--SHM实现消息队列

相信不少小伙伴在面试过程中都被问到过共享内存。通过阅读本文您可以学习到一套比较完整的共享内存使用流程,以及共享内存在使用过程中常见问题的处理方法。

本文介绍一个基于共享内存SHM实现的消息队列,通过该队列能够实现多进程数据通信。在工程项目中,也具有一定的参考价值。

具体内容如下:

  1. SHM循环队列需求说明
  2. SHM循环队列整体设计
  3. SHM Manager设计
  4. SHM Mutex设计
  5. Circular Queue结构设计
  6. Circular Queue入队设计
  7. Circular Queue出队设计
  8. SHM Queue设计
  9. 总结

00 SHM循环队列需求说明

00.1 功能性需求

  • 需要基于共享内存,实现多个进程(非父子关系)之间相互通信的消息队列。
  • 每个进程都可以写入消息和读出消息。
  • 共享内存的消息队列使用循环队列。

00.2 稳定性需求

其中某个进程崩溃时,要尽可能不影响其他进程访问共享内存。
在这里插入图片描述

01 SHM循环队列整体设计

为了实现上述的SHM消息队列,需要完成如下一些模块的设计:

  1. 一个管理SHM对象的模块,SHM Manager,用于创建/销毁或打开/关闭SHM对象。
  2. 一个维护循环队列的模块,Circular Queue。
  3. 一个保证多个进程安全访问共享内存的同步模块,SHM Mutex。
  4. 一个用户调用接口模块,SHM Queue。
    在这里插入图片描述

为了简化设计,我们将进程分为两类:

  1. 一个manager进程,用于创建和销毁SHM资源,同时也能读写消息。
  2. 多个Normal进程,不需要创建和销毁SHM资源,只需要打开和关闭由manager进程创建的SHM资源,并通过循环队列读写消息。

程序运行流程如下:
图片
在Manager进程启动,并创建SHM资源后,SHM进入可用状态。在此状态中,Normal进程才能启动并读写SHM循环消息队列。

02 SHM Manager设计

这里我们使用POSIX SHM接口(使用其他SHM方式也可以)。

02.1 ShmManager接口定义

ShmManager接口定义如下:

class ShmManager {
   
   
public:
    ShmManager();
    ~ShmManager();

    // 初始化SHM资源对象,即创建/打开
    bool Init(uint64_t shm_size, bool is_manager);
    // 释放SHM资源,即销毁/关闭
    void Release();

    void* GetMemory(uint64_t& mem_size);

private:
    bool is_manager_{
   
   false};
    int shm_fd_{
   
   -1};
    void* start_ptr_{
   
   nullptr};
    uint64_t shm_size_{
   
   0};

    static const std::string shm_name_;
};

02.2 创建/打开SHM

创建/打开SHM资源的代码如下:

const std::string ShmManager::shm_name_("/shm_queue");

bool ShmManager::Init(uint64_t shm_size, bool is_manager) {
   
   
    shm_size_ = shm_size;
    is_manager_ = is_manager;

    int oflag = O_RDWR;
    if (is_manager_) {
   
   
        // manager进程需要O_CREAT标记创建SHM资源
        oflag |= O_CREAT;
        // manager进程需要删除历史残留文件,normal进程不需要
        shm_unlink(shm_name_.c_str());
    }

    // 1. 创建共享内存对象
    shm_fd_ = shm_open(shm_name_.c_str(), oflag, 0666);
    if (shm_fd_ == -1) {
   
   
        std::cerr << "shm_open failed:" << strerror(errno)
            << std::endl;
        return false;
    }

    if (is_manager_) {
   
   
        // 2. 设置共享内存大小,normal进程不需要
        if (ftruncate(shm_fd_, shm_size_) == -1) {
   
   
            std::cerr << "ftruncate failed:" << strerror(errno)
                << std::endl;
            return false;
        }
    }

    // 3. 将共享内存映射到当前进程地址空间
    start_ptr_ = mmap(0, shm_size_, PROT_WRITE|PROT_READ,
        MAP_SHARED, shm_fd_, 0);
    if (start_ptr_ == MAP_FAILED) {
   
   
        std::cerr << "mmap failed:" << strerror(errno)
            << std::endl;
        start_ptr_ = nullptr;
        return false;
    }

    return true;
}

上面的代码产生了一个共享内存的区域,共享内存的首地址为start_ptr_,大小为shm_size_。

对于manager进程来说,调用shm_open时,需要使用O_RDWR | O_CREAT参数,以确保能新建SHM对象。对于normal进程,需要使用O_RDWR参数,以确保打开已有的SHM对象。

normal进程不需要调用ftruncate函数,来修改共享内存大小。

另外,manager进程需要在调用shm_open前,调用shm_unlink函数,以防止之前由于进程崩溃等原因,残留的shm文件能够被清理掉。以增加程序稳定性。

02.3 销毁/关闭SHM

销毁/关闭SHM资源的代码如下:

void ShmManager::Release() {
   
   
    // 1. 解除映射
    if (start_ptr_ != nullptr) {
   
   
        munmap(start_ptr_, shm_size_);
        start_ptr_ = nullptr;
    }
    // 2. 关闭共享内存对象
    if (shm_fd_ != -1) {
   
   
        close(shm_fd_);
        shm_fd_ = -1;
    }
    // 3. 删除位于/dev/shm下的shm文件,normal进程不需要
    if (is_manager_) {
   
   
        shm_unlink(shm_name_.c_str());
    }

    shm_size_ = 0;
}

对于normal进程,在退出过程中不需要删除shm文件。

对于SHM文件,如果在调用shm_unlink函数之前,其它进程已经打开了该文件的文件描述,那么此文件描述一直有效,即使某些进程通过shm_unlink函数删除了该文件。

02.4 其它接口

GetMemroy接口实现如下:

void* ShmManager
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值