共享内存及共享内存实现进程间通信

个人主页:Lei宝啊 

愿所有美好如期而遇


我们知道,进程是具有独立性的,什么叫进程具有独立性?就是说,每一个进程都有他们自己的虚拟地址空间,而虚拟进程空间里的虚拟地址通过页表映射到不同的物理内存上,进程独立性很大一部分是因为这个原因。

同时我们需要提到的一点是,即使是虚拟地址空间通过页表映射的物理内存,这个物理内存是不属于进程的,他仍然是属于操作系统的,只是进程可以访问这块空间。

所以假设我们有两个进程,进程A和进程B,由进程的独立性也就决定了进程A不能访问进程B的数据,但我们仍然希望可以有一块公共的空间可以使得他们之间可以交换数据,这就是共享内存,由操作系统开辟,通过页表映射到虚拟地址空间的共享区,并且共享内存比较特殊的一点是,他不会破坏进程的独立性,你可能有这样的疑问,既然进程的独立性很大一部分是由进程的虚拟地址空间所决定的,那么两个进程的虚拟地址空间通过页表映射到了同一块物理内存,并且可以互相访问,这难道不是破坏了进程的独立性吗?

并不是这样的,虽然多个进程可以访问同一块物理内存,但它们仍然是通过各自的虚拟地址空间进行访问的,每个进程都有自己的页表,将其虚拟地址映射到共享内存的物理地址。这样,每个进程仍然保持其地址空间的独立性,只是在共享内存区域上有所重叠。



接下来,我们将使用共享内存来实现进程间通信。

首先我们需要创建共享内存,但是涉及到内存的开辟,这种事情只有操作系统才有权限,我们想要做到,就只有使用操作系统提供的系统调用。

创建共享内存---shmget 

int shmget(key_t key, size_t size, int shmflg);

第一个参数---key

既然共享内存可以使进程间通信,那么内存中就可能不止一个共享内存,所以我们需要一个唯一标识共享内存的值,这个key值就类似于文件描述符中的struct file*,他唯一确定一个共享内存。

但是这个值不由操作系统去生成,而是由我们用户通过ftok函数,也就是一个算法计算得来,那么为什么操作系统不去生成一个key,而是非要我们用户去传呢?

首先我们要明白,进程间通信,如果一个进程中的共享内存的key值是由操作系统所决定的,那么另一个想和他通信的进程,如何在内存中那么多的共享内存中找到要通信的进程所创建的共享内存?答案显而易见是不可能的,操作系统他没有办法将这个key传给另一个进程,如果可以将这个key传给另一个进程,那么你都能通信了,我们还要共享内存干什么?所以这个key值只能是由将要进行通信的两个进程共同决定后计算得出。

key_t ftok(const char *pathname, int proj_id);

第二个参数---size

这个参数将决定创建的共享内存的大小,单位为字节,但是我们需要注意的是,共享内存开辟时,将会以4096字节的倍数开辟,如果说我们想要开辟4097个字节的共享内存,那么他会开辟8192字节的内存空间,但是我们只能使用4097,这就类似于reserve和resize。

第三个参数---shmflg

这是两个宏,第一个宏的意思是,创建一个共享内存;如果这个共享内存已经存在,则返回这块空间的标识符。第二个宏不可以单独使用,需要配合第一个宏使用,两者或以后,意为:创建一个共享内存;如果这个共享内存已经存在,则shmget调用失败,返回-1,设置错误码。

返回值

这个函数的返回值是shmid,也就是共享内存标识符,他其实类似于fd。 

连接共享内存---shmat

void *shmat(int shmid, const void *shmaddr, int shmflg);

第一个参数传shmget返回的shmid,第二个参数指定共享内存在进程地址空间中的连接位置,我们默认为nullptr时,操作系统会自动选择一个合适的地址去连接共享内存。第三个参数指定了共享内存的读写权限,我们默认0就可以进行读写。

返回值

