共享内存

共享内存

  • 共享内存是 Linux / Unix 系统提供的一种 进程间通信(IPC, Inter-Process Communication)机制
    • 允许 两个或多个进程访问同一块物理内存
    • 数据可以直接从内存中读写,无需复制,所以速度极快。
    • 必须自己管理同步,否则会出现数据冲突或破坏。

Producer / Consumer 模式

  • Producer: 负责生产数据(Camera / RGA 输出)
  • Consumer: 负责消费数据(AI / 推理 / 模型)
  • 核心原则:
    1. Producer 不阻塞
    2. Consumer 可以慢
    3. buffer 生命周期清晰
特性 说明
零拷贝 Producer 写入内存,Consumer 直接读取,不经过 socket / 文件拷贝
低延迟 CPU 不用做序列化 / 反序列化操作
跨进程 只要进程映射了同一块共享内存,任何进程都能访问
必须同步 没有内置锁,需要 mutex / semaphore / condition variable 来保证安全

Linux 下实现方式

  • POSIX 共享内存
    • shm_open 创建/打开
    • ftruncate 设置大小
    • mmap 映射到用户空间
  • System V 共享内存(老式,不常用)
  • DMA-BUF / GPU buffer(现代图像应用)
    • 可以直接传递硬件 buffer fd 给其他进程

内存布局概念

共享内存不仅仅是一个 裸数据块,通常还包含:

  1. 数据区
    • 图像 buffer、frame_id、timestamp
  2. 状态标志
    • Producer / Consumer 状态,是否可读/可写
  3. 同步机制
    • Mutex + Condition Variable

这三部分一起组成了共享内存结构体

Producer / Consumer 架构的概念

Producer / Consumer 是一种经典的并发模式

  • Producer(生产者)

    只负责生成数据。

  • Consumer(消费者)
    只负责使用数据。

  • 核心思想:职责分离,数据通过共享缓冲区传递。

Producer 写共享内存

Consumer 读共享内存

同步逻辑保证:

  1. Producer 不覆盖未消费的数据
  2. Consumer 不读无效数据
  3. CPU 不空转,条件变量阻塞

核心同步逻辑

1
2
3
4
5
6
7
8
9
Producer                  Consumer
| |
lock mutex lock mutex
check data_ready ----------> wait (if false)
write data |
data_ready = true read data
signal cond <-------------- signal cond
unlock mutex unlock mutex

头文件

头文件 用途
sys/mman.h mmap/munmap 映射共享内存
sys/stat.h 文件权限模式常量(0666)
fcntl.h shm_open, O_CREAT / O_RDWR
unistd.h ftruncate, close
pthread.h mutex / cond 等同步机制
cstring 字符串操作,例如 strncpy

共享内存结构体

1
2
3
4
5
6
7
8
struct SharedBlock {
pthread_mutex_t mutex; //互斥锁,保证读写互斥
pthread_cond_t cond; //条件变量,用于阻塞等待
bool data_ready; //状态标志,true 表示 Producer 已写数据
uint32_t frame_id;
uint64_t timestamp;
char payload[256]; //数据缓冲区(可替换为 DMA-BUF 或 RGA buffer)
};

函数详解

1
2
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(fd, SHM_SIZE);
1
shm_open
  • 打开或创建一个共享内存对象
  • 参数:
    • SHM_NAME:共享内存名字
    • O_CREAT | O_RDWR:创建 + 可读写
    • 0666:权限
1
ftruncate(fd, SHM_SIZE)
  • 调整共享内存大小(必须指定 size = sizeof(SharedBlock))
1
2
3
auto* shm = (SharedBlock*)mmap(nullptr, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);

mmap 映射共享内存到进程地址空间

参数:

  • PROT_READ | PROT_WRITE → 可读写
  • MAP_SHARED → 对其他进程可见
  • fd → shm_open 的文件描述符

返回值:指向映射的内存,可以像普通指针一样操作

初始化互斥锁和条件变量

1
2
3
4
5
6
7
8
9
pthread_mutexattr_t mattr;
pthread_condattr_t cattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); //允许跨进程使用 mutex
pthread_mutex_init(&shm->mutex, &mattr);

pthread_condattr_init(&cattr);
pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); //允许跨进程使用条件变量
pthread_cond_init(&shm->cond, &cattr);

必须由一个进程初始化,其他进程只映射即可

生产数据逻辑

