环形缓冲区实现共享内存

共享内存的问题在于如果发送进程频繁写数据,而读取进程没来得及读,会导致共享内存里的数据被覆盖,共享内存(shm_open + mmap)只是提供了一块所有进程都能访问的公共地址空间,它本身不带任何缓存、消息队列、版本控制的机制。操作系统不会帮你保存"历史数据",也不会阻止另一个进程覆盖你写的内容。

这意味着:

  • 多个写者同时写 → 数据竞争(race condition);

  • 写者频繁写而读者没跟上 → 前面写的内容会被新的覆盖;

  • 除非你自己加同步机制缓冲结构,否则共享内存就只是"一块裸内存"。

使用 环形缓冲区(ring buffer)

原理是:

  • 分配一块较大的共享内存;
  • 定义头尾指针(head, tail);
  • 写者往尾部写数据;
  • 读者从头部读数据;
  • 用原子操作或信号量控制 head/tail

这种方式可以:

✅ 支持多个连续消息;

✅ 避免频繁 mmap

✅ 控制覆盖策略(例如"满了丢最旧"或"阻塞等待")。

c++ 复制代码
//shared_memory.h
#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
#include <sys/types.h>
#include <vector>
#include <atomic>

typedef struct img_data{
    size_t lengh;
    std::vector<char> data;
}IMG_DATA;

class SharedMemory{
public:
    struct shared_header{
        std::atomic<size_t> head_;
        std::atomic<size_t> tail_;
        size_t buffer_size;
        unsigned char memory_[1];
    };


    SharedMemory(const std::string &name, size_t size, bool create=true);
    ~SharedMemory();

    //get shared memory address
    void * get_memory() const;

    //notify other process data is ready.
    void notify();

    void wait();
    int wait_until_timedout(int milliseconds);

    size_t get_size() const;
    bool write(const void *data, size_t len);
    void write_data(size_t tail, const void * data, size_t len);
    ssize_t read(void * out, size_t maxlen);
private:
    std::string name_;

    size_t size_;
    shared_header * header_;
    int shm_fd_;
    sem_t *semaphore_;
    std::string sem_name_;
    bool creator_;
private:
    void create_shared_memory(size_t size);
    void attach_shared_memory();
};

源文件实现

c++ 复制代码
#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
#include <sys/types.h>
#include <vector>
#include <atomic>

typedef struct img_data{
    size_t lengh;
    std::vector<char> data;
}IMG_DATA;

class SharedMemory{
public:
    struct shared_header{
        std::atomic<size_t> head_;
        std::atomic<size_t> tail_;
        size_t buffer_size;
        unsigned char memory_[1];
    };


    SharedMemory(const std::string &name, size_t size, bool create=true);
    ~SharedMemory();

    //get shared memory address
    void * get_memory() const;

    //notify other process data is ready.
    void notify();

    void wait();
    int wait_until_timedout(int milliseconds);

    size_t get_size() const;
    bool write(const void *data, size_t len);
    void write_data(size_t tail, const void * data, size_t len);
    ssize_t read(void * out, size_t maxlen);
private:
    std::string name_;

    size_t size_;
    shared_header * header_;
    int shm_fd_;
    sem_t *semaphore_;
    std::string sem_name_;
    bool creator_;
private:
    void create_shared_memory(size_t size);
    void attach_shared_memory();
};

uos@uos-PC:/software/code/test/tls_server/ipc_demo$ 
uos@uos-PC:/software/code/test/tls_server/ipc_demo$ 
uos@uos-PC:/software/code/test/tls_server/ipc_demo$ cat shared_memory.cpp
#include "shared_memory.h"

SharedMemory::SharedMemory(const std::string& name, size_t size, bool create)
    : name_(name), size_(sizeof(shared_header) + size - 1), header_(nullptr), shm_fd_(-1), semaphore_(nullptr) {

    creator_ = create;
    // 创建或获取共享内存
    if (create) {
        create_shared_memory(size);
    } else {
        attach_shared_memory();
    }
    header_ = (shared_header*)mmap(nullptr, size_, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd_, 0);
    if(header_ == MAP_FAILED){
        throw std::runtime_error(std::string("mmap failed: ")+strerror(errno));
    }
    if(creator_){
        header_->head_.store(0);
        header_->tail_.store(0);
        header_->buffer_size = size;
    }
}

