当前位置: 首页 > news >正文

Linux -- SysremV 共享内存通信

一、共享内存通信直接原理

1.1 进程间通信的本质是:先让不同的进程看到同一份资源;
1.2 共享内存通信图解:

①:第一步进程A向物理内存中申请创建一块资源空间;

②:第二步申请成功后把这块内存对应的虚拟地址挂载到进程A的地址空间中;

③:第三步进程B向物理内存中获取进程A已创建好的资源,

④:进程B获取成功后,把对应的虚拟地址挂载到进程B的地址空间中;

以上步骤实现完就已经建立了通信的前提:两个不同的进程看到同一块资源,可以开始通信了!

⑥:通信结束后进程A取消对这块共享内存的关联,进程B也取消对这块共享内存的关联;

⑦:最后一步:谁把这块共享内存创建出来的谁就来释放,这里明显是进程A来创建的所以只需要进程A来释放,进程B啥事也不用做;

1.3如何保证不同进程看到同一块共享内存?怎么知道这块共享内存存在还是不存在??

介绍一个接口:

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char* pathname,int proj_id);
//这是一个用来获取key的接口,这个key具有唯一性
// pathname、proj_id :通过这两货运用算法生成一个重复概率比较低的key值,pathname和proj_id参数可以随便传
// 返回值key_t :如果获取失败返回值小于0,如果成功返回key值

通过这个key值向内存中申请一块空间,确保了这块共享内存空间的唯一性!

有了key值那就来创建一块共享内存空间吧!

介绍一个接口:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);
//1. 这是一个通过key值来创建或者获取共享内存的接口,注意这里创建跟获取的意思:
创建:申请的这块共享内存空间必须不存在,存在就出错返回,确保这块共享内存一定是新的,确保了‘我’一定是第一个创建的!!
获取: 获取的意思是这块共享内存不存在就创建,存在就获取并返回,即获得的这一块共享内存并不一定是新的!!有可能是已经被别人创建好的了!!
//2. key -> 一个已经通过接口函数创建好的key值;size -> 想要创建内存空间的大小,这里最好是4096的整数倍,因为你申请4097系统会给你8192shmflg-> 通过这个参数传参使这个接口函数的功能发生改变,如果单单传的是IPC_CREAT功能就是获取,如果传IPC_CREAT|IPC_EXCL|0666  功能等于创建,这里的0666是权限根据自己需求写;int返回值 -> 创建失败会返回<0;创建成功返回一个值,这个值就是shmid作用相当于key,只不过shmid是给用户层使用的,用户可以通过shmid找到这块共享内存对其进行操作,而key是共享内存的内核数据结构,用来标志这块内存的唯一性,是操作系统内核数据结构用的;

OK,接口有了,那我们就来创建一块共享内存吧!

二、共享内存的创建

2.1 在.hpp文件中实现创建跟获取方法,在进程中直接调用
//.hpp
#ifndef __COMM__
#define __COMM__
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATH_NAME "/home/LDCWD/learncode"
#define PROJ_ID 12345
#include <cstdio>
#include <cstdlib>
#define SHMSIZE 4096
key_t GetKey()
{key_t key=ftok(PATH_NAME,PROJ_ID);if(key<0){perror("ftok");exit(-1);}return key;
}
int GetSHMmethod(int shmflg)
{key_t key=GetKey();int shmid=shmget(key,SHMSIZE,shmflg);if(shmid<0){perror("shmget");exit(-2);}return shmid;
}
int CreatSHM()//创建
{return GetSHMmethod(IPC_CREAT|IPC_EXCL|0666);
}
int GetSHM()//获取
{return GetSHMmethod(IPC_CREAT);
}#endif
//进程A
#include "comm.hpp"
int main()
{int shmid=CreatSHM();return 0;
}

编译运行后查看共享内存:(命令:ipcs -m)

运行前:

编译ProcessA并运行:

可以看到我们成功创建好一块共享内存,shmid为13,key为0x39010dd7 ,权限为666,大小为4096字节;

2.2 共享内存的生命周期

