1. 共享内存的概念
System V共享内存,是一个高效的进程间通信IPC机制,允许多个进程共享同一块物理内存区实现快速的数据交换。如下图所示

这两个进程分别通过页表映射到这一块共享内存中
2. 共享内存的函数
shmget
功能: 创建新的共享内存段,或获取已经存在的共享内存标识符(shmid)
头文件和原型如下
cpp#include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
参数如下:
1. key:共享内存的唯一键值(由ftok生成或IPC_PRIVATE)
2. size:共享内存的大小(字节),创建时必须指定,获取已有的共享内存可设为0。
3. shmflg:权限标识(例如0666),可用权限如下
IPC_CREAT:如果不存在就创建新段
IPC_EXCL:与IPC_CREAT一起使用,若段已存在则失败。
SHM_HUGETLB:使用大页内存(需要内核支持)
返回值:成功返回共享内存标识符(shmid),失败返回-1,错误码存在error
shmat
功能:将共享内存段附加到当前进程的地址空间,返回指向共享内存的指针。
原型:
cpp#include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
1. shmid:共享内存标识符
2. shmaddr:指定附加地址(通常设为NULL)
3. shmflg:选项如下
SHM_RDONLY:以只读模式附加
SHM_REMAP(Linux特供):替换现有映射
返回值:成功返回共享内存的起始地址指针,失败返回(void*) -1
shmdt
功能:将共享内存段从当前进程的地址空间分离
cppint shmdt(const void *shmaddr);
参数:
shmaddr:shmat返回的指针
返回值: 成功返回0,失败返回-1
shmctl
在linux下可以用下面指令删除
bash
ipcrm -m shmid
功能:对共享内存段执行控制操作(删除、查询状态)
cppint shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
1. shmid:共享内存标识符
2. cmd:控制命令,选项如下
IPC_RMID:标记共享内存段为待删除(所有进程分离后销毁)
IPC_STAT:获取共享内存段的状态信息
IPC_SET:修改共享内存段的权限或所有者
3. buf:指向struct shmid_ds的指针,用于存储或设置状态信息
返回值:成功返回0,失败返回-1设置错误信息errno
ftok函数生成键值
功能:根据文件路径和项目标识符生成唯一键值(key_t)
功能和头文件
cpp#include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
参数:
1. pathname:一个存在的文件路径(例如/tmp/shmfile)。2. proj_id:项目标识符(一个字节,通常用一个字符)
返回值:成功返回生成的键值,失败返回-1
3. 共享内存通信
cpp
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0) \
//处理错误的宏
写端创建共享内存
cpp
int _k = ftok(".", 48);
_size= 4096;
_shmid=shmget(_k,_size,IPC_CREAT|IPC_EXCL|0666);
if(_shmid<0)
{
ERR_EXIT("shmid");
}
读端获取共享内存
cpp
int _k = ftok(".", 48);
_size= 4096;
_shmid=shmget(_k,_size,IPC_CREAT);
if(_shmid<0)
{
ERR_EXIT("shmid");
}
获取指向共享内存的指针
获取指向共享内存的指针
cpp
_start_mem=shmat(_shmid,nullptr,0);
if((long long)_start_mem<0)
{
std::cout<<"shmat 失败"<<std::endl;
ERR_EXIT("shmat");
}
将指针与共享内存分离
进程退出时没解除绑定,共享内存段就仍然保留在系统的共享内存资源中,直到shmctl或系统重启
cpp
int n=shmdt(_start_mem);//将指针与共享内存分离
if(n==0)//成功
{
printf("shmctl delete shm: %d success!\n",_shmid);
}
else
{
ERR_EXIT("shmdt");
}
共享内存也采用引用计数,shmdt成功时,引用计数-1
查看共享内存
cpp
#include <sys/shm.h>
struct shmid_ds {
struct ipc_perm shm_perm; // 权限和所有者信息
size_t shm_segsz; // 共享内存段大小(字节)
time_t shm_atime; // 最后附加时间(attach)
time_t shm_dtime; // 最后分离时间(detach)
time_t shm_ctime; // 最后修改时间(创建或属性变更)
pid_t shm_cpid; // 创建者进程的PID
pid_t shm_lpid; // 最后操作(attach/detach)的进程PID
shmatt_t shm_nattch; // 当前附加到该段的进程数
// ... 其他系统特定字段(可能因操作系统不同而扩展)
};
struct ipc_perm {
key_t __key; // 共享内存的键值(ftok生成)
uid_t uid; // 所有者的用户ID
gid_t gid; // 所有者的组ID
uid_t cuid; // 创建者的用户ID
gid_t cgid; // 创建者的组ID
unsigned short mode; // 权限标志(如0666)
unsigned short __seq; // 序列号(内核使用)
};
进程结束后共享内存不会销毁,导致资源泄露
cpp
struct shmid_ds ds;//结构体
int n=shmctl(_shmid,IPC_STAT,&ds);
printf("shm_segsz: %ld\n",ds.shm_segsz);
printf("key: 0x%x\n",ds.shm_perm.__key);
IPC_STAT通过shmctl获取共享内存段的元数据(存入我们存入的struct shmid_ds结构体)
销毁共享内存
cpp
//_shmid=shmget(_k,_size,IPC_CREAT|IPC_EXCL|0666);
//_shmid值如上所示
if(_shmid==gdefaultid) return;//没初始化
int n=shmctl(_shmid,IPC_RMID,nullptr);
if(n==0)//删除成功返回0
{
int n=shmdt(_start_mem);//将指针与共享内存分离
if(n==0)//成功
{
printf("shmctl delete shm: %d success!\n",_shmid);
}
else
{
ERR_EXIT("shmdt");
}
}
else//删除失败返回-1
{
ERR_EXIT("shmctr");
}
IPC_RMID是 shmctl第二个参数时->当引用计数为0即shmdt将所有附加进程都分离后,内核自动销毁该段。
4. 共享内存优缺点
优点
直接读写共享内存,没有数据拷贝 例如管道中拷贝到管道文件再拷贝出来的操作。资源占用就少。并且独立于进程存在,可以通过shmctl来显式删除
缺点
附加到共享内存后进程可以自由读写(没设置SHM_RDONLY),恶意进程可能破坏数据。
没有同步机制,容易导致数据不一致,由于数据更新不及时。可以通过命名管道的同步机制解决这一问题
5. 源码
Shm.hpp
cpp
#pragma once
#include<iostream>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<string>
#include<unistd.h>
const int gdefaultid=-1;
const int gsize=4096;
std::string pathname=".";
const int projid=0x66;
const int mode=0666;
#define CREATER "creater"
#define USER "user"
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0) \
class Shm
{
private:
void CreateHelper(int flg)
{
printf("key: 0x%x\n",_k);
_shmid=shmget(_k,_size,flg);
if(_shmid<0)
{
ERR_EXIT("shmid");
}
printf("shmid: %d\n",_shmid);
}
void Creat()//创建共享内存
{
// std::cout<<"创建共享内存的是: "<<_usertype<<std::endl;
CreateHelper(IPC_CREAT|IPC_EXCL|mode);//要给权限
}
void Get()//获取共享内存
{
CreateHelper(IPC_CREAT);
}
void Detach()
{
int n=shmdt(_start_mem);//将指针与共享内存分离
if(n==0)//成功
{
printf("shmctl delete shm: %d success!\n",_shmid);
}
else
{
ERR_EXIT("shmdt");
}
}
public:
Shm(const std::string& pathname,int projid,const std::string& usertype)
:_shmid(gdefaultid)
,_size(gsize)
,_start_mem(nullptr)
,_usertype(usertype)
{
_k=ftok(pathname.c_str(),projid);
if(_k<0)
{
ERR_EXIT("ftok");
}
if(_usertype==CREATER) Creat();//如果是创建者
else if(_usertype==USER) Get();//用户使用
else{}
Attach();
}
void Destroy()
{
if(_shmid==gdefaultid) return;//没初始化
int n=shmctl(_shmid,IPC_RMID,nullptr);
if(n==0)//删除成功返回0
{
Detach();//分离指针
// printf("shmctl delete shm: %d success!\n",_shmid);
}
else//删除失败返回-1
{
ERR_EXIT("shmctr");
}
}
void Attach()//
{
_start_mem=shmat(_shmid,nullptr,0);
if((long long)_start_mem<0)
{
std::cout<<"shmat 失败"<<std::endl;
ERR_EXIT("shmat");
}
printf("Attach success\n");
}
void* VirtualAddr()//返回虚拟地址
{
printf("VirtualAddr %p\n",_start_mem);
return _start_mem;
}
int Size()
{
return _size;
}
void Attr()
{
//System V 共享内存机制中用于描述共享内存段状态的核心数据结构
//通过 shmctl() 系统调用进行读取或修改。
struct shmid_ds ds;//结构体
int n=shmctl(_shmid,IPC_STAT,&ds);
printf("shm_segsz: %ld\n",ds.shm_segsz);
printf("key: 0x%x\n",ds.shm_perm.__key);
}
~Shm()
{
if(_usertype==CREATER)//创建的负责删除
Destroy();
}
private:
int _shmid;
key_t _k;
int _size;
void* _start_mem;
std::string _usertype;
};
fifo.hpp
通过命名管道来同步
cpp
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#define FIFO_FILE "fifo"
// #define path '.'
class Fifo
{
public:
Fifo(const std::string& path,const std::string& name)
{
umask(0);
_fifoname=path+'/'+name;
int n=mkfifo(_fifoname.c_str(),0666);
if(n<0)
{
std::cerr<<"mkfifo error"<<std::endl;//失败
}
}
~Fifo()
{
int n=unlink(_fifoname.c_str());//删除管道文件
if(n==0)
{
std::cout<<"remove fifo success"<<std::endl;
}
else
{
std::cout<<"remove fifo failed"<<std::endl;
}
}
private:
std::string _fifoname;
};
class FileOper
{
public:
FileOper(const std::string& path,const std::string& name)
:_path(path),_name(name)
{
_fifoname=_path+"/"+_name;
}
void OpenForRead()
{
_fd=open(_fifoname.c_str(),O_RDONLY);
if(_fd<0)
{
std::cerr<<"open fifo error"<<std::endl;
return ;
}
}
void OpenForWrite()
{
_fd=open(_fifoname.c_str(),O_WRONLY);
if(_fd<0)
{
std::cerr<<"open fido success"<<std::endl;
return ;
}
}
void Wakeup()//唤醒
{
char c;
c='a';
int n=write(_fd,&c,1);
if(n<0)
std::cerr<<"write error"<<std::endl;
}
bool Wait()//看是否等待成功
{
char c;
int n=read(_fd,&c,1);
if(n>0) return true;
return false;
}
void Close()
{
if(_fd>0)
close(_fd);
}
~FileOper()
{}
private:
std::string _path;
std::string _name;
std::string _fifoname;
int _fd;
};
server.cpp
cpp
#include"Shm.hpp"
#include"fifo.hpp"
int main()
{
Shm shm(pathname,projid,CREATER);
shm.Attr();
char* mem=(char*)shm.VirtualAddr();
umask(0);
Fifo fifo(".","fifo");
FileOper wait(".","fifo");
wait.OpenForRead();//
// sleep(8);
while(true)
{
if(wait.Wait())//可以读了
{
printf("%s\n",mem);
// sleep(1);
}
else
break;
}
// sleep(10);
wait.Close();
return 0;
}
client.cpp
cpp
#include"Shm.hpp"
#include"fifo.hpp"
int main()
{
umask(0);
FileOper writefile(".","fifo");
writefile.OpenForWrite();
Shm shm(pathname,projid,USER);
char* mem=(char*)shm.VirtualAddr();
int cur=0;
for(int i=0;i<26;i++)
{
mem[cur++]='a'+i;
// sleep(1);
mem[cur++]='a'+i;
// sleep(1);
mem[cur]=0;//设置为0
writefile.Wakeup();//唤醒
sleep(2);
}
writefile.Close();
return 0;
}
这篇就到这里啦(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