void SharedMemory::create_shared_memory(size_t size) {
    std::string shm_name = "/" + name_;
    sem_name_ = "/sem_"+name_;
    shm_unlink(shm_name.c_str());
    shm_fd_ = shm_open(shm_name.c_str(), O_CREAT|O_RDWR, 0666);
    if(shm_fd_ == -1){
        std::cerr << "shm_open failed: " << strerror(errno) << std::endl;
        throw std::runtime_error("shm_open failed");
    }
    if(ftruncate(shm_fd_,size) == -1){
        throw std::runtime_error("ftruncate failed");
    }
    sem_unlink(sem_name_.c_str());
    // 创建信号量用于同步
    semaphore_ = sem_open(sem_name_.c_str(), O_CREAT, 0644, 0);
    if (semaphore_ == SEM_FAILED) {
        std::cerr << "sem_open failed: " << strerror(errno) << std::endl;
        throw std::runtime_error("sem_open failed");
    }
}

void SharedMemory::attach_shared_memory() {
    std::string shm_name = "/" + name_;
    shm_fd_ = shm_open(shm_name.c_str(), O_RDWR, 666);
    if(shm_fd_ == -1){
        std::cerr << "shm_open failed: " << strerror(errno) << std::endl;
        throw std::runtime_error("shm_open failed");
    }
    // 获取信号量
    std::string semName = "/sem_" + name_;
    semaphore_ = sem_open(semName.c_str(), 0);
    if (semaphore_ == SEM_FAILED) {
        std::cerr << "sem_open failed: " << strerror(errno) << std::endl;
        throw std::runtime_error("sem_open failed");
    }
}

SharedMemory::~SharedMemory() {
    if (header_ != nullptr && header_ != (void*)-1) {
        munmap(header_, size_);
    }
    if(shm_fd_!=-1){
        close(shm_fd_);
    }
    if (semaphore_ != nullptr && semaphore_ != SEM_FAILED) {
        sem_close(semaphore_);
    }
    if(creator_){
        std::string shm_name = "/" + name_;
        shm_unlink(shm_name.c_str());
        sem_unlink(sem_name_.c_str());
    }
}

void* SharedMemory::get_memory() const {
    return header_->memory_;
}

void SharedMemory::notify() {
    sem_post(semaphore_);
}

int SharedMemory::wait_until_timedout(int milliseconds) {
    int return_status = 0;
    struct timespec ts;
    ts.tv_sec = milliseconds / 1000; //s
    ts.tv_nsec = (milliseconds %1000) * 1000000; //ns
    int ret = sem_timedwait(semaphore_, &ts);
    if(ret!=0){
        return_status = errno;
    }
    return return_status;
}
void SharedMemory::wait(){
    sem_wait(semaphore_);
}
size_t SharedMemory::get_size() const {
    return header_->buffer_size;
}

bool SharedMemory::write(const void *data, size_t len){
    if(len + sizeof(size_t) > header_->buffer_size){
        std::cerr<<"message too large for buffer\n";
        return false;
    }
    while(true){
        size_t head = header_->head_.load(std::memory_order_acquire);
        size_t tail = header_->tail_.load(std::memory_order_acquire);

        size_t used = (tail >= head)?(tail - head):(header_->buffer_size - head + tail);
        size_t free_space = header_->buffer_size -used - 1; //reserve 1byte for empty or full flag
        if(free_space < len + sizeof(size_t)){
            //mem cache is full, waitting
            usleep(1000);
            continue;
        }
        write_data(tail, data, len);
        return true;
    }

    return true;
}

void SharedMemory::write_data(size_t tail, const void * data, size_t len){
    size_t data_len = static_cast<size_t>(len);
    //write len
    if(tail + sizeof(size_t) <= header_->buffer_size){
        memcpy((unsigned char*)header_->memory_ + tail, &data_len, sizeof(size_t));
    }
    else{
        size_t first = header_->buffer_size - tail;
        memcpy((unsigned char*)header_->memory_+tail, &data_len, first);
        memcpy((unsigned char*)header_->memory_, &data_len + first, sizeof(size_t) - first);
    }
    tail = (tail + sizeof(size_t)) % header_->buffer_size;

    //write data
    if(tail + len <= header_->buffer_size){
        memcpy((unsigned char*)header_->memory_ + tail, data, len);
    }
    else{
        size_t first = header_->buffer_size - tail;
        memcpy((unsigned char*)header_->memory_+tail, data, first);
        memcpy((unsigned char*)header_->memory_, (unsigned char*)data + first, len - first);
    }
    tail = (tail + len) % header_->buffer_size;
    header_->tail_.store(tail, std::memory_order_release);
    notify();
}

