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!!!以上关于实现共享内存通信的所有基本步骤完成!!!
如果对您有所帮助麻烦点赞收藏+关注哦!!谢谢!!!
咱下期见!!!