返回一个指向共享内存的指针(共享内存在进程虚拟地址空间上的连接位置,即虚拟地址

取消连接共享内存---shmdt

int shmdt(const void *shmaddr);

这个参数我们传的就是shmat的返回值,也就是传共享内存的地址(也就是共享内存在进程虚拟地址空间中的连接位置)

控制共享内存(删除,获取,设置)---shmctl

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

这个函数返回值对于不同的cmd参数有不同,但对于我们将要介绍的cmd参数来说是一样的,就是这样:

删除

设置cmd参数为IPC_RMID,第三个参数我们稍后会介绍,这里的删除操作对他设置为空即可。

获取共享内存属性

所以我们第三个参数是用来获取共享内存属性的,需要配合IPC_STAT这个cmd参数。



实现进程间通信

我们希望两个没有亲缘关系的进程进行通信,所以写一个服务端·,一个客户端。

我们先来看服务端的主逻辑:

我们封装了一个shm类管理共享内存,这个类将实现共享内存的创建,连接,去关联,获取信息,关闭操作,因为每一个操作都需要shmget返回的shmid,所以我们设置一个shmid成员变量,以及连接后返回共享内存地址,我们也需要保存,因为后面还需要去关联,所以我们再设置一个addr成员变量。

reader操作,读取客户端发送的消息。

int main()
{shm shared_memory;shared_memory.create_shm();shared_memory.shm_attach();reader((char*)shared_memory.get_addr());shared_memory.shm_detach();shared_memory.close_shm();return 0;
}
template<class T>
void reader(T* addr)
{SyncLast sync;sync.CreatePipe();sync.ROpen();for(;;){if(!sync.Wait()) break;cout << addr << endl;}
}

接下来是客户端的主逻辑:

这里我们不需要创建共享内存,只需要获得共享内存的shmid即可,但是我们不能直接get_shmid,因为这是客户端,是另一个进程,所以我们只需要IPC_CREAT获取shmid即可。

在客户端,我们不需要释放共享内存,去关联即可。

writer即向服务端发送消息。

int main()
{shm shared_memory;shared_memory.get_shm();shared_memory.shm_attach();writer((char*)shared_memory.get_addr());shared_memory.shm_detach();return 0;
}
template<class T>
void writer(T* addr)
{SyncLast sync;sync.WOpen();memset(addr,0,SIZE);for(int i='A'; i<='Z'; i++){addr[i-'A'] = i;sync.Wake();sleep(1);        }
}

 

在这里要先提到一件事情,共享内存没有同步机制,也就是说,如果我们将服务端跑起来,那么服务端会一直读取消息,即使客户端没有发送,而这点是我们不想看到的,所以我们可以使用信号量来进行实现,博主这里就使用管道简单的实现一下同步就可以了。

接下来我们看实现。

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>using namespace std;#define PATH "/home"
#define PROJ_ID 66
#define SIZE 4096class shm
{
public:shm():_shmid(-1),_addr(nullptr){}key_t getkey(const char* path = PATH, int proj_id = PROJ_ID){   key_t key = ftok(path, proj_id);if(key < 0){cout << "ftok error:" << errno << " reason:" << strerror(errno) << endl;exit(1);}cout << "getkey success:" << key << endl; return key;}void create_shm(int size = SIZE, const char* path = PATH, int proj_id = PROJ_ID){_shmid = shmget(getkey(path,proj_id),size,IPC_CREAT | IPC_EXCL);if(_shmid < 0){cout << "shmget error:" << errno << " reason:" << strerror(errno) << endl;exit(2);}cout << "create_shm success" << endl;}int get_shm(int size = SIZE, const char* path = PATH, int proj_id = PROJ_ID){_shmid = shmget(getkey(path,proj_id),size,IPC_CREAT);if(_shmid < 0){cout << "get_shm error:" << errno << " reason:" << strerror(errno) << endl;exit(3);}    cout << "get_shm success" << endl;return _shmid;}bool shm_attach(){_addr = shmat(_shmid,nullptr,0);if(_addr == (void*)-1){cout << "shmat error:" << errno << " reason:" << strerror(errno) << endl;return false;}cout << "shm_attach success" << endl;return true;}bool shm_detach(){int shmdt_return_val = shmdt(_addr);if(shmdt_return_val == -1){cout << "shmdt error:" << errno << " reason:" << strerror(errno) << endl;return false;}cout << "shm_detach success" << endl;return true;}void close_shm(){int shmctl_return_val = shmctl(_shmid,IPC_RMID,nullptr);if(shmctl_return_val == -1){cout << "close_shm error:" << errno << " reason:" <<strerror(errno) << endl;exit(6);}cout << "close_shm success" << endl;}struct shmid_ds* get_imformation_of_shm(){static struct shmid_ds information;int num = shmctl(_shmid,IPC_STAT,&information);if(num == -1){cout << "get_imformation_of_shm error:" << errno << " reason:" << strerror(errno) << endl;return nullptr;}cout << "get_imformation_of_shm success" << endl;return &information;}int get_shmid(){return _shmid;}void* get_addr(){return _addr;}private:int _shmid;void* _addr;
};#define Mode 0666
#define PIPEPath "./default"class Fifo
{
public:Fifo(string path = PIPEPath, int mode = Mode):_path(path),_mode(mode){}void CreateNamePipe(){int return_mkfifo_val = mkfifo(_path.c_str(),_mode);if(return_mkfifo_val < 0){cout << "mkfifo error:" << errno << " reason :" << strerror(errno) << endl;exit(1);}cout << "mkfifo success" << endl;}~Fifo(){~Fifo(){     int return_unlink_val = unlink(_path.c_str());if(return_unlink_val < 0){cout << "unlink error:" << errno << " reason :" << strerror(errno) << endl;}cout << "unlink namepipe success" << endl;         }}private:string _path;int _mode;};class SyncLast
{
public:SyncLast():wfd(-1),rfd(-1){}void CreatePipe(){_fifo.CreateNamePipe();}void WOpen(){wfd = open(PIPEPath, O_WRONLY);if(wfd < 0){cout << "wfd error" << endl;exit(7);}}void ROpen(){        rfd = open(PIPEPath, O_RDONLY);if(rfd < 0){cout << "rfd error" << endl;exit(8);}    }bool Wait(){int wait = 0;ssize_t val = read(rfd, &wait, sizeof(wait));if(val == sizeof(wait)){cout << "wait success" << endl;}else if(val == 0){cout << "wfd close, read finish->0, exit" << endl;return false;}else{cout << "read error" << endl;return false;}return true;}void Wake(){int wake = 0;ssize_t val = write(wfd, &wake, sizeof(wake));if(val != sizeof(wake)){cout << "write error" << endl;exit(10);}cout << "wake finish" << endl;}~SyncLast(){close(wfd);close(rfd);}
private:Fifo _fifo;int wfd;int rfd;
};

最后,我们要说到的是,共享内存是最快的通信方式,那么他为什么比管道快呢?因为我们可以通过系统调用得到共享内存地址,直接进行访问,而管道就需要从用户层将数据拷贝到内核缓冲区里,再读取到用户层,这样的两次拷贝明显是不如我们直接访问共享内存的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/617186.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电池电量监测系统设计 单片机+LabVIEW+Matlab+Protues+Keil程序

目录 前言 提供 软件 系统展示 1.放电试验及其处理 2.硬件系统原理图 3.下位机程序 4.显示 5.上位机界面 6.上位机程序 7.文档 资料下载地址&#xff1a;电池电量监测系统设计 单片机LabVIEWMatlabProtuesKeil程序 前言 这套系统首先使用Matlab分析获得了电压…

【Java系列】SpringCloudAlibaba统一返回体及全局异常捕获实现

本文将以实际代码展示如何实现SpringCloudAlibaba的统一返回体及全局异常捕获。 作者&#xff1a;后端小肥肠 1. 前言 在构建微服务应用时&#xff0c;统一返回体和异常捕获机制的设计对于保持代码的整洁性和提高服务的可维护性至关重要。特别是在使用 Spring Boot 和 Spring …

DataX案例,MongoDB数据导入HDFS与MySQL

【尚硅谷】Alibaba开源数据同步工具DataX技术教程_哔哩哔哩_bilibili 目录 1、MongoDB 1.1、MongoDB介绍 1.2、MongoDB基本概念解析 1.3、MongoDB中的数据存储结构 1.4、MongoDB启动服务 1.5、MongoDB小案例 2、DataX导入导出案例 2.1、读取MongoDB的数据导入到HDFS 2…

Re65:读论文 GPT-3 Language Models are Few-Shot Learners

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文全名&#xff1a;Language Models are Few-Shot Learners ArXiv网址&#xff1a;https://arxiv.org/abs/2005.14165 2020 NeurIPS&#xff1a;https://papers.nips.cc/paper/2020/hash/1457c0d6bfcb49674…

UVa1313/LA2693 Ghost Busters

UVa1313/LA2693 Ghost Busters 题目链接题意分析AC代码 题目链接 本题是2002年ICPC欧洲区域赛东北欧赛区的G题 题意 有 N ( N ≤ 100 ) N(N≤100) N(N≤100)个鬼&#xff0c;每个鬼是中心在 ( X i , Y i , Z i ) ( 1 ≤ X i , Y i , Z i ≤ 10000 ) (X_i,Y_i,Z_i) (1 ≤ X_i,Y…

Python中的回调函数和C中函数指针什么关系?

你好&#xff0c;我是安然无虞。 Python 回调 在Python中&#xff0c;‘回调函数’ (callback) 是指一个作为参数传递给其它代码的函数。 目的是在后者完成某些操作后调用这个传递进来的函数。 回调允许在执行异步操作或处理事件时通知调用者代码。 回调函数通常用于&#…

【数据结构】-- 单链表 vs 双向链表

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

react query 学习笔记

文章目录 react query 学习笔记查询客户端 QueryClient获取查询客户端 useQueryClient异步重新请求数据 queryClient.fetchQuery /使查询失效 queryClient.invalidateQueries 与 重新请求数据queryClient.refetchQueries 查询 QueriesuseQuery查询配置对象查询的键值 Query Key…

力扣 | 24. 两两交换链表中的节点

两两交换链表中的节点 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 你不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 输入&#xff1a;head 1->2->3->4->5->NULL 输出&#xff1a;2->1-&g…

互联网轻量级框架整合之MyBatis核心组件

在看本篇内容之前&#xff0c;最好先理解一下Hibernate和MyBatis的本质区别&#xff0c;这篇Hibernate和MyBatis使用对比实例做了实际的代码级对比&#xff0c;而MyBatis作为更适合互联网产品的持久层首选必定有必然的原因 MyBatis核心组件 MyBatis能够成为数据持久层首选框&a…

【博客710】victoriametrics数据写入的pull和push模式以及优缺点

victoriametrics数据写入的pull和push模式以及优缺点 example&#xff1a; curl -d ‘{“metric”:{“name”:“foo”,“job”:“node_exporter”},“values”:[0,1,2],“timestamps”:[1549891472010,1549891487724,1549891503438]}’ -X POST ‘http://localhost:8428/api/v1…

:app debug:armeabi-v7a failed to configure C/C++

报错信息 由于刚换电脑不久&#xff0c;新建native c工程时&#xff0c;出现报错如下&#xff1a; :app debug:armeabi-v7a failed to configure C/C null java.lang.NullPointerExceptionat com.android.build.gradle.tasks.CmakeQueryMetadataGenerator.getProcessBuilder(…