ssize_t SharedMemory::read(void * out, size_t maxlen){
    size_t head = header_->head_.load(std::memory_order_acquire);
    size_t tail = header_->tail_.load(std::memory_order_acquire);

    if(tail == head){
        return 0;
    }
    uint32_t data_len;
    memcpy(&data_len, (unsigned char*)header_->memory_+head, sizeof(size_t));
    head = (head + sizeof(size_t)) % header_->buffer_size;

    if(data_len > maxlen) return -1;
    if(head + data_len <= header_->buffer_size){
        memcpy(out, (unsigned char*)header_->memory_+head, data_len);
    }
    else{
        size_t first = header_->buffer_size - head;
        memcpy(out, (unsigned char*)header_->memory_+head,first);
        memcpy((unsigned char*)out+first, header_->memory_, data_len - first);
    }
    header_->head_.store((head + data_len)%header_->buffer_size, std::memory_order_release);
    return data_len;
}

查看系统支持的消息队列最大长度,以及每条消息的长度

shell 复制代码
uos@uos-PC:~$ cat /proc/sys/fs/mqueue/msg_max
10    
uos@uos-PC:~$ cat /proc/sys/fs/mqueue/msgsize_max
8192

查看共享内存内容

shell 复制代码
hexdump -C /dev/shm/my_shm | less
00000000  4e 00 00 00 00 00 00 00  4e 00 00 00 00 00 00 00  |N.......N.......|
00000010  00 00 a0 00 00 00 00 00  12 00 00 00 00 00 00 00  |................|
00000020  74 68 69 73 20 69 73 20  61 20 70 69 63 74 75 72  |this is a pictur|
00000030  65 2e 12 00 00 00 00 00  00 00 74 68 69 73 20 69  |e.........this i|
00000040  73 20 61 20 70 69 63 74  75 72 65 2e 12 00 00 00  |s a picture.....|
00000050  00 00 00 00 74 68 69 73  20 69 73 20 61 20 70 69  |....this is a pi|
00000060  63 74 75 72 65 2e 00 00  00 00 00 00 00 00 00 00  |cture...........|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

对应的数据结构

c 复制代码
    struct shared_header{
        std::atomic<size_t> head_;
        std::atomic<size_t> tail_;
        size_t buffer_size;
        unsigned char memory_[1];
    };

可以看到head_为4e 00 00 00 00 00 00 00,tail_为4e 00 00 00 00 00 00 00,buffer_size为00 00 a0 00 00 00 00 00,每次存数据格式为长度+值,12 00 00 00 00 00 00 00为长度,74 68 69 73 20 69 73 20 61 20 70 69 63 74 75 72 65 2e 为值。tail和head相等表示数据已读完。

相关推荐
chen36736 小时前
嵌入式AI Arm_linux_第一个Demo_让IPU跑起来
linux·arm开发·人工智能
Larry_Yanan6 小时前
QML学习笔记(四十七)QML与C++交互:上下文对象
c++·笔记·qt·学习·ui
2501_938791226 小时前
服务器恶意进程排查:从 top 命令定位到病毒文件删除的实战步骤
java·linux·服务器
LCG元6 小时前
SSH密钥对认证配置详解:告别密码登录,实现Linux服务器安全免密远程连接
linux
黑菜钟7 小时前
代码随想录第53天 | 图论二三题
c++·图论
西哥写代码7 小时前
基于dcmtk的dicom工具 第十二章 响应鼠标消息实现图像的调窗、缩放、移动
c++·mfc·dicom·dcmtk·vs2017
Jack电子实验室7 小时前
Linux系统调用lseek详解:文件指针的灵活控制
linux·运维·服务器
TracelessLe7 小时前
/usr/bin/ld: cannot find -lcuda报错分析
linux·运维·服务器
程序员buddha7 小时前
curl开发常用方法总结
linux