目录
一、什么是共享内存
1.1创建共享内存
1.2释放共享内存
1.2.1shmctl
1.2.2shmat
1.2.3 shmdt
二、共享内存的实现及使用
2.1ShmClient
2.2Shm_Server
2.3Fifo.hpp
2.4Comm.hpp
一、什么是共享内存
标准系统V也叫system V的本地通信方式一般有三种:
1、共享内存
2、消息队列
3、信号量
而博主此片文章主要从共享内存方面去进行使用及讲解
1.1创建共享内存
共享内存的系统调用,第一个参数是共享内存在内核中唯一性的标识,第二个参数是所要创建共享内存的大小 ,第三个参数有两个选项:IPC_CRREAT和IPC_EXCL,有以下三种组合使用方法:
IPC_CRREAT:如果共享内存不存在,就创建它,如果共享内存已经存在,直接获取它
IPC_EXCL:不能单独使用,没有意义
IPC_CRREAT|IPC_EXCL:如果共享内存不存在就创建,如果存在就出错返回。
第三种方法可以保证如果创建的共享内存是成功的那么它一定是一个新的共享内存。
创建成功则会返回共享内存的标识符,失败返回-1,错误码被返回表示错误原因。
共享内存在内核中同时可以存在很多个。而OS管理多个共享内存依旧是六字真言:先描述再组织
所以对共享内存的管理就变成了对描述共享内存的结构体的管理。
而第一个参数key要具有唯一性,而这个key值不一定要我们自己去创建,可以去调用系统调用ftok这个算法生成一个key值。ftok第一个参数是用户传入一个const char*的字符串,第二个参数是一个int类型。所以我们可以通过ftok在两个进程中通过同样的pathname和id来让两个进行看到同一块共享内存。
文件操作,一个进程打开一个文件,进程退出的时候,这个被打开的文件就会被系统自动释放掉。文件的生命周期随进程但是注意:共享内存,如果进程结束,用户没有主动释放它,则其会一直存在。共享内存的生命周期随内核。
1.2释放共享内存
ipcs
ipcs可以查出三种资源:消息队列、共享内存、信号量。
perms则是权限
ipcs -m//查看系统中指定用户创建的共享内存
ipcrm -m shmid//删除shmid所对应的共享内存
以上都是指令级别的操作,而在代码中就需要调用系统接口来进行删除。
1.2.1shmctl
对共享内存进行设置、获取、删除三种功能。
shmid就是共享内存的id
cmd表示你想对其进行什么操作
buf则是含有共享内存熟悉的sturct结构体
获取用IPC_STAT可以获取struct shmid_ds中的各各种属性信息
设置用IPC_SET
删除用IPC_RMID
1.2.2shmat
at的意思就是attach关联的意思,因为共享内存是属于操作系统的,所以我们需要将共享内存挂接到当前进程地址空间的共享区,此时就需要用到shmat。
第一个参数表示要将哪一个共享内存贴到当前进程地址空间,第二个参数代表用户指明将shm挂接到哪里,所以第二个参数就是个地址(可以nullptr省略),第三个参数shmflag代表的是在进行挂接时的形式一般默认设置为0,保证可以读写。
如果挂接成功,会返回在进程地址空间中所挂接的虚拟地址。失败返回-1。
挂接完成后可以拿着返回的地址直接访问共享内存。
1.2.3 shmdt
dt即detach去关联的意思,将共享内存从进程地址空间中删除。将共享内存的虚拟地址传进来,就可以进行进程地址空间和共享内存的去关联。
不管是指令级还是代码级的操作, 最后对共享内存进行控制,用的都是shmid,类似于操作文件时使用的fd。
二、共享内存的实现及使用
共享内存和管道不同,它不提供进程间协同的任何机制,这是共享内存的缺点,会引起数据不一致。但是共享内存是所有进程间通信中速度最快的。这是其优点 。
所以需要用户来进行共享内存协调机制的实现。
2.1ShmClient
#include "Comm.hpp"
#include "Fifo.hpp"
int main()
{
//1、获取key
key_t key=GetShmKeyOrDie();
cout<<"key: "<<tosix(key)<<endl;
//2、创建共享内存
int shmid=GetShm(key,defaultsize);//创建一个全新的共享内存
cout<<"shmid: "<<shmid<<endl;
char* addr=(char*)ShmAttach(shmid);
cout<<"Attach shm sucess,addr: "<<tosix((uint64_t)addr)<<endl;
memset(addr,0,defaultsize);
//以读方式打开管道
Sync syn;
syn.OpenReadOrDie();
//进程间通信
for(char c='A';c<='Z';c++)
{
addr[c-'A']=c;
sleep(1);
syn.Wakeup();
}
ShmDetach(addr);
cout<<"Detach shm sucess,addr: "<<tosix((uint64_t)addr)<<endl;
sleep(5);
return 0;
}
2.2Shm_Server
#include "Comm.hpp"
#include "Fifo.hpp"
int main()
{
//1、获取key
key_t key=GetShmKeyOrDie();
cout<<"key: "<<tosix(key)<<endl;
//2、创建共享内存
int shmid=CreateShm(key,defaultsize);//创建一个全新的共享内存
cout<<"shmid: "<<shmid<<endl;
//ShmDebug(shmid);
//4、挂接共享内存
char* addr=(char*)ShmAttach(shmid);
cout<<"Attach shm sucess,addr: "<<tosix((uint64_t)addr)<<endl;
//0.为了弥补进程间的协同机制,先引入管道
Fifo fifo;
Sync syn;
syn.OpenReadOrDie();
//进行进程间通信
for(;;)
{
if(!syn.Wait()) break;
cout<<"shm content: "<<addr<<endl;
}
ShmDetach(addr);
cout<<"Detach shm sucess,addr: "<<tosix((uint64_t)addr)<<endl;
//3、删除共享内存
sleep(100);
DeleteShm(shmid);
return 0;
}
2.3Fifo.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
//头文件
#include <cstring>
#include <string>
#include <cerrno>
#include <iostream>
#include<cassert>
//调用fifo所需的头文件+opean用到的头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//open
//调用unlink对管道进行析构的头文件
#include <unistd.h>
//define
#define Mode 0666
#define Path "./fifo"
using namespace std;
class Fifo
{
public:
Fifo(const string &path=Path):_path(path)//创建有名管道
{
umask(0);
int n=mkfifo(_path.c_str(),Mode);
if(n==0)
{
cout<<"mkfifo sucess"<<endl;
}
else
{
cerr<<"mkfifo failed,errno: "<<errno<<",errstring: "<<strerror(errno)<<endl;
}
}
~Fifo()
{
int n=unlink(_path.c_str());//删除管道
if(n==0)
{
cout<<"remove fifo file "<<_path<<"sucess"<<endl;
}
else
{
cerr<<"remove failed,errno: "<<errno<<",errstring: "<<strerror(errno)<<endl;
}
}
private:
string _path;//有名管道的文件路径+文件名
};
class Sync
{
public:
Sync()
:rfd(-1)
,wfd(-1)
{}
void OpenReadOrDie()
{
rfd=open(Path,O_RDONLY);
if(rfd<0) exit(1);
}
void OpenWriteOrDie()
{
wfd=open(Path,O_WRONLY);
if(wfd<0) exit(1);
}
bool Wait()
{
bool ret=true;
uint32_t c=0;
ssize_t n=read(rfd,&c,sizeof(uint32_t));//读到数据才能接着往后走
if(n==sizeof(uint32_t))
{
cout<<"server wakeup,begin read shm..."<<endl;
}
else if(n<=0)
{
ret=false;
}
else
{
return false;
}
return ret;
}
void Wakeup()
{
uint32_t c=0;
ssize_t n=write(wfd,&c,sizeof(uint32_t));
assert(n==sizeof(uint32_t));
cout<<"wakeup server..."<<endl;
}
~Sync()
{
}
private:
int rfd;
int wfd;
};
#endif
2.4Comm.hpp
#pragma once
#include <unistd.h>
#include <iostream>
#include <cerrno>
#include <string>
#include <cstring>
#include <cstdlib>
//shmget 创建共享内存所需头文件
#include <sys/ipc.h>
#include <sys/shm.h>
//ftok 所需头文件
#include <sys/types.h>
using namespace std;
//创建一个共享内存
const char* pathname="/home/gaz";
const int proj_id=0x66;
const int defaultsize=4096;//单位是字节
//在内核中,共享内存大小是以4KB为基本单位的,只能用自己申请的大小,一般建议申请大小为N*4KB
//将标识符转换成16进制
string tosix(key_t k)
{
char buffer[1024];
snprintf(buffer,sizeof(buffer),"0x%x",k);
return buffer;
}
//根据pathname和proj_id转换出一个key值
key_t GetShmKeyOrDie()
{
key_t k=ftok(pathname,proj_id);
if(k<0)
{
cerr<<"ftok error,errno: "<<errno<<",error string: "<<strerror(errno)<<endl;
exit(1);
}
return k;
}
//创造或获取一个共享内存
int CreateShmorDie(key_t key,int size,int flag)
{
int shmid=shmget(key,size,flag);
if(shmid<0)
{
cerr<<"shmget error,errno: "<<errno<<",error string: "<<strerror(errno)<<endl;
exit(2);
}
return shmid;
}
//对于服务端,创建一个全新的共享内存
int CreateShm(key_t key,int size)
{
// IPC_CREAT: 不存在就创建,存在就获取
// IPC_EXCL: 没有意义
// IPC_CREAT | IPC_EXCL: 不存在就创建,存在就出错返回
return CreateShmorDie(key,size,IPC_CREAT | IPC_EXCL | 0666);
}
//对于使用端,获取已经创建成功的共享内存
int GetShm(key_t key,int size)
{
return CreateShmorDie(key,size,IPC_CREAT);
}
void DeleteShm(int shmid)
{
int n=shmctl(shmid,IPC_RMID,nullptr);
if(n<0)
{
cerr<<"shmctl error"<<endl;
}
else
{
cout<<"shmctl delete shm sucess,shmid: "<<shmid<<endl;
}
}
void ShmDebug(int shmid)
{
struct shmid_ds shmds;
int n=shmctl(shmid,IPC_STAT,&shmds);//IPC_STAT获取shmid_ds中的各种属性信息
if(n<0)
{
cerr<<"shmctl error"<<endl;
return;
}
cout<<"shmds.shm_segsz: "<<shmds.shm_segsz<<endl;
cout<<"shmds.shm_segsz: "<<shmds.shm_nattch<<endl;
cout<<"shmds.shm_segsz: "<<shmds.shm_ctime<<endl;
}
void* ShmAttach(int shmid)//将共享内存和进程地址空间进行挂接
{
void* addr=shmat(shmid,nullptr,0);
if((long long int)addr==-1)//因为机器是64位的,int是4字节会引起精度损失
{
cerr<<"shmat error"<<endl;
return nullptr;
}
return addr;
}
void ShmDetach(void* addr)//将共享内存和进程地址空间进行去关联
{
int n=shmdt(addr);
if(n<0)
{
cerr<<"shm error"<<endl;
}
}