1
2
3
pthread_mutex_lock(&shm->mutex);  // 上锁
while (shm->data_ready) // 检查状态
pthread_cond_wait(&shm->cond, &shm->mutex); // 阻塞等待
  • 上锁:保证写入互斥

  • while 检查状态:

    • 如果上一次数据还没被 Consumer 消费,Producer 阻塞
  • pthread_cond_wait

    1. 自动释放 mutex,阻塞等待

    2. 被 Consumer signal 唤醒后重新获取 mutex

写入数据:

1
2
3
4
shm->frame_id++;
shm->timestamp = now;
strncpy(shm->payload, "frame_x", sizeof(shm->payload));
shm->data_ready = true; // 标记数据已准备好
  • 先写数据,再标记 data_ready = true
  • 顺序关键:否则 Consumer 可能读到半写入的数据

通知 Consumer 并解锁:

1
2
pthread_cond_signal(&shm->cond);  // 唤醒 Consumer
pthread_mutex_unlock(&shm->mutex); // 解锁
  • signal → 唤醒 Consumer
  • unlock → 允许 Consumer 获取 mutex

消费者逻辑

打开共享内存:

1
2
3
4
int fd = shm_open(SHM_NAME, O_RDWR, 0666);
auto* shm = (SharedBlock*)mmap(nullptr, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
  • 不需要 O_CREAT
  • Consumer 不初始化 mutex / cond,直接使用映射的

读取数据逻辑:

1
2
3
pthread_mutex_lock(&shm->mutex);          // 上锁
while (!shm->data_ready) // 等待数据准备
pthread_cond_wait(&shm->cond, &shm->mutex); // 阻塞
  • 如果没有数据,阻塞等待
  • 条件变量避免 busy loop → CPU 零占用

读取并消费:

1
2
3
4
std::cout << shm->frame_id << " " << shm->payload << "\n";
shm->data_ready = false; // 标记数据已被消费
pthread_cond_signal(&shm->cond); // 唤醒 Producer
pthread_mutex_unlock(&shm->mutex); // 解锁
  • 先读,再清 data_ready
  • signal → 告诉 Producer 可以写下一帧
  • unlock → 释放共享内存访问权

智能指针管理逻辑

共享内存里的指针操作用 智能指针 封装,方便管理,避免手动 mmap/munmapshm_open/close 出错,同时保持线程安全。

智能指针管理共享内存的主要目标:

  1. 自动释放资源

    • mmapmunmap

    • shm_openclose

    • 可用 std::unique_ptr + 自定义删除器封装

  2. 依旧保持 Producer/Consumer 同步逻辑

    • mutexcond 仍然是原生 pthread,智能指针不干预
  3. 安全、异常友好

    • 如果程序异常退出,智能指针自动释放映射

智能指针

智能指针是一种 C++ 提供的 RAII 资源管理机制,它能自动管理动态分配的资源,防止内存泄漏和资源未释放。

std::unique_ptr<T> 独占所有权 std::unique_ptr<int> p(new int(5)); 生命周期结束自动释放;不能拷贝,可移动
std::shared_ptr<T> 共享所有权 auto sp = std::make_shared<int>(5); 多个指针共享同一资源,最后一个析构时释放
std::weak_ptr<T> 弱引用 std::weak_ptr<int> wp = sp; 不增加引用计数,用于避免循环引用

为什么共享内存适合用智能指针

共享内存操作流程:

  1. shm_open → 获取 fd
  2. ftruncate → 设置大小
  3. mmap → 映射
  4. munmap → 释放映射
  5. close → 关闭 fd(可选)

问题:

  • 如果手动操作,一旦异常退出,munmap 可能没调用 → 内存泄漏
  • fd 没关闭 → 资源泄漏

解决方案:

  • unique_ptr + 自定义删除器封装 mmap 的地址
  • 这样映射就能和智能指针绑定,出作用域自动释放

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 自定义删除器
struct ShmDeleter {
size_t size;
ShmDeleter(size_t s) : size(s) {}
void operator()(SharedBlock* ptr) {
if (ptr) munmap(ptr, size); // 自动释放映射
}
};

// 创建共享内存智能指针
std::unique_ptr<SharedBlock, ShmDeleter> shm_ptr(
(SharedBlock*)mmap(nullptr, sizeof(SharedBlock),
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0),
ShmDeleter(sizeof(SharedBlock))
);