Linux System V 共享内存:原理、实操与避坑指南
System V 共享内存是 Linux 进程间通信的高效机制,允许多个进程直接访问同一物理内存区域。本文详解其底层原理,包括内核数据结构 struct shmid_ds 及 ftok、shmget、shmat、shmdt、shmctl 五大系统调用流程。通过 C++ 封装类示例展示创建、挂载、读写及删除操作,并指出内存泄漏、并发同步、Key 值匹配等常见陷阱。适用于高性能数据传输场景,需注意手动管理生命周期与同步机制。

System V 共享内存是 Linux 进程间通信的高效机制,允许多个进程直接访问同一物理内存区域。本文详解其底层原理,包括内核数据结构 struct shmid_ds 及 ftok、shmget、shmat、shmdt、shmctl 五大系统调用流程。通过 C++ 封装类示例展示创建、挂载、读写及删除操作,并指出内存泄漏、并发同步、Key 值匹配等常见陷阱。适用于高性能数据传输场景,需注意手动管理生命周期与同步机制。

在 Linux 进程间通信(IPC)中,共享内存是效率最高的方式之一。它直接让多个进程共享同一块物理内存区域,无需像管道、消息队列那样进行数据拷贝,省去了内核与用户空间之间的频繁数据交换开销。System V 共享内存(简称 SysV 共享内存)作为 Linux 早期就支持的经典 IPC 机制,至今仍在很多底层开发、高性能程序中广泛应用。
本文从是什么、怎么工作、怎么用、踩过哪些坑四个维度,详解 System V 共享内存,全程附代码示例。
System V 共享内存是 System V 系列 IPC 机制中的一种,核心作用是'让多个进程访问同一块物理内存',实现高效的数据共享。

内核通过 struct shmid_ds 管理共享内存的属性,核心字段如下:
struct shmid_ds {
struct ipc_perm shm_perm; // 权限控制结构体
size_t shm_segsz; // 共享内存大小(字节)
pid_t shm_cpid; // 创建进程 PID
pid_t shm_lpid; // 最后一次操作该内存的进程 PID
unsigned short shm_nattch;// 当前挂载到该内存的进程数
time_t shm_atime; // 最后一次挂载时间
time_t shm_dtime; // 最后一次脱离时间
time_t shm_ctime; // 最后一次属性修改时间
void *shm_unused2; // 预留字段
};
struct ipc_perm是 System V IPC 的通用权限结构体,内核通过该结构体的 key 字段唯一标识一个 IPC 资源。
System V 共享内存的使用流程遵循:生成 Key → 创建/获取共享内存 → 挂载 → 读写 → 脱离 → 删除。
用于将文件路径 + 项目 ID 转换为唯一的 key_t 类型值。
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
返回共享内存标识符(shmid)。
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
IPC_CREAT | IPC_EXCL | 0666。将共享内存映射到当前进程的虚拟地址空间。
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
SHM_RDONLY 为只读挂载。解除映射关系,并非删除共享内存。
#include <sys/shm.h>
int shmdt(const void *shmaddr);
用于获取属性、修改属性或删除共享内存。
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
IPC_RMID 标记删除,IPC_STAT 获取属性。提供 Shm.hpp 封装类对上述核心 API 进行完整封装。
| 函数名 | 调用示例 | 功能描述 |
|---|---|---|
| Create() | `shmget(key, size, IPC_CREAT | IPC_EXCL)` |
| Get() | shmget(key, size, IPC_CREAT) | 获取已存在的共享内存 |
| Attch() | shmat(shmid, NULL, 0) | 挂载共享内存 |
| Delete() | shmctl(shmid, IPC_RMID, NULL) | 删除共享内存 |
| GetShmAttr() | shmctl(shmid, IPC_STAT, &ds) | 获取共享内存属性 |
#ifndef __SHM_HPP__
#define __SHM_HPP__
#include <iostream>
#include <cstdio>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
const std::string proj_name = "/home";
const int proj_id = 0x6666;
const int g_size = 4096;
class Shm {
public:
Shm(int size = g_size): _shmid(-1), _size(size), _key(0) {}
~Shm() {}
private:
key_t GetKey() {
_key = ftok(proj_name.c_str(), proj_id);
if(_key < 0) { perror("ftok"); }
return _key;
}
bool CreateCoreHelper(int flags) {
key_t key = GetKey();
_shmid = shmget(key, _size, flags);
if(_shmid < 0) { perror("shmget"); return false; }
return true;
}
public:
bool Create() { return CreateCoreHelper(IPC_CREAT | IPC_EXCL | 0666); }
bool Get() { return CreateCoreHelper(IPC_CREAT); }
bool Delete() { int n = shmctl(_shmid, IPC_RMID, nullptr); return n < 0 ? false : true; }
void GetShmAttr() {
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds);
if(n < 0) { perror("shmctl"); return; }
std::cout << ds.shm_cpid << std::endl;
std::cout << ds.shm_segsz << std::endl;
}
void *Attch() { _start = (char *)shmat(_shmid, nullptr, 0); return _start; }
void Detach() { int n = shmdt(_start); (void)n; }
void Debug() {
std::cout << "shmid: " << _shmid << std::endl;
std::cout << "size: " << _size << std::endl;
}
private:
int _shmid;
int _size;
key_t _key;
char *_start;
};
#endif
#include "Shm.hpp"
#include <iostream>
int main() {
Shm shm;
shm.Create(); // 创建共享内存
shm.Attach(); // 挂载
shm.Debug();
shm.Detach();
shm.Delete(); // 删除共享内存
return 0;
}
#include "Shm.hpp"
#include <iostream>
#include <unistd.h>
int main() {
Shm shm;
sleep(3); // 等待 Writer 创建
shm.Get(); // 获取已存在的共享内存
shm.Attach(); // 挂载
shm.Debug();
shm.GetShmAttr();
shm.Detach();
return 0;
}
all: Writer Reader
Reader:Reader.cc
g++ -o $@ $^ -std=c++11
Writer:Writer.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f Writer Reader
./Reader(Wait for creation logic adjusted in code above to match flow, or run Writer first depending on logic. Based on corrected code: Writer creates, so run Writer first.
Correction: In the corrected code above, Writer creates, Reader gets. So run Writer first.
Revised Step: 1. 运行 ./Writer 创建内存。2. 运行 ./Reader 获取并读取。注意:共享内存大小建议为 4096 的整数倍。若设置为 4097,操作系统可能会申请 4096*2,造成浪费且可能导致越界访问无提示。
内核通过 struct ipc_ids 和 struct shmid_kernel 管理所有共享内存资源。
shm_ids 全局变量,记录元数据。entries 数组存储权限结构体指针。shmctl(IPC_RMID),内存长期占用。ipcs -m 查看,ipcrm -m <shmid> 删除。System V 共享内存的核心优势是'高效',劣势是'需手动管理同步和生命周期'。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online