
💡Yupureki:个人主页
✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》《MySQL数据库》
🌸Yupureki🌸的简介:

目录
[1. System V 共享内存](#1. System V 共享内存)
[1.1 什么是共享内存](#1.1 什么是共享内存)
[1.2 共享内存的原理](#1.2 共享内存的原理)
[1.3 接口介绍](#1.3 接口介绍)
[1.3.1 创建或获取共享内存:shmget()](#1.3.1 创建或获取共享内存:shmget())
[1.3.2 附加到进程地址空间:shmat()](#1.3.2 附加到进程地址空间:shmat())
[1.3.3 分离:shmdt()](#1.3.3 分离:shmdt())
[1.3.4 控制:shmctl()](#1.3.4 控制:shmctl())
[1.4 C++封装共享内存接口](#1.4 C++封装共享内存接口)
[1.5 测试用例](#1.5 测试用例)
1. System V 共享内存
1.1 什么是共享内存
共享内存是Linux中效率最高 的进程间通信方式,它允许多个进程将同一块物理内存区域映射到各自的虚拟地址空间中,使得数据不需要经过内核拷贝,直接像访问普通内存一样读写,从而获得极快的传输速度。但正因如此,它需要配合同步机制(如信号量)来避免数据竞争。
1.2 共享内存的原理
共享内存的本质是内存映射。
-
物理内存:内核在物理内存中分配一块区域(可连续也可不连续)。
-
映射:通过页表将这块物理内存映射到多个进程的虚拟地址空间。每个进程访问自己虚拟地址空间中的共享区域,最终指向同一块物理内存。
-
零拷贝:数据从写进程的用户空间直接放入共享内存,读进程直接从共享内存读取,无需经过内核缓冲区。这比管道、消息队列等需要两次数据拷贝(用户→内核→用户)的方式高效得多。
-
持久性:共享内存的生命周期通常与内核对象绑定,即使所有进程解除映射,内存区域仍然存在(直到显式删除或系统重启)。
由于多个进程可以同时读写共享内存,必须使用信号量 、互斥锁 或原子操作来保证数据一致性。

1.3 接口介绍
Linux提供两套共享内存 API:
-
System V 共享内存(传统,经典)
-
POSIX 共享内存 (较新,更简洁,常与
mmap配合)
这里我们先介绍System V版
1.3.1 创建或获取共享内存:shmget()
cpp
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
-
key:IPC 键值,可以用IPC_PRIVATE(私有)或ftok()生成。 -
size:共享内存大小(字节),通常向上取整到页大小(4KB)。 -
shmflg:权限标志(如0666)与IPC_CREAT(若不存在则创建)。 -
返回值:共享内存标识符(
shmid),失败返回 -1。
生成唯一的key值:ftok()
cpp#include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
- pathname:路径名称(自定义,但该路径一定要存在)
- proj_id:id值(自定义,一般为十六进制数)
1.3.2 附加到进程地址空间:shmat()
cpp
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
shmid:shmget返回的标识符。 -
shmaddr:指定映射地址,通常设为NULL由系统选择。 -
shmflg:SHM_RDONLY(只读)或 0(读写)。 -
返回值:映射后的虚拟地址,失败返回
(void *)-1。
1.3.3 分离:shmdt()
cpp
int shmdt(const void *shmaddr);
- 从进程地址空间解除映射 ,并不删除共享内存本身。
1.3.4 控制:shmctl()
cpp
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
常用命令:
-
IPC_STAT:获取状态信息。 -
IPC_SET:设置权限等。 -
IPC_RMID:标记删除(当所有进程分离后真正销毁)。
1.4 C++封装共享内存接口
comm.hpp:
cpp
#pragma one
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define PATHNAME "." //默认路径
#define ID 0x6666 //默认id
//用户类别:
//creater创造共享内存并使用,一般是服务器
//user使用已存在的共享内存,一般是客户端
#define CREATER "creater"
#define USER "user"
#define MAXNUM 4096
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
class Shm
{
public:
Shm(std::string path = PATHNAME, int id = ID, std::string user = USER)
{
_key = ftok(path.c_str(), id);//ftok生成唯一的key
if (_key == -1)
{
ERR_EXIT("ftok");
exit(1);
}
_user = user;
if (_user == CREATER)//creater生成并使用共享内存
create(IPC_CREAT | IPC_EXCL | 0666);
else//用户只使用共享内存
create(IPC_CREAT | 0666);
}
bool attach()//将共享内存映射到虚拟地址空间中
{
mem = (char *)shmat(_shm_id, NULL, 0);
if (mem == (void *)-1)
{
ERR_EXIT("shmat");
exit(1);
}
std::cout << "attach success" << std::endl;
return true;
}
bool detach()//断连共享内存
{
if (shmdt(mem) == -1)
{
ERR_EXIT("shmdt");
exit(1);
}
if (_user == CREATER)
{
int n = shmctl(_shm_id, IPC_RMID, NULL);
if (n < 0)
{
ERR_EXIT("shmctl");
exit(1);
}
std::cout << "detach success" << std::endl;
return true;
}
return false;
}
int get_key()
{
return _key;
}
int get_shm_id()
{
return _shm_id;
}
~Shm()
{
detach();
}
char *get_mem()
{
return mem;
}
private:
void create(int flag)
{
_shm_id = shmget(_key, MAXNUM, flag);
if (_shm_id == -1)
{
ERR_EXIT("shmget");
exit(1);
}
}
key_t _key;
int _shm_id;
std::string _user;
char *mem;
};
1.5 测试用例
server.cpp:
cpp
#include "comm.hpp"
int main()
{
Shm shm(PATHNAME,ID,CREATER);
std::cout<<"shm id: "<<shm.get_shm_id()<<std::endl;
shm.attach();
char * mem = shm.get_mem();
while(true)
{
if(strcmp(mem,"exit") == 0)
break;
std::cout<<"client input: "<<mem<<std::endl;
sleep(1);
}
return 0;
}
client.cpp:
cpp
#include "comm.hpp"
int main()
{
Shm shm(PATHNAME,ID,USER);
std::cout<<"shm id: "<<shm.get_shm_id()<<std::endl;
shm.attach();
char *mem = shm.get_mem();
while(true)
{
fgets(mem,MAXNUM,stdin);
mem[strlen(mem) - 1] = '\0';
if(strcmp(mem,"exit") == 0)
break;
std::cout<<"client input: "<<mem<<std::endl;
}
return 0;
}
测试:
