System V 共享内存是 Linux 进程间通信(IPC)中效率最高的方式之一。它允许不同进程直接访问同一块物理内存区域,省去了内核与用户空间之间的数据拷贝开销。作为经典的 IPC 机制,它在底层开发和高性能场景中依然广泛应用。
一、什么是 System V 共享内存?
System V 共享内存属于 System V 系列 IPC 机制的一部分,核心作用是让多个进程访问同一块物理内存。可以把它想象成一个公共仓库,系统划分出一块专属区域,给多个进程发放'访问权限',它们可以直接读写这块区域,无需通过内核缓冲区中转。
1.1 关键特点
- 高效性:无内核与用户空间的数据拷贝,直接操作物理内存,是所有 IPC 机制中速度最快的。
- 持续性:共享内存一旦创建,会一直存在于内核中,直到被显式删除或系统重启,不受进程生命周期影响。
- 非实时性:没有自带的同步机制,多个进程同时读写时,需要手动搭配信号量等同步手段,否则会出现数据混乱。
- 标识符(key):每个 System V 共享内存都有一个唯一的 key 值,进程通过 key 找到对应的共享内存。
1.2 核心理解
创建和'删除'shm 需要系统调用,而使用 shm 不需要(类似 malloc)。用户空间最有代表的就是用户可以直接访问指针。
二、底层原理与核心 API
2.1 内核管理数据结构
内核通过 struct shmid_ds 管理共享内存的属性。结合 Linux 内核源码,核心字段如下:
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 资源。
2.2 核心系统调用
System V 共享内存的使用流程遵循'生成 Key→创建/获取→挂载→读写→脱离→删除'。
ftok:生成唯一 Key
用于将文件路径 + 项目 ID 转换为唯一的 key_t 类型值,作为共享内存的全局标识。
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- pathname:必须是系统中已存在的文件路径。
- proj_id:非 0 的 8 位整数,不同的 proj_id 会生成不同的 key。
- 返回值:成功返回唯一 key,失败返回 -1。
shmget:创建 / 获取共享内存
返回共享内存标识符(shmid),后续操作均通过 shmid 关联。
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- key:ftok 生成的唯一 Key。
- size:共享内存大小(建议为 4096 的整数倍)。
- shmflg:权限标志组合,如
IPC_CREAT(不存在则创建)、IPC_EXCL(确保全新创建)及权限位(如0666)。
shmat:挂载共享内存
将共享内存映射到当前进程的虚拟地址空间,返回映射后的虚拟地址指针。
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- shmaddr:指定挂载的虚拟地址(NULL 表示由内核自动分配)。
- shmflg:挂载标志,如
SHM_RDONLY(只读)。
shmdt:脱离共享内存
将共享内存从当前进程的虚拟地址空间中解除映射关系,并非删除共享内存本身。
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmctl:控制共享内存
用于获取属性、修改属性或删除共享内存,是生命周期管理的核心 API。
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- cmd:控制命令,如
IPC_STAT(获取属性)、IPC_SET(修改属性)、IPC_RMID(标记删除)。
三、实战代码示例
以下提供 C++ 封装类及读写进程示例,展示如何完整操作 System V 共享内存。
3.1 封装类核心逻辑(Shm.hpp)
该类封装了'生成 Key→创建/获取→挂载→删除→属性查询'的全流程。
#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;
static std::string ToHex(long long data) {
char buf[16];
snprintf(buf, sizeof(buf), "0x%llx", data);
return buf;
}
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;
std::cout << ToHex(_key) << 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;
std::cout << "key: " << ToHex(_key) << std::endl;
}
private:
int _shmid;
int _size;
key_t _key;
char *_start;
};
typedef struct data {
int count;
char buf[26 * 2];
} buffer_t;
#endif
3.2 Writer 进程(Writer.cc)
演示获取并调试共享内存的状态。
#include "Shm.hpp"
#include <iostream>
#include <string>
int main() {
Shm shm;
shm.Get();
shm.Debug();
return 0;
}
3.3 Reader 进程(Reader.cc)
演示创建、挂载、读取属性及清理共享内存的流程。
#include "Shm.hpp"
#include <iostream>
#include <string>
#include <unistd.h>
int main() {
Shm shm;
shm.Create();
sleep(3);
shm.Attch();
shm.Debug();
shm.GetShmAttr();
sleep(5);
shm.Delete();
return 0;
}
3.4 编译与运行
使用 Makefile 进行编译:
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 创建内存,再运行 ./Writer 获取内存。注意测试时若设置大小为 4097,操作系统会向上对齐到 4096*2,可能导致浪费或越界风险。
四、内核管理机制
内核通过 struct ipc_ids 和 struct shmid_kernel 管理所有共享内存资源。全局维护 shm_ids 变量记录元数据,通过索引机制关联权限结构体,最终通过文件系统的 inode 和 vm_area_struct 实现物理内存与进程虚拟地址的映射。
五、避坑指南
虽然高效,但使用时容易踩坑,新手需重点关注:
- 内存泄漏:共享内存生命周期独立于进程。若进程异常退出未执行
shmctl(IPC_RMID),内存会一直占用。建议在程序中添加信号处理捕获 SIGINT,或在退出前强制删除。可通过ipcs -m查看,ipcrm -m <shmid>删除。 - 数据混乱:没有自带同步机制。多个进程同时写入会导致覆盖。需搭配 System V 信号量或互斥锁实现互斥访问。
- Key 值不一致:不同进程使用
ftok()生成 key 时,路径或标识不一致会导致无法找到同一块内存。确保所有进程使用完全相同的 KEY_PATH 和 KEY_ID。 - 大小设置不合理:不足会被截断,过大浪费资源。建议设置为页大小的整数倍。
六、总结
System V 共享内存的核心优势是'高效',劣势是'需手动管理同步和生命周期'。它适合高性能要求的进程间通信、大量数据频繁交换的场景以及底层嵌入式开发。现代 Linux 开发中也可关注 POSIX 共享内存(如 shm_open()、mmap()),但 System V 的兼容性和底层可控性依然是其不可替代的优势。


