文章目录
- 概述
- 共享内存基本原理
- 共享内存的操作
- 创建共享内存函数接口
- 形成key--fotk
- 创建共享内存代码演示
- 补充指令集--ipc的指令
- key和shmid区别
- 创建并获取共享内存代码
- 删除共享内存函数接口
- 删除共存内存函数代码演示
- 共享内存段连接到进程地址空间函数接口
- 代码演示
- 取消关联
- 代码演示
- 共享内存优缺点
- 对共享内存进行保护
- 获取共享内存属性
- 关于共享内存的完整代码
概述
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
本地通信方案:system V IPC:
- 共享内存
- 消息队列
- 信号量
共享内存基本原理
每一个进程有自己的地址空间,经过页表转化,找到物理内存,由于进程具有独立性,每个进程有自己的代码和数据,内核数据结构独立。
如何实现共享内存呢?
假设目前有两个进程,进程A和进程B。
首先由操作系统在物理内存中开辟一段内存空间,共享内存虽然是操作系统创建的,但是这些进程中的某一个需要来创建这个共享内存,这个共享内存属于操作系统的。
其次,将这个内存空间经过页表,映射到进程A的共享区,在共享区申请一段空间,然后将起始虚拟地址返回给用户,进程就可以通过地址和页表直接向共享内存中写内容。进程B也可以执行操作。
我们将这种用地址空间进行映射让进程A和进程B可以看到同一段共享内存,称之为共享内存。
上述操作都是操作系统来完成,操作系统可以提供上述的系统调用,让进程A和进程B进行调用。此时如果进程C、进程D、进程E、进程F等进程也需要通信,但是不使用进程和进程B的共享内存,因此共享内存在系统中可以同时存在多份,让不同个数、不同进程进行通信。由此,操作系统就要对共享内存进程管理(先描述,再组织),共享内存不是简单的一段内存空间,也要有描述并管理共享内存的数据结构和匹配算法。简单来说,对共享内存的管理,就变成了对链表的增删查改。
总结一下:
共享内存=内存空间(数据)+共享内存的属性
共享内存的操作
创建共享内存函数接口
创建共享内存函数接口为:shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
- key:这个共享内存段名字,具有唯一性,使用户设置的
- size:共享内存大小
- shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
标志位IPC_CREAT
:如果创建的共享内存不存在,那就创建它;如果存在获取该共享内存并返回,意味着总能获取到一个。
标志位IPC_EXCL
:单独使用没有意义,一般和IPC_CREAT
组合才有意义。
标志位IPC_EXCL | IPC_CREAT
:如果创建的共享内存不存在,就创建该共享内存;如果存在,出错返回。也就是说,成功返回了,那么创建的共享内存就是全新的。
进程如何知道该共享内存存不存在?
共享内存有自己对应的属性,这个属性有一个标识共享内存唯一性的字段,因此对应的共享内存存不存在,可以看对应的唯一性标识符。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
形成key–fotk
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
-
pathname
:是一个指向包含路径名的字符串的指针,通常是一个现有的文件的路径。这个文件在使用ftok
函数时不会被打开或读取,只是用于生成唯一的键值。 -
proj_id
:是一个用户定义的整数,通常是0到255之间的数字。它用来区分同一个路径名下不同IPC对象的不同标识符。
返回值: ftok
函数返回一个key_t
类型的键值,如果发生错误则返回 -1
,并设置 errno
来指示错误的原因。
创建共享内存代码演示
//Shm.hpp
#ifndef __SHM_HPP__
#define __SHM_HPP__
#include<iostream>
#include<string>
#include<cerrno>
#include<cstdio>
#include<sys/ipc.h>
#include<sys/shm.h>
const std::string gpathname="/home/gwj/111/lesson22_shm";
const int gproj_id=0x66;
std::string ToHex(key_t k)
{
char buffer[128];
snprintf(buffer,sizeof(buffer),"0x%x",k);
return buffer;
}
key_t GetCommKey(const std::string &pathname,int proj_id)
{
key_t k=ftok(pathname.c_str(),proj_id);
if(k<0)
{
perror("ftok");
}
return k;
}
int ShbGet(key_t key,int size)
{
int shmid=shmget(key,size,IPC_CREAT|IPC_EXCL);
if(shmid)
{
perror("shmget");
}
return shmid;
}
#endif
//client.cc
#include"Shm.hpp"
int main()
{
key_t key=GetCommKey(gpathname,gproj_id);
std::cout<<"key: "<<ToHex(key)<<std::endl;
return 0;
}
//server.cc
#include"Shm.hpp"
int main()
{
key_t key=GetCommKey(gpathname,gproj_id);
std::cout<<"key: "<<ToHex(key)<<std::endl;
int shmid=ShbGet(key,4096);
std::cout<<"shmid: "<<shmid<<std::endl;
return 0;
}
运行结果:
当我们再次运行该程序时,shmid会报错,是因为使用了标志位IPC_EXCL | IPC_CREAT
当我们删除该程序,重新生成,再次运行,shmid依然报错,是因为共享内存不随着进程的结束而释放内存,一直存在,直到系统重启,因此我们需要通过手动释放(指令或者其他系统调用),共享内存的周期随内核,文件是生命周期随进程。
补充指令集–ipc的指令
查看进程的共享内存指令:ipcs -m
删除创建的共享内存:ipcrm -m shmid
key和shmid区别
key属于用户形成,是内核使用的一个字段,用户不能使用key来进行shm的管理,内核进行区分shm的唯一性。
shmid:是内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值(fd)。
因此在适应指令对共享内存操作时,使用的是shmid。
创建并获取共享内存代码
//Shm.hpp
#ifndef __SHM_HPP__
#define __SHM_HPP__
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/gwj/111/lesson22_shm";
const int gproj_id = 0x66;
const int gShmSize = 4096;
class Shm
{
private:
key_t GetCommKey()
{
key_t k = ftok(_pathname.c_str(), _proj_id);
if (k < 0)
{
perror("ftok");
}
return k;
}
int GetShmHelper(key_t key, int size, int flag)
{
int shmid = shmget(key, size, flag);
if (shmid)
{
perror("shmget");
}
return shmid;
}
public:
Shm(const std::string &pathname, int proj_id, int who)
: _pathname(pathname), _proj_id(proj_id), _who(who)
{
_key = GetCommKey();
if(_who==gCreater) GetShmUseCreate();
else if(_who==gUser) GetShmForUse();
std::cout<<"shmid: "<<_shmid<<std::endl;
std::cout<<"_key: "<<ToHex(_key)<<std::endl;
}
~Shm()
{
}
std::string ToHex(key_t key)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", key);
return buffer;
}
bool GetShmUseCreate()
{
if (_who == gCreater)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL);
if (_shmid >= 0)
return true;
}
return false;
}
bool GetShmForUse()
{
if (_who == gUser)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT);
if (_shmid >= 0)
return true;
}
return false;
}
private:
key_t _key;
int _shmid;
std::string _pathname;
int _proj_id;
int _who;
};
#endif
//client.cc
#include"Shm.hpp"
int main()
{
Shm shm(gpathname,gproj_id,gUser);
return 0;
}
//server.cc
#include"Shm.hpp"
int main()
{
Shm shm(gpathname,gproj_id,gCreater);
return 0;
}
运行结果:
删除共享内存函数接口
删除共享内存函数接口:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
- shmid:由shmget返回的共享内存标识码
- cmd:将要采取的动作(有三个可取值)
- buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
删除共存内存函数代码演示
删除共享内存的函数接口写在上述析构函数中:
~Shm()
{
if(_who==gCreater)
{
int res=shmctl(_shmid,IPC_RMID,nullptr);
}
std::cout<<"shm remove done..."<<std::endl;
}
运行结果:
共享内存段连接到进程地址空间函数接口
共享内存段连接到进程地址空间函数为shmat
:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg); //关联
参数:
- shmid: 共享内存标识
- shmaddr:指定连接的地址
- shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:失败返回0,成功返回地址空间中共享内存的起始地址。
代码演示
void *AttachShm()
{
void *shmaddr=shmat(_shmid,nullptr,0);
if(shmaddr==nullptr)
{
perror("shmat");
}
std::cout<<"who: "<<RoleToString(_who)<<"attch shm..."<<std::endl;
return shmaddr;
}
运行结果演示:
注意: perms
是共享内存的权限,需要修改成666
或者664
。
取消关联
取消函数关联的函数为:
int shmdt(const void *shmaddr); //取消关联
代码演示
void DetachShm(void *shmaddr)
{
if(shmaddr==nullptr) return;
shmdt(shmaddr);
std::cout<<"who: "<<RoleToString(_who)<<"detcah shm..."<<std::endl;
}
共享内存优缺点
共享内存不提供对共享内存的任何保护机制,双方进程不会出现等待进程的现象,会造成数据不一致问题。例如下面的例子,客户端不写入任何内容,但是服务端一直在读入
:
在访问共享内存时没有使用任何系统调用,共享区是所有进程IPC中速度最快的,因为共享内存大大减少数据拷贝次数。
对共享内存进行保护
虽然共享内存不存在保护机制,但是我们可以=使用管道,管道有自我保护机制,于是可以在创建共享内存时把管道也建立好。服务器在读数据之前,都需要去读管道。
//client.cc
#include"Shm.hpp"
#include"namedPipe.hpp"
int main()
{
//创建共享内存
Shm shm(gpathname,gproj_id,gUser);
shm.Zero();
char *shmaddr=(char*)shm.Addr();
sleep(3);
//打开管道
NamePiped fifo(comm_path,User);
fifo.OpenForWrite();
//当成string进行通信
char ch='A';
while(ch<='Z')
{
shmaddr[ch-'A']=ch;
ch++;
std::string temp="weakup";
fifo.WriteNamedPipe(temp);
sleep(2);
}
return 0;
}
//server.cc
#include"Shm.hpp"
#include"namedPipe.hpp"
int main()
{
//创建共享内存
Shm shm(gpathname,gproj_id,gCreater);
char *shmaddr=(char*)shm.Addr();
//创建管道
NamePiped fifo(comm_path,Creater);
fifo.OpenForRead();
while(true)
{
std::string temp;
fifo.ReadNamedPipe(&temp);
std::cout<<"shm memory content: "<<shmaddr<<std::endl;
sleep(1);
}
return 0;
}
当客户端不去写入,服务端就不会读取,只有当客户端开始写入时,才开始读取。
获取共享内存属性
通过 shmctl 函数区获取共享内存的属性。struct shmid_ds 结构体就是用户层面去描述一个共享内存的结构体。
void DebugShm()
{
struct shmid_ds ds;
int n=shmctl(_shmid,IPC_STAT,&ds);
if(n<0) return;
std::cout<<"ds.shm_perm.__key: "<<ToHex(ds.shm_perm.__key)<<std::endl;
std::cout<<"ds.shm_nattch: "<<ds.shm_nattch<<std::endl;
}
关于共享内存的完整代码
//Shm.hpp
#ifndef __SHM_HPP__
#define __SHM_HPP__
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include<cstring>
#include<unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
const int gCreater = 1; //用于标识创建者
const int gUser = 2; //标识使用者
const std::string gpathname = "/home/gwj/111/lesson22_shm";
const int gproj_id = 0x66;
const int gShmSize = 4096;
class Shm
{
private:
key_t GetCommKey()
{
key_t k = ftok(_pathname.c_str(), _proj_id);
if (k < 0)
{
perror("ftok");
}
return k;
}
int GetShmHelper(key_t key, int size, int flag)
{
int shmid = shmget(key, size, flag);
if (shmid)
{
perror("shmget");
}
return shmid;
}
std::string RoleToString(int who)
{
if(who==gCreater) return "Creater";
else if(who==gUser) return "gUser";
else return "None";
}
void *AttachShm()
{
if(_addrshm!=nullptr) DetachShm(_addrshm);
void *shmaddr=shmat(_shmid,nullptr,0);
if(shmaddr==nullptr)
{
perror("shmat");
}
std::cout<<"who: "<<RoleToString(_who)<<"attch shm..."<<std::endl;
return shmaddr;
}
void DetachShm(void *shmaddr)
{
if(shmaddr==nullptr) return;
shmdt(shmaddr);
std::cout<<"who: "<<RoleToString(_who)<<"detcah shm..."<<std::endl;
}
public:
Shm(const std::string &pathname, int proj_id, int who)
: _pathname(pathname), _proj_id(proj_id), _who(who),_addrshm(nullptr)
{
_key = GetCommKey();
if(_who==gCreater) GetShmUseCreate();
else if(_who==gUser) GetShmForUse();
_addrshm=AttachShm();
std::cout<<"shmid: "<<_shmid<<std::endl;
std::cout<<"_key: "<<ToHex(_key)<<std::endl;
}
~Shm()
{
if(_who==gCreater)
{
int res=shmctl(_shmid,IPC_RMID,nullptr);
}
std::cout<<"shm remove done..."<<std::endl;
}
std::string ToHex(key_t key)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", key);
return buffer;
}
bool GetShmUseCreate()
{
if (_who == gCreater)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
if (_shmid >= 0)
return true;
std::cout<<"shm create done..."<<std::endl;
}
return false;
}
bool GetShmForUse()
{
if (_who == gUser)
{
_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
if (_shmid >= 0)
return true;
std::cout<<"shm get done..."<<std::endl;
}
return false;
}
void Zero() //对共享内存进行清空
{
if(_addrshm)
{
memset(_addrshm,0,gShmSize);
}
}
void *Addr()
{
return _addrshm;
}
private:
key_t _key;
int _shmid;
std::string _pathname;
int _proj_id;
int _who;
void *_addrshm;
};
#endif
#pragma once
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096
class NamePiped
{
private:
bool OpenNamedPipe(int mode)
{
_fd = open(_fifo_path.c_str(), mode);
if (_fd < 0)
return false;
return true;
}
public:
NamePiped(const std::string &path, int who)
: _fifo_path(path), _id(who), _fd(DefaultFd)
{
if (_id == Creater)
{
int res = mkfifo(_fifo_path.c_str(), 0666);
if (res != 0)
{
perror("mkfifo");
}
std::cout << "creater create named pipe" << std::endl;
}
}
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
bool OpenForWrite()
{
return OpenNamedPipe(Write);
}
// const &: const std::string &XXX
// * : std::string *
// & : std::string &
int ReadNamedPipe(std::string *out)
{
char buffer[BaseSize];
int n = read(_fd, buffer, sizeof(buffer));
if(n > 0)
{
buffer[n] = 0;
*out = buffer;
}
return n;
}
int WriteNamedPipe(const std::string &in)
{
return write(_fd, in.c_str(), in.size());
}
~NamePiped()
{
if (_id == Creater)
{
int res = unlink(_fifo_path.c_str());
if (res != 0)
{
perror("unlink");
}
std::cout << "creater free named pipe" << std::endl;
}
if(_fd != DefaultFd) close(_fd);
}
private:
const std::string _fifo_path;
int _id;
int _fd;
};
//server.cc
#include"Shm.hpp"
#include"namedPipe.hpp"
int main()
{
//创建共享内存
Shm shm(gpathname,gproj_id,gCreater);
char *shmaddr=(char*)shm.Addr();
//创建管道
NamePiped fifo(comm_path,Creater);
fifo.OpenForRead();
while(true)
{
std::string temp;
fifo.ReadNamedPipe(&temp);
std::cout<<"shm memory content: "<<shmaddr<<std::endl;
sleep(1);
}
return 0;
}
//client.cc
#include"Shm.hpp"
#include"namedPipe.hpp"
int main()
{
//创建共享内存
Shm shm(gpathname,gproj_id,gUser);
shm.Zero();
char *shmaddr=(char*)shm.Addr();
sleep(3);
//打开管道
NamePiped fifo(comm_path,User);
fifo.OpenForWrite();
//当成string进行通信
char ch='A';
while(ch<='Z')
{
shmaddr[ch-'A']=ch;
std::string temp="weakup";
std::cout<<"add "<<ch<<"into Shm, "<<"weakup reader"<<std::endl;
fifo.WriteNamedPipe(temp);
sleep(2);
ch++;
}
return 0;
}