1.共享内存的通信本质

此图说明两个进程通过共享内存通信是通过将物理内存的一块内存分别映射到两个毫不相干的进程的地址空间上,这样两个进程就看到了同一份资源;
2.共享内存和操作系统的关系
共享内存(Shared Memory)是操作系统(OS)内核提供的核心进程间通信(IPC)机制,也是 OS 内存管理体系的重要组成部分。它的设计、实现和使用全程依赖 OS 的底层支撑,二者的核心关系可从以下维度清晰理解:
①OS 是共享内存的「创建者与管理者」 内存空间的分配与回收共享内存并非进程私有内存,而是 OS 从系统物理内存中划出的一块独立区域(不属于任何单个进程的地址空间)。进程通过 OS 提供的系统调用(如 Linux 下的shmget()、mmap())向内核申请创建 / 获取共享内存段,OS 会为其分配唯一的标识符(shmid)、设置大小、权限(读写 / 只读),并维护共享内存的元数据(如所有者、关联进程数、创建时间)。
当无进程使用时,OS 会根据配置(如shmctl()的 IPC_RMID)回收这块内存,避免内存泄漏。 地址映射的核心桥梁每个进程有独立的虚拟地址空间,OS 通过「内存映射」机制,将同一块物理共享内存映射到多个进程的虚拟地址空间中。进程对自身虚拟地址的读写,会被 OS 内核转发到物理共享内存,实现数据互通 ------ 这个映射过程(shmat())完全由 OS 的内存管理单元(MMU)和页表机制支撑。
权限与冲突管控OS 负责校验进程对共享内存的访问权限(如是否有读 / 写权限),同时提供基础的同步 / 互斥接口(如信号量),辅助进程解决共享内存的并发读写冲突(OS 不主动管控冲突,仅提供工具,需程序员自行实现)。共享内存是 OS 实现高效 IPC 的「核心方案」 OS IPC 机制的性能天花板对比管道、消息队列等 IPC 方式,共享内存是效率最高的 ------ 因为数据无需在进程和 OS 内核间拷贝(管道 / 消息队列需 2 次拷贝:进程→内核→另一个进程),仅需 OS 完成一次地址映射,进程可直接读写物理内存,这是 OS 为高性能 IPC 专门设计的机制。 OS 对共享内存的资源管控OS 会将共享内存纳入系统资源统一管理: 可通过ipcs -m(Linux)查看 OS 中所有共享内存段的状态(大小、关联进程数、权限); 可通过ipcrm -m shmid(Linux)强制删除 OS 中闲置的共享内存段; OS 会限制共享内存的最大段数、单段最大大小(如/proc/sys/kernel/shmmax),避免滥用系统内存
3.共享内存的接口解释
①创建共享内存函数------shmget

size:表示我们要创建的共享内存的大小
shmflg:是标记位,有两个,分别是IPC_CREAT和IPC_EXCL;即只有一个标记位为1的宏;

观察这两个标记位的定义,我们发现其实IPC_CREAT|IPC_EXCL是创建共享内存的进程使用的,而IPC_CREAT是获取共享内存的进程使用的,很简单,你见谁家好人想获取内存了人家有了反而还要返回的呢?存不存在看的是key
key:是操作系统用来管理共享内存的唯一标识符或者键值,但是这个值不像fd,它不是OS生成的,而是程序员自己传进去的,之所以要有这个值是因为共享内存在内存中不止一份,所以需要我们的OS管理,管理的话就需要有唯一的值来分辨这些共享内存,呢就需要我们传入的key了,emmmmmm,有点不严谨,但的确如此,为了有点严谨性,OS设置了一个系统调用函数,但是这个函数并不会陷入内核,也就是我们下面提到的ftok函数,一起来看看他的作用吧;
③防冲突函数 ------ftok

OS实现了上面的函数,减少了key值冲突的问题,即将你项目的路径和ID传给此函数,此函数通过算法减小了冲突的概率;为什么使用key呢?因为只有程序员在编码层面设定key才能被两个进程都看到,如果让一个进程设置,因为进程具有独立性,另一个进程怎么能看到呢?如果key值和别人的冲突了,就会返回;
共享内存间的两个进程也是没有任何关系的,所以我们的实现基于前面的命名管道;

③共享内存的释放

删改查的三个选项;

④共享内存的挂接

这个函数的返回值是共享内存在虚拟地址空间块的的起始地址,和我们之前使用的malloc申请的堆空间很相似;挂接的时候要设置好我们的权限,要不然观察不到;
第二个参数 shmaddr 的作用是指定共享内存映射到进程虚拟地址空间的起始地址,一般我们写的时候写成nullptr就行,写库这样的场景才能确定具体是什么地址;
返回-1表示映射失败;
观察挂接和权限的命令;
bash
while :; do ipcs -m; sleep 2; done
⑤去除挂接

4.实现
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
const int gsize=4096;
const std::string gpath="./root/sharemem";
const int gporjId = 07777;
mode_t gmode = 0600;
class Sharemem
{
private:
void CreatShmHelper()
{
_key = ::ftok(gpath.c_str(),gporjId);
if(_key < 0)
{
std::cerr<< "ftok failed" <<std::endl;
return;
}
_shmid=::shmget(_key,gsize,IPC_CREAT|IPC_EXCL|gmode);
if(_shmid <0)
{
std::cerr <<"shmget failed" <<std::endl;
return;
}
std::cout << "shmid:" <<_shmid <<std::endl;
}
public:
Sharemem()
:_key(0)
,_shmid(-1)
,_addr(nullptr)
{
}
void CreatShm()
{
if(_shmid == -1)
CreatShmHelper();
}
//建立挂接
void AttchShm()
{
_addr = shmat(_shmid,nullptr,0);
if((int)_addr == -1)
{
std::cerr << "attch failed"<<std::endl;
return ;
}
}
//去除挂接
void DetchShm()
{
if((int)_addr != 0)
::shmat(_shmid,nullptr,0);
std::cout <<"decth done ...."<< std::endl;
}
//释放共享内存
void DeleteShm()
{
::shmctl(_shmid,IPC_RMID,nullptr);
}
void * GetAddr()
{
return _addr;
}
~Sharemem()
{
}
private:
key_t _key;
int _shmid;
void * _addr;
};
Sharemem Shm;