📟作者主页:慢热的陕西人
🌴专栏链接:Linux
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
本博客主要内容讲解共享内存原理和相关接口的介绍,以及一个案例的展示
文章目录
- system V共享内存
- 1.共享内存的原理
- 2.直接写代码--编写代码进行原理介绍
- 2.1shmget接口的介绍
- 2.2key值为什么需要用ftok生成
- 2.3ftok接口
- 2.3三个命令
- 2.4shmat和shmdt
- 3.通信测试
- 4.代码
system V共享内存
共享内存属于是,操作系统单独为我们设计的进程间通信方式,最后慢慢演变成为了systemV的一种标准。它是属于文件系统之外的,但是它属于专门为了通信而设计的内核模块,我们称为systemV的IPC通信机制。
但是进程是具有独立性的,所以我们要实现进程间的通信,首先第一点就是要做到两个进程看到同一份资源。
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
1.共享内存的原理
首先我们用一个例子来介绍:首先存在两个进程,我们画出对应的地址空间和页表。首先和我们之前的知识一样,进程内部的PCB内部有一个指针指向其对应的内存空间,然后地址空间和物理内存的映射关系存储在每个进程对应的页表中,唯独不同的是这时候页表映射到物理内存空间上却是同一份空间并且这个空间就是我们所谓的共享内存。
宏观控制共享内存的三个步骤:
- ①创建
- ②关联进程和取消关联
- ③释放共享内存
2.直接写代码–编写代码进行原理介绍
2.1shmget接口的介绍
作用:用于申请system V的共享内存空间
参数key :这个参数由我们的另一个接口生成:
ftok
:关于这个接口后面会介绍参数size :申请共享内存空间的大小
参数 shmflg:
这里我们介绍其中的两个:
a. IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建,如果已经存在,获取已经存在的共享内存并返回。
b. IPC_EXCL:不能单独使用,一般要配合IPC_CREAT来使用,含义是创建一个共享内存,如果共享内存不存在,就创建,如果已经存在,则立马出错返回 – 如果创建成功,对应的共享内存一定是最新的共享内存!
返回值:成功返回对应的共享内存标识符,失败的话返回-1,并且errno被设置。
2.2key值为什么需要用ftok生成
首先系统中可以用shm通信,是不是只能有一对进程使用共享内存呢?可以!->所以,在任何一个时刻,可能有多个共享内存在被用来进行通信->所以系统中一定会存在很多shm同时存在->OS要不要整体管理所有的共享内存呢?要!-> OS如何管理多个shm内存呢?先描述,再组织。所以共享内存,不是我们想象的那样,只要在内存中开辟空间即可,系统也要为了关了shm,构建对应的描述共享内存的结构体对象!!
共享内存 = 共享内存的内核数据结构(伪代码:struct shm) + 真正开辟的内存空间。
2.3ftok接口
- 参数pathname:路径字符串
- 参数proj_id:项目id
在操作系统内部就是将一块块共享内存通过这种方式组织起来的,那么进程调用ftok通过pathname和proj_id来生成对应的key(也就是申请对应的共享内存空间),那么我们的进程B就可以通过对应的key来寻找对应的共享内存空间。从而实现进程间通信的本质:两个进程看到同一块资源,所以这个key本质是在内核中使用的。
2.3三个命令
-
ipcs -m
查看系统中存在的共享内存:
-
ipcrm -m shmid
删除shmid所对应的共享内存,共享内存的声明周期不随进程,随OS。。
-
shmctl
这是一个函数接口:用来查看共享内存的一些相关属性,前提是我们执行这个进程的用户具有查看当前共享内存属性的权限,否则无法查看,一般sudo运行即可。或者创建时权限修改为066即可.
代码:
struct shmid_ds ds; int n = shmctl(shmid, IPC_STAT, &ds); if(n == -1) { cerr << errno << ":" << strerror(errno) << endl; exit(3); } cout << "prem:" << toHex(ds.shm_perm.__key) << endl; cout << "creater pid :" << ds.shm_cpid << ":" << getpid() << endl;
运行效果:
2.4shmat和shmdt
这两个接口相关的是nttach参数,他表示的有多少个进程和当前的共享内存挂载的。
- shmat
用于进程和共享内存产生关联,非常类似于malloc,返回一个void指针,一般我们把这个指针强制转换成char*;
它内部也有一些参数,值得我们去学习和研究:
第一个参数我们已经很熟悉了共享内存描述符是个整数。
shmaddr:
文档中的大概含义是,假如我们传入的是nullptr,那么操作系统就会自动帮我们分配一个未被使用过的合适的地址去链接这份共享内存
如果shmaddr不为空,并且shmflg中指定了SHM_RND,则连接发生在地址上,并且该地址等于shmaddr,并四舍五入到最接近SHMLBA的倍数。否则shmadr必须是一个与页面对齐的地址,在这个地址上发生附加操作。
shmflg中的一些:
比如:SHM_RDONLY
-
shmdt
用于进程和对应的共享内存取消关联,相当于我们c语言当时学习的动态内存管理部分的free函数。
3.通信测试
那么好了总体来说我们要做四步:我们直接把这四步封装成一个类Init;
①获取key
②通过k获取shmid
③链接共享内存
④断开共享内存
#define SERVER 0x1
#define CLIENT 0x2
class Init
{
//构造函数
public:
Init(int T) :type(T)
{
//1.创建key
key_t k = getKey();
//2.获取shmid
if(type == SERVER) shmid = creatShm(k, gsize);
else shmid = getShm(k, gsize);
//3.链接共享内存
start = attachShm(shmid);
}
~Init()
{
//1.切断共享内存
dettachShm(start);
//2.释放共享内存
if(type == SERVER) delShm(shmid);
}
char* getstart()
{
return start;
}
private:
char* start; //共享内存的地址
int type; //辨别对象是server还是client
int shmid; //共享内存标识符
};
运行结果:
4.代码
server.cc
#include"comm.hpp"
int main()
{
Init server = Init(SERVER);
//通信
char* start = server.getstart();
//输出从共享内存中读取到的信息
int n = 0;
while(n <= 30)
{
cout << "client->server #" << start << endl;
sleep(1);
n++;
}
//拓展内容
//1.client写完了,才通知让server读取,刚开始,一定先让client运行,一个管道
//2.命名管道带进来
//3.client写完了,才通知让server读取,读取完了,才让client进行写入,两个管道
return 0;
}
client.cc
#include"comm.hpp"
int main()
{
Init client = Init(CLIENT);
//通信
char* start = client.getstart();
char c = 'A';
while(c <= 'Z')
{
start[c - 'A'] = c;
c++;
start[c - 'A'] = '\0';
sleep(1);
}
return 0;
}
comm.cpp
#ifndef COMM_HPP
#define COMM_HPP
#include<iostream>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cerrno>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<cassert>
#include<sys/types.h>
using namespace std;
//路径名称
#define PATHNAME "."
//项目id
#define PROJID 0x6666
//共享内存大小
#define gsize 4096
//获取key
key_t getKey()
{
//用ftok函数获取key
key_t k = ftok(PATHNAME, PROJID);
if(k == -1)
{
cerr << errno << ":" << strerror(errno) << endl;
exit(1);
}
return k;
}
string toHex(key_t k)
{
char buffer[64];
snprintf(buffer, sizeof(buffer),"0x%x", k);
return buffer;
}
static int creatShmHeaper(key_t k, int size, int flag)
{
int shmid = shmget(k, size, flag);
if(shmid == -1)
{
cerr << errno << ":" << strerror(errno) << endl;
exit(2);
}
return shmid;
}
//创建共享内存
int creatShm(key_t k, size_t size)
{
umask(0);
return creatShmHeaper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}
//获取共享内存
int getShm(key_t k, size_t size)
{
return creatShmHeaper(k, size, IPC_CREAT);
}
//删除shmid
void delShm(int shmid)
{
int n = shmctl(shmid, IPC_RMID, nullptr);
assert(n != -1);
(void)n;
}
char* attachShm(int shmid)
{
char* start = (char*)shmat(shmid, nullptr, 0);
return start;
}
void dettachShm(char* start)
{
int n = shmdt(start);
assert(n != -1);
(void)n;
}
#define SERVER 0x1
#define CLIENT 0x2
class Init
{
//构造函数
public:
Init(int T) :type(T)
{
//1.创建key
key_t k = getKey();
//2.获取shmid
if(type == SERVER) shmid = creatShm(k, gsize);
else shmid = getShm(k, gsize);
//3.链接共享内存
start = attachShm(shmid);
}
~Init()
{
//1.切断共享内存
dettachShm(start);
//2.释放共享内存
if(type == SERVER) delShm(shmid);
}
char* getstart()
{
return start;
}
private:
char* start; //共享内存的地址
int type; //辨别对象是server还是client
int shmid; //共享内存标识符
};
#endif
到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正