当程序运行结束后,我们发现这块共享内存依然存在!

由此可得出结论:共享内存的生命周期是随内核的!用户不主动关闭,共享内存会一直存在!(除非内核重启或者用户释放!)

注意:这里的nattch是关联的意思,已经就是当前的共享内存有0个进程关联,即当前共享内存挂载到0个进程当中,ok!接下来我们把共享内存挂载到进程中看看nattch是否会发生变化?

2.3 共享内存的关联

介绍两个接口:

#include <sys/types.h>
#include <sys/shm.h>
void* shmat(int shmid,const void* shmaddr,int shmflg);
//1. 这是一个把共享内存挂载到进程地址空间中的函数接口
//2. shmid -> 用key创建内存时的返回值
//3. shmaddr -> 设为null让操作系统去做,shmflg也设为0int shmdt(const void* shmaddr);
//1. 这是一个取消共享内存关联的函数接口
//2 . shmaddr -> 只要告诉起始地址
//3. int -> 返回值如果取消失败返回-1

接下来我们要做的是:

①挂载到进程A;

②取消进程A与共享内存的关联;

查看nattch的变化!

代码:

int main()
{int shmid=CreatSHM();sleep(3);char *shmptr=(char*)shmat(shmid,NULL,0);//挂载sleep(3);int n=shmdt(shmptr);//取消关联if(n==-1){perror("shmdt");exit(-3);}return 0;
}

运行3s后nattch由0变1,接下来3s后nattch由1变0

所以关联与取消关联成功!

接下来再介绍一个接口:

#include<sys/ipc.h>
#inlcude <sys/shm.h>
int shmctl(int shmid, int cmd ,struct shmid_ds* buf);
//1. 这是一个释放共享内存的接口函数,也可以是一个获取共享内存属性的接口函数
//2. 当需要释放内存的时候,shmid传入的还是那个shmid,cmd需要设置为 IPC_RMID,并且 buf设空
//3. 当需要获取共享内存属性时,shmid还是那个shmid ,cmd需要设置为 IPC_STAT,buf需要设为共享内存数据结构的地址;获取后再通过访问这个内存数据结构体的成员变量获得;
2.4共享内存的属性获取与共享内存的释放

增加代码后编译运行:

int main()
{int shmid=CreatSHM();//创建共享内存sleep(3);char *shmptr=(char*)shmat(shmid,NULL,0);//关联sleep(3);int n=shmdt(shmptr);//取消关联if(n==-1){perror("shmdt");exit(-3);}//获取属性并打印struct shmid_ds shmds;shmctl(shmid,IPC_STAT,&shmds);std::cout<<"atime:"<<shmds.shm_atime<<" "<<"nattch:"<<shmds.shm_nattch<<" "<<std::endl;//释放共享内存shmctl(shmid,IPC_RMID,NULL);return 0;
}

共享内存属性成功打印出来,并成功释放掉共享内存!

以上进程A的创建共享内存并关联还有释放工作已经就绪,接下来就是进程B去获取进程A已创建好的共享内存,并挂载到自己的进程空间地址中,然后就可以开始通信了!!!

三、进程间通信

3.1进程B的工作
//进程B
#include "comm.hpp"
int main()
{int shmid=GetSHM();//获取共享内存char *shmptr=(char*)shmat(shmid,NULL,0);//关联sleep(3);int n=shmdt(shmptr);//取消关联if(n==-1){perror("shmdt");exit(-3);}return 0;
}

我们让进程A创建好后休眠两秒再关联然后休眠10s后再取消关联,进程B获取后直接关联,然后休眠三秒后再取消关联,先运行进程A再运行进程B,同时查看nattch的变化:

写一下makefile:

//makefile
.PHONY:all
all:ProcessA ProcessB
ProcessA:ProcessA.ccg++ -o $@ $^ -std=c++11
ProcessB:ProcessB.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f ProcessA ProcessB

make之后生成两个进程:

先运行A再运行B:

while :; do ipcs -m ;sleep 1;done 查看:

可以看到nattch从A创建出来从 0,到A挂载-> 1 到B挂载 -> 2 到B 取关 -> 1 到A取关 -> 0 到A释放内存  -> 空的过程!!

3.2开始通信

我们让B进程往共享内存里写,A进程从共享内存中读取

进程A通信代码:

进程B通信代码:

运行 A 后运行 B:

注意此时我还没开始运行B ,A已经开始读起来了!

运行B后输入内容:

进程B:

进程A:

B进程:

A进程:

OK,到这里进程A跟进程B已经实现通信了!!

四、共享内存通信特点

4.1、共享内存没有同步与互斥之类的保护机制;
4.2、共享内存是所有通信中速度最快的,原因:拷贝少

例:管道通信:

共享内存通信直接往内存里写或者读就行,不需要拷贝!

4.3、共享内存内部的数据由用户自己维护;

五、利用管道实现共享内存通信的同步

5.1、实现方法,思路

利用管道实现同步的思路:①首先要由一个进程负责管道的创建与删除工作;

②让读端在从共享内存里读取数据前先从管道里读内容,这个内容是写端写入的内容,如果读取到说明写端已经向共享内存完成写入,就可以向共享内存中读取数据了!!

③让写端在往共享内存中写好数据后,接着往管道里写入内容,表面共享内存写端已写入完成,可以开始读取了!!

由以上三个步骤可以实现同步!

5.2 代码,测试

向头文件中加入Init类,用来对管道的创建与删除工作:

class Init
{public:Init(){//创建命名管道int retmkfifo=mkfifo(FIFOFILE_NAME,MODE);if(retmkfifo==-1){perror("mkfifo");exit(-6);}}~Init(){//删除管道文件int retunlink=unlink(FIFOFILE_NAME);if(retunlink==-1){perror("unlink");exit(-7);}}
};

在A进程中实例化对象,并在读共享内存之前打开管道文件并读取:

在进程B中写入共享内存完成后向管道写入内容,表示可以读了:

可以看到A进程运行后没有立马开始读,在等待B进程输入:

B进程输入一条消息后A进程不会一直输出,只会打印一条消息!!

OK!!!以上关于实现共享内存通信的所有基本步骤完成!!!

如果对您有所帮助麻烦点赞收藏+关注哦!!谢谢!!!

咱下期见!!!

相关文章:

  • 软件产品登记测试 VS 确认测试有何不同?第三方检测机构深度解析
  • 0901context_useReducer_状态管理-react-仿低代码平台项目
  • Django 学习指南:从入门到精通(大体流程)
  • 健康养生:构建健康生活的多维度指南
  • 扩展根分区
  • Word中批量修改MathType公式
  • 完美解决react-native文件直传阿里云oss问题一
  • 港口危货储存单位主要安全管理人员考试精选题目
  • K8S - HPA + 探针实战 - 实现弹性扩缩与自愈
  • springboot框架常用配置
  • Microsoft Entra ID 详解:现代身份与访问管理的核心
  • 《PyTorch documentation》(PyTorch 文档)
  • 学习记录:DAY21
  • 深度解析:Vue.js 性能优化全景指南(从原理到实践)
  • 破局 AI 焦虑:企业如何抢占智能时代的制高点
  • DC-DC常见应用问题解疑
  • 2025年CC攻击防御全攻略:应对复杂化攻击的实战策略
  • DeepSeek基础-使用python请求deepseek
  • 2025华东杯A/B/C题解题思路+可运行代码参考
  • 从 “可办“ 到 “好办“:云蝠大模型如何重塑政务服务体验
  • 陕西省通报6起违反八项规定典型问题,省卫健委原主任刘宝琴违规收受礼品礼金
  • “80后”蒋美华任辽宁阜新市副市长
  • 节前A股持续震荡,“五一”假期持股还是持币过节胜率更高?
  • 海尔智家一季度营收791亿元:净利润增长15%,海外市场收入增超12%
  • 80后共青团云南省委副书记许思思已任迪庆州委副书记
  • 五一“拼假”催热超长假期,热门酒店民宿一房难求