共享内存的原理很简单,取物理内存的一块空间,将它通过页表映射到多个进程的共享区,这样多个进程就可以看到同一块物理内存了,这块内存就叫做共享内存。
基本操作:
shmget 函数
分配共享内存
NAME
shmget - allocates a System V shared memory segment
SYNOPSIS
#include
#include
int shmget(key_t key, size_t size, int shmflg);
// 返回值:成功:返回有效的共享内存标识符。错误:返回-1,并设置errno以指示错误。
key:共享内存的唯一值,自己通过函数ftok设置
NAME
ftok - convert a pathname and a project identifier to a System V IPC key
SYNOPSIS
#include
#include
key_t ftok(const char *pathname, int proj_id);
// 返回值:成功:返回生成的key_t值,失败:返回-1
size:指定大小(byte),建议设置为页(PAGE_SIZE = 4KB)的整数倍
shmflg:用于区分共享内存已经存在和不存在时的做法,其有两个选项
IPC_CREAT:共享内存如果已经存在,就获取之,如果不存在,就创建之
IPC_EXCL:与 IPC_CREAT 配合使用,如果不存在指定的共享内存,创建之,如果存在,出错返回。该选项可以保证如果shmget函数调用成功,则一定创建了一个全新的共享内存。
此参数还可以直接按位或一个八进制来指定权限
注意:system V 下的共享内存,生命周期是随内核的,创建的共享内存在进程退出后依然存在。
例子:
Comm.hpp
包含各种头文件,创建key的相关设置
#pragma once
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define PATH_NAME "/home/CegghnnoR/code"
#define PROJ_ID 0x18
#define MEM_SIZE 4096
key_t CreateKey()
{
key_t key = ftok(PATH_NAME, PROJ_ID);
if (key < 0)
{
cerr << strerror(errno) << endl;
exit(1);
}
return key;
}
Log.hpp
设置输出格式
#pragma once
#include
#include
using namespace std;
ostream& Log()
{
cout << "For Debug | " << "timestamp: " << (uint64_t)time(nullptr) << "| ";
return cout;
}
IpcShmSer
服务端代码,创建共享内存
#include "Comm.hpp"
#include "Log.hpp"
// 表示创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;
int main()
{
key_t key = CreateKey();
Log() << "key:" << key << endl;
int shmid = shmget(key, MEM_SIZE, flags);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
Log() << "create shm success, shmid: " << shmid << "\n";
return 0;
}
实验结果:
[CegghnnoR@VM-4-13-centos 2022_11_4]$ ./IpcShmSer
For Debug | timestamp: 1667631442| key:402719453
For Debug | timestamp: 1667631442| create shm success, shmid: 1
[CegghnnoR@VM-4-13-centos 2022_11_4]$ ./IpcShmSer
For Debug | timestamp: 1667631551| key:402719453
For Debug | timestamp: 1667631551| shmget: File exists
第一次运行可以成功创建共享内存,进程退出后再运行一次,发现创建失败了,说明第一次运行后,原来的共享内存依然存在。
使用 ipcs -m 命令可以查看当前已创建的共享内存
[CegghnnoR@VM-4-13-centos 2022_11_4]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00005feb 0 root 666 12000 1
0x180102dd 1 CegghnnoR 0 4096 0
其中 perms 为访问权限,nattch 表示与此共享内存关联的进程数。
指令删除
ipcrm -m [shmid]用于删除共享内存
[CegghnnoR@VM-4-13-centos 2022_11_4]$ ipcrm -m 1
[CegghnnoR@VM-4-13-centos 2022_11_4]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00005feb 0 root 666 12000 1
系统调用接口删除
NAME
shmctl - System V shared memory control
SYNOPSIS
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
仅仅作删除共享内存使用,指定相应的 shmid,cmd 指定为 IPC_RMID,buf 指定为 nullptr
NAME
shmat, shmdt - System V shared memory operations
SYNOPSIS
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 返回值:成功:返回附加的共享内存段的地址;失败:返回(void*)-1,并设置errno以指示错误的原因。
int shmdt(const void *shmaddr);
shmat
shmid 指定你要关联的共享内存,shmaddr 我们设为 nullptr,shmflg 设为 0
共享内存的使用,和直接用malloc出来的空间的使用相似(除了free)。
shmdt
shmaddr 就是 shmat 的返回值服务端:
// 关联共享内存
char* str = (char*)shmat(shmid, nullptr, 0);
Log() << "attach shm: " << shmid << " success\n";
// 使用...
// 去关联
shmdt(str);
Log() << "detach shm: " << shmid << " success\n";
// 删除共享内存
shmctl(shmid, IPC_RMID, nullptr);
Log() << "delete shm: " << shmid << " success\n";
客户端:
服务端创建的共享内存服务端删除,客户端不用删
// 关联
char* str = (char*)shmat(shmid, nullptr, 0);
// 使用...
// 去关联
shmdt(str);
共享内存的使用非常简单,shmat 返回的地址就是共享内存的地址,我们可以直接使用指针访问块空间,且由于连个进程共享这一块空间,所以其中一方写,另一方立刻就能看到。
完整代码:
Comm.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define PATH_NAME "/home/CegghnnoR/code"
#define PROJ_ID 0x18
#define MEM_SIZE 4096
key_t CreateKey()
{
key_t key = ftok(PATH_NAME, PROJ_ID);
if (key < 0)
{
cerr << strerror(errno) << endl;
exit(1);
}
return key;
}
Log.hpp
#pragma once
#include
#include
using namespace std;
ostream& Log()
{
cout << "For Debug | " << "timestamp: " << (uint64_t)time(nullptr) << "| ";
return cout;
}
IpcShmSer.cpp
循环读取共享内存中的字符串
#include "Comm.hpp"
#include "Log.hpp"
// 表示创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;
int main()
{
key_t key = CreateKey();
Log() << "key:" << key << endl;
int shmid = shmget(key, MEM_SIZE, flags | 0666);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
Log() << "create shm success, shmid: " << shmid << "\n";
// 关联共享内存
char* str = (char*)shmat(shmid, nullptr, 0);
Log() << "attach shm: " << shmid << " success\n";
// 使用...
while (true)
{
printf("%s\n", str);
sleep(1);
}
// 去关联
shmdt(str);
Log() << "detach shm: " << shmid << " success\n";
// 删除共享内存
shmctl(shmid, IPC_RMID, nullptr);
Log() << "delete shm: " << shmid << " success\n";
return 0;
}
IpcShmCli.cpp
向共享内存写入ABCD…
#include "Comm.hpp"
#include "Log.hpp"
int main()
{
// 获取相同的key值
key_t key = CreateKey();
Log() << "key:" << key << endl;
// 获取共享内存
int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
// 关联
char* str = (char*)shmat(shmid, nullptr, 0);
// 使用...
int cnt = 0;
while (cnt <= 26)
{
str[cnt] = 'A' + cnt;
++cnt;
str[cnt] = '\0';
sleep(1);
}
// 去关联
shmdt(str);
return 0;
}
运行结果:

从结果也很容易发现,共享内存没有任何访问控制,即读端会直接读取,不管共享内存中有没有数据,一切都是原生的指针访问。
共享内存,是所有进程间通信渠道中,速度最快的,但是不安全。
概念: