1.原理
2.相关函数以及相关指令
1.shmget//创建共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
key为用户给操作系统来标识共享内存的标识符,方便另一个进程来找到共享内存
size为共享内存大小
shmflg 创建共享内存的方式,位图形式
返回shmid操作系统给用户的对应共享内存的操作符,用来对共享内存进行操作,比如说要释放共享内存,给shmctl函数传参shmid,要查看对应共享内存的属性,给shmctl也要传shmid
2.ftok//产生一个随机值key,保证共享内存的唯一性
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
pathname :路径字符串(任意)
proj_id:任意数值
返回值为key,保证共享内存的唯一性,保证两个进程的参数一样,俩个进程得到相同的key
3.shmat//将对应共享内存映射到当前进程的地址空间中
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid是区分共享内存的标识符,shmaddr是要挂在地址空间的地址,如果设置为nullptr,操作系统自己分配,shmflg设置为0即可
返回的是共享内存映射到地址空间的起始地址
4.shmdt//对应的共享内存和进程的地址空间断开映射关系
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
shmaddr为共享内存映射到地址空间的起始地址
5.ipcs -m //查看内存中的共享内存
6.ipcrm -m +shmid号
//释放对应的共享内存
7.shmctl//对共享内存管理操作
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd为对共享内存的操作,buf为共享内存的结构体
查看共享内存属性第二个参数传IPC_STAT
释放共享内存第二个参数传IPC_RMID
3.代码实现
1.实现共享内存创建以及两进程拿到一个key和shmid
shm.hpp
#ifndef __SHM_HPP__ #define __SHM_HPP__ #include<iostream> #include<string> #include <sys/types.h>//ftok #include <sys/ipc.h>//ftok #include <sys/ipc.h>//shmget #include <sys/shm.h>//shmget using namespace std; const int gCreator=1; const int gUer=2; const string pathname="/home/zjw/lesson25"; const int g_id=0x21; const int gshmsize=4096; class shm { private: key_t getcommkey() //获得key,是用户给系统用来唯一确定一块共享内存 { key_t k=ftok(_pathname.c_str(),_id);//两个都是用户给的 if(k<0) { perror("ftok"); } return k; } int getshmhelper(key_t key,int size,int flag) { int shmid=shmget(key,size,flag);//创建共享内存的函数//key是用户给操作系统,方便其他进程找到对应的共享内存,size是共享内存的大小//flag是共享内存的权限 //flag可以区分是创建共享内存的,还是找共享内存的 if(shmid<0) {perror("shmid");} return shmid;//shmid<0说明共享内存申请失败,shmid是操作系统给用户的,方便用户对共享内存的操作,比如说删除共享内存 } public: shm(const string &pathname,int id,int who) :_pathname(pathname) ,_id(id) ,_who(who) { _key=getcommkey();//相当于初始化时创建端的key和找key的进程一样,因为ftok函数的两个参数都是全局的,返回的key是一样的 if(_who==gCreator)getshmusecreate(); else if(_who==gUer)getshmforusr(); cout<<"shmid:"<<_shmid<<endl; cout<<"_key:"<<_key<<endl; } bool getshmusecreate() { if(_who==gCreator) { _shmid= getshmhelper(_key,gshmsize,IPC_CREAT|IPC_EXCL|0666); if(_shmid>=0) {cout<<"shm create done..."<<endl; return true;} } return false; } bool getshmforusr() { if(_who==gUer) { _shmid=getshmhelper(_key,gshmsize,IPC_CREAT|0666); if(_shmid>=0) { cout<<"shm get done..."<<endl; return true; } } return false; } ~shm() { if(_who==gCreator) { int res=shmctl(_shmid,IPC_RMID,nullptr);//第一个参数是对一个共享内存的标识,给用户,方便用户对共享内存进行操作,IPC_RMID对共享内存的删除 //第三个一个结构体的地址,保存共享内存的属性 cout<<"shm remove done..."<<endl; } } private: key_t _key; int _shmid; string _pathname; int _id; int _who; }; #endif
2.client.cc
#include"shm.hpp" int main() { shm cli(pathname,g_id,gCreator); while(1) //防止析构,释放共享内存 {} }
3.server.cc
#include"shm.hpp" int main() { shm ser(pathname,g_id,gUer); while(1) {} }
共享内存创建,进程拿到key和shmid
2.实现通信
shm.hpp
#ifndef __SHM_HPP__ #define __SHM_HPP__ #include<iostream> #include<string> #include <unistd.h> #include <sys/types.h>//ftok #include <sys/ipc.h>//ftok #include <sys/shm.h>//shmget using namespace std; const int gCreator=1; const int gUer=2; const string pathname="/home/zjw/lesson25"; const int g_id=0x21; const int gshmsize=4096; class shm { private: key_t getcommkey() //获得key,是用户给系统用来唯一确定一块共享内存 { key_t k=ftok(_pathname.c_str(),_id);//两个都是用户给的 if(k<0) { perror("ftok"); } return k; } int getshmhelper(key_t key,int size,int flag) { int shmid=shmget(key,size,flag);//创建共享内存的函数//key是用户给操作系统,方便其他进程找到对应的共享内存,size是共享内存的大小//flag是共享内存的权限 //flag可以区分是创建共享内存的,还是找共享内存的 if(shmid<0) {perror("shmid");} return shmid;//shmid<0说明共享内存申请失败,shmid是操作系统给用户的,方便用户对共享内存的操作,比如说删除共享内存 } void* attachshm() { void*shmaddr=shmat(_shmid,nullptr,0);//对应进程共享内存和地址空间链接,返回的是共享内存在地址空间对应的起始地址 if(shmaddr==nullptr) { perror("shmat"); } cout<<"who:>"<<_who<<"< "<<"1是gCreator,2是gUer"<<"对应进程共享内存和地址空间链接"<<endl; return shmaddr; } void detachshm(void* shmaddr) { if(shmaddr==nullptr) return ; shmdt(shmaddr);//将共享内存段从调用进程的地址空间中分离。 //cout<<"who:>"<<_who<<"< "<<"1是gCreator,2是gUer"<<"对应进程共享内存和地址空间断开链接"<<endl; } public: shm(const string &pathname,int id,int who) :_pathname(pathname) ,_id(id) ,_who(who) { _key=getcommkey();//相当于初始化时创建端的key和找key的进程一样,因为ftok函数的两个参数都是全局的,返回的key是一样的 if(_who==gCreator)getshmusecreate(); else if(_who==gUer)getshmforusr(); _addrshm=attachshm();//返回对应共享空间对应在地址空间的起始地址 } bool getshmusecreate() { if(_who==gCreator) { _shmid= getshmhelper(_key,gshmsize,IPC_CREAT|IPC_EXCL|0666);//为了传参给 getshmhelper if(_shmid>=0) {cout<<"shm create done..."<<endl; return true;} } return false; } bool getshmforusr() { if(_who==gUer) { _shmid=getshmhelper(_key,gshmsize,IPC_CREAT|0666); if(_shmid>=0) { cout<<"shm get done..."<<endl; return true; } } return false; } ~shm() { if(_addrshm!=nullptr) detachshm(_addrshm); if(_who==gCreator) { int res=shmctl(_shmid,IPC_RMID,nullptr);//第一个参数是对一个共享内存的标识,给用户,方便用户对共享内存进行操作,IPC_RMID对共享内存的删除 //第三个一个结构体的地址,保存共享内存的属性 cout<<"shm remove done..."<<endl; } } void* addr()//让两个进程能拿到类的私有 { return _addrshm; } private: key_t _key; int _shmid; string _pathname; int _id; int _who; void*_addrshm; }; #endif
client.cc
#include"shm.hpp" #include"namepipe.hpp" int main() { shm cli(pathname,g_id,gCreator); char*shmaddr=(char*)cli.addr(); sleep(10); char ch='A'; while(ch<='Z') { shmaddr[ch-'A']=ch; sleep(2); ch++; } }
server.cc
#include"shm.hpp" int main() { shm ser(pathname,g_id,gUer); char*shmaddr=(char*)ser.addr(); while(1) { cout<<"shm memory content:"<<shmaddr<<endl; sleep(1); } }
共享内存通信
我们通过对上面的演示,共享内存并不提供对共享内存的保护机制,当没有往共享内存里面写东西的时候,还可以读共享内存
数据不一致问题—共享内存的缺点
我们在访问共享内存时,没有任何的系统调用
共享内存是所有进程IPC,速度最快,共享内存大大减小了数据拷贝次数
解决方法:我们可以通过管道的特性来对共享内存做保护,当我们开始往共享内存写东西了,打开管道的写端,
将管道读端放在读共享内存前面,因为管道写端不写,管道读端就会阻塞,等待管道写,这样就解决了未往共享内存写,就开始读这种缺点
3.解决共享内存的缺点
将之前写的命名管道移到今天lesson25目录下
client.cc
#include"shm.hpp" #include"namepipe.hpp" int main() { shm cli(pathname,g_id,gCreator); char*shmaddr=(char*)cli.addr(); Namepipe fifo(com_path,Creater); fifo.openforwrite(); sleep(10); char ch='A'; while(ch<='Z') { shmaddr[ch-'A']=ch; string temp="wake"; fifo.writenamepipe(temp); sleep(2); ch++; } }
server.cc
#include"shm.hpp" #include"namepipe.hpp" int main() { shm ser(pathname,g_id,gUer); Namepipe fifo(com_path,User); fifo.openforread(); char*shmaddr=(char*)ser.addr(); while(1) { string tmp; fifo.readnamepipe(&tmp); cout<<"shm memory content:"<<shmaddr<<endl; sleep(1); } }
namepipe.hpp
#include<iostream> #include <sys/types.h> //mkfifo的头文件open头文件 #include<string> #include <sys/stat.h> //mkfifo的头文件open头文件 #include <unistd.h> //unlink头文件/close头文件 #include <fcntl.h> //open头文件 using namespace std; const string com_path="./myfifo"; #define Defaultfd -1 #define Creater 1 #define User 2 #define Read O_RDONLY #define Write O_WRONLY #define Basesize 1024 class Namepipe { private: bool opennamepipe(int mode) { _fd=open(_fifo_path.c_str(),mode); if(_fd<0) return false; return true; } public: Namepipe(const string &path,int who) :_fifo_path(path) ,_id(who) ,_fd(Defaultfd) { if(_id==Creater) { int ret=mkfifo(_fifo_path.c_str(),0666); if(ret!=0) { perror("mkfifo"); } cout<<"creater create named pipe"<<endl; } } ~Namepipe() { if(_id==Creater) { int ret=unlink(_fifo_path.c_str()); if(ret!=0) { perror("unlink"); } cout<<"creater free namepipe"<<endl; } if(_fd!= Defaultfd)close(_fd); } bool openforread() { return opennamepipe(Read); } bool openforwrite() { return opennamepipe(Write); } int writenamepipe(const string &in) { return write(_fd,in.c_str(),in.size()); } int readnamepipe(string *out) { char buffer[Basesize]; int n=read(_fd,buffer,sizeof(buffer)); if(n>0) {buffer[n]=0; *out=buffer; } return n; } private: const string _fifo_path; int _id; int _fd; };
解决共享内存的缺点