前文:
Linux–进程间的通信-匿名管道
Linux–进程间的通信–进程池
Linux–进程间的通信-命名管道
共享内存
对于两个进程,通过在内存开辟一块空间(操作系统开辟的),进程的虚拟地址通过页表映射到对应的共享内存空间中,进而实现通信;
特点和作用:
高效性: 共享内存是一种高效的进程间通信方式,因为它允许多个进程直接访问同一块内存,而无需进行复制或数据传输。快速通信: 由于共享内存直接映射到进程的地址空间,因此读写速度快,适用于对通信速度有较高要求的场景。灵活性: 共享内存提供了一种灵活的通信方式,允许多个进程在需要时访问共享数据,而无需通过中间介质进行通信。数据共享: 多个进程可以通过共享内存实现数据共享,从而实现对数据的共同读写和处理。模拟实现
代码
Comm.hpp:包含共享内存的创建,销毁,挂接进程等。
#pragma once#include<stdio.h>#include<iostream>#include<string>#include<cerrno>#include<cstring>#include<cstdlib>#include<sys/ipc.h>#include<sys/types.h>#include<sys/shm.h>using namespace std;const char* pathname="/home/ubuntu/Learning/Pipe";const int proj_id=0x66;//在内核中,共享内存的基本单位是4kb,我们申请的大小相当于是n*4kbconst int DefaultSize=4096;//将key值转换为16进制的;string ToHEX(key_t k){ char buffer[1024]; snprintf(buffer,sizeof(buffer),"0x%x",k); return buffer;}//获取键值key_t GetShmKeyorDie(){ key_t k=ftok(pathname,proj_id); if(k<0) { //当返回值为-1时,错误表示stat(2)系统调用错误 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) { std::cerr << "shmget error, errno : " << errno << ", error string: " << strerror(errno) << std::endl; exit(2); } return shmid;}//调用时的创建共享内存int CreateShm(key_t key,int size){ //如果已经存在了,那么会报错; 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 success, shmid: "<<shmid<<endl; }}//查看共享内存的状态void ShmDebug(int shmid){ struct shmid_ds shmds; int n=shmctl(shmid ,IPC_STAT,&shmds); if(n<0) { std::cerr << "shmctl error" << std::endl; return; } std::cout << "shmds.shm_segsz: " << shmds.shm_segsz << std::endl; std::cout << "shmds.shm_nattch:" << shmds.shm_nattch << std::endl; std::cout << "shmds.shm_ctime:" << shmds.shm_ctime << std::endl; std::cout << "shmds.shm_perm.__key:" << ToHEX(shmds.shm_perm.__key) << std::endl;}void* ShmAttach(int shmid){ void* addr = shmat(shmid,nullptr,0); //第二个参数设置nullptr,表示让系统选择合适的地址进行连接 if((long long int)addr==-1) { cerr<<"shmat error"<<endl; return nullptr; } return addr;}void ShmDetach(void* addr){ int n=shmdt(addr); if(n<0) { cerr<<"shmdt error"<<endl; }}
fifo.hpp:利用管道来实现对共享内存实现同步机制。
#include<iostream>#include<string>#include<cstring>#include<cerrno>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<fcntl.h>#include<assert.h>using namespace std;#define Mode 0666#define Path "./fifo"class fifo{public: fifo(const string & path=Path) :_path(path) { umask(0); int n=mkfifo(_path.c_str(),Mode); if(n==0) { cout<< "mkfifo success" << 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 << " success" << 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 OpenWriteDie() { _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(c)); assert(n==sizeof(uint32_t)); cout<<"wakeup server..."<<endl; } ~Sync() {}private: int _wfd; int _rfd;};
ShmServer.cc
#include "Comm.hpp"#include "fifo.hpp"#include<unistd.h>int main(){ //1.获取key key_t key = GetShmKeyorDie(); std::cout << "key: " << ToHEX(key) << std::endl; // sleep(2); //2.创建共享内存 int shmid = CreateShm(key, DefaultSize); std::cout << "shmid: " << shmid << std::endl; sleep(2); //4.将共享内存与进程挂接 char* addr=(char*)ShmAttach(shmid); cout<<"Attach shm success, addr: "<<ToHEX((uint64_t)addr)<<endl; //0.先引入管道 fifo ff; Sync syn; syn.OpenReadOrDie(); //进行通信 while(1) { if(!syn.Wait())break; cout<<"shm content: "<<addr<<endl; } ShmDetach(addr); std::cout << "Detach shm success, addr: " << ToHEX((uint64_t)addr) << std::endl; //3.删除共享内存 DeleteShm(shmid); return 0;}
ShmClient.cc
#include"Comm.hpp"#include "fifo.hpp"#include<unistd.h>int main(){ key_t key = GetShmKeyorDie(); std::cout << "key: " << ToHEX(key) << std::endl; // sleep(2); int shmid = GetShm(key, DefaultSize); std::cout << "shmid: " << shmid << std::endl; char* addr=(char*)ShmAttach(shmid); cout<<"Attach shm success, addr: "<<ToHEX((uint64_t)addr)<<endl; //通信 memset(addr,0,DefaultSize); Sync syn; syn.OpenWriteDie(); for(char c ='A';c<='Z';c++) { addr[c-'A']=c; sleep(1); syn.Wakeup(); } ShmDetach(addr); std::cout << "Detach shm success, addr: " << ToHEX((uint64_t)addr) << std::endl; return 0;}
解释
获取键值和创建共享内存
如果ftok函数返回失败时,我们就需要不断的尝试,对路径名和id值进行修改,直至成功。一般来说,有几种可能:
1:如果传入的路径名不存在2:传入的路径名没有读取权限,无法读取该文件的索引节点号3:文件的索引节点超过了8位,即超过了一个字节的范围4:系统中已经使用了所有的IPC键值