systemV共享内存
一、什么是共享内存?
共享内存是 Linux 里效率最高的进程间通信(IPC)方式,你可以把它理解成:
多个进程之间的「公共黑板」,大家都能直接在这块黑板上读写数据,不用像管道那样绕内核的弯儿,所以速度是所有 IPC 里最快的。
它的核心逻辑,就是让多个进程,把同一块物理内存区域 ,映射到各自的虚拟地址空间里。进程读写共享内存,就和读写自己的普通变量一样,数据不需要经过内核中转。
二、它的工作原理
1. 先在内核里划分一块物理内存
内核先在物理内存中,分配一块连续 / 不连续的区域 ,作为「公共黑板」,这一步由系统调用 **shmget()**完成。

2. 把物理内存映射到进程的虚拟地址空间
上图进程 A 和进程 B,都通过 shmat() 系统调用,把这块物理内存,映射到自己的虚拟地址空间里。
- 对进程 A 来说,它看到的是自己地址空间里的一块普通内存
- 对进程 B 来说,它看到的也是自己地址空间里的一块普通内存
- 但这两块虚拟内存,最终指向的是同一块物理内存
3. 数据「零拷贝」,直接读写
进程 A 写数据时,直接往共享内存里写;进程 B 读数据时,直接从共享内存里读。
- 对比你之前用的命名管道:数据要走两次拷贝(用户空间→内核缓冲区→另一个进程的用户空间),相当于「写信→交给邮局→邮局转交给对方」
- 共享内存:数据不用经过内核中转,直接在物理内存里读写,相当于「大家共用一块黑板,写了别人直接就能看到」,所以效率是最高的。
4. 生命周期跟着内核走
共享内存的生命周期和内核绑定,就算进程 A 和 B 都退出了,这块共享内存还会一直存在,直到你手动用 shmctl() 删除它,或者系统重启。
三、它的缺点 & 注意事项
共享内存虽然快,但有个致命问题:多个进程可以同时读写这块内存,会出现数据竞争。就像两个人同时在黑板上写字,内容会互相覆盖,数据直接乱掉。
所以,共享内存必须配合同步机制使用,比如:
- 信号量(Semaphore)
- 互斥锁(Mutex)来保证同一时间,只有一个进程在读写共享内存,避免数据混乱。
四、和「命名管道」对比
| 特性 | 命名管道(FIFO) | 共享内存 |
|---|---|---|
| 速度 | 慢(两次数据拷贝,需要内核中转) | 最快(零拷贝,直接读写) |
| 通信方向 | 半双工,单向传递 | 双向,直接读写 |
| 同步机制 | 自带阻塞 / 非阻塞机制,不用手动处理 | 必须手动加同步(信号量 / 互斥锁) |
| 生命周期 | 进程退出后自动消失 | 内核管理,需手动删除 |
五、核心接口介绍
Linux 提供两套共享内存 API:
- System V 共享内存:传统、经典,兼容性好
- POSIX 共享内存 :较新,更简洁,常与
mmap配合使用
下面详细介绍 System V 版的核心接口:
1. 创建 / 获取共享内存:shmget()
头文件 :#include <sys/shm.h>
cpp
int shmget(key_t key, size_t size, int shmflg);
| 参数 | 说明 |
|---|---|
key |
IPC 键值,用来唯一标识共享内存。可以用 IPC_PRIVATE(私有,父子进程用),或 ftok() 生成公共键值 |
size |
共享内存大小(字节),内核会自动向上对齐到页大小(通常 4KB) |
shmflg |
权限标志,和文件权限类似,常用 IPC_CREAT(不存在则创建) + 0666(读写权限) |
返回值 :成功返回共享内存标识符 shmid,失败返回 -1。
2. 生成唯一键值:ftok()
头文件 :#include <sys/ipc.h>
cpp
key_t ftok(const char *pathname, int proj_id);
- 作用:把一个文件路径和一个项目 ID,转换成唯一的
key_t值,让不同进程获取同一个共享内存 - 注意:
pathname必须是一个存在的文件路径,proj_id取 1-255 之间的整数即可
3. 映射共享内存到进程地址空间:shmat()
头文件 :#include <sys/shm.h>
cpp
void *shmat(int shmid, const void *shmaddr, int shmflg);
| 参数 | 说明 |
|---|---|
shmid |
shmget() 返回的共享内存标识符 |
shmaddr |
指定映射的虚拟地址,填 NULL 让内核自动分配(推荐) |
shmflg |
映射标志,0 表示读写,SHM_RDONLY 表示只读 |
返回值 :成功返回映射后的虚拟地址指针,失败返回 (void*)-1。
4. 解除进程与共享内存的映射:shmdt()
头文件:#include <sys/shm.h>
cpp
int shmdt(const void *shmaddr);
- 作用:解除当前进程和共享内存的映射,进程退出时也会自动解除
- 参数:
shmat()返回的虚拟地址指针 - 返回值:成功返回
0,失败返回-1
5. 控制 / 删除共享内存:shmctl()
头文件 :#include <sys/shm.h>
cpp
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
| 参数 | 说明 |
|---|---|
shmid |
共享内存标识符 |
cmd |
控制命令,常用:IPC_RMID(删除共享内存)、IPC_STAT(获取状态) |
buf |
状态结构体指针,填 NULL 即可 |
返回值 :成功返回 0,失败返回 -1。
命令行操作:
ipcs -m:查看共享内存
ipcrm -m ID:删除共享内存
指令本质是运行在用户空间的。
删除,控制共享内存在用户层,我们不能使用key!key未来只给内核进行区分唯一性!要用shmid进行管理内存。
补充:必须配合的同步机制(信号量)
共享内存没有自带同步,多个进程同时读写会出现数据竞争,所以必须搭配 System V 信号量使用,核心接口:
semget():创建 / 获取信号量semop():P/V 操作(加锁 / 解锁)semctl():删除信号量
C++封装共享内存接口
1. 公共头文件 comm.hpp
cpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <cstring>
using namespace std;
// 生成key用的路径和项目ID
#define PATHNAME "./comm.hpp"
#define PROJ_ID 0x6666
// 共享内存大小
#define SHM_SIZE 4096
// 信号量操作函数
union semun {
int val;
};
// P操作(加锁)
void semP(int semid)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
// V操作(解锁)
void semV(int semid)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
// 创建/获取信号量
int createSem()
{
key_t key = ftok(PATHNAME, PROJ_ID);
int semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1)
{
cerr << "semget error" << endl;
exit(1);
}
// 初始化信号量为1
union semun su;
su.val = 1;
semctl(semid, 0, SETVAL, su);
return semid;
}
// 创建/获取共享内存
int createShm()
{
key_t key = ftok(PATHNAME, PROJ_ID);
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1)
{
cerr << "shmget error" << endl;
exit(1);
}
return shmid;
}
#endif
2. 服务端 server.cc(读取共享内存)
cpp
#include "comm.hpp"
int main()
{
// 1. 创建共享内存和信号量
int shmid = createShm();
int semid = createSem();
// 2. 映射共享内存到当前进程
char* shmaddr = (char*)shmat(shmid, NULL, 0);
if (shmaddr == (char*)-1)
{
cerr << "shmat error" << endl;
exit(1);
}
// 3. 循环读取共享内存
while (true)
{
semP(semid); // 加锁
if (strlen(shmaddr) > 0)
{
cout << "Server recv: " << shmaddr << endl;
memset(shmaddr, 0, SHM_SIZE); // 读完清空
}
semV(semid); // 解锁
sleep(1);
}
// 4. 解除映射(实际不会走到这里)
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
return 0;
}
3. 客户端 client.cc(写入共享内存)
cpp
#include "comm.hpp"
int main()
{
// 1. 获取共享内存和信号量
int shmid = createShm();
int semid = createSem();
// 2. 映射共享内存到当前进程
char* shmaddr = (char*)shmat(shmid, NULL, 0);
if (shmaddr == (char*)-1)
{
cerr << "shmat error" << endl;
exit(1);
}
// 3. 写入数据到共享内存
int cnt = 0;
while (true)
{
semP(semid); // 加锁
snprintf(shmaddr, SHM_SIZE, "Hello Shm! cnt: %d", cnt++);
cout << "Client send: " << shmaddr << endl;
semV(semid); // 解锁
sleep(1);
}
// 4. 解除映射(实际不会走到这里)
shmdt(shmaddr);
return 0;
}
结果:

总结
读写共享内存没有调用系统调用!共享内存存在于堆栈之间,属于用户空间,用户可直接使用。
共享内存是进程间通信中,速度最快的!
1.映射之后,读写直接被对方看到!
2.不需要进行系统调用获取或者写入内容,直接以指针,地址的方法进行访问。通信双方没有所谓的"同步进制"(数据不一致)。共享内存没有保护进制。
共享内存创建的时候,它的大小必须是4KB的整数倍。