【Linux】共享内存

目录

原理

代码


在之前,无论是匿名管道还是命名管道,说到底都是基于文件的通信,也就意味着没有为了通信让OS单独设计一套通信模块代码,而是直接复用内核中文件相关的数据结构、缓冲区、代码来实现通信的,这在一定程度上是设计者偷懒,但是也符合我们软件工程尽可能复用代码的思想。除了文件版的通信外,还有人专门为通信从零设计了一套方案,也就是本地通信方案的代码:System V IPC,这套方案有几个典型代表 :共享内存、消息队列、信号量,但是这种方案由于很多原因已经处于淘汰的边缘了,我们重点需要了解共享内存

原理

首先,由OS在物理内存为我们①开辟一段内存空间,然后②内存空间通过页表映射到地址空间中的共享区,于是,这段共享区和内存空间构成映射,然后,将共享内存的起始虚拟地址返回给上层用户,用户就可以通过虚拟地址经页表转向共享内存中写内容了,那另一个进程也可以映射这段内存空间。我们把这种用地址空间映射进而能让两个进程看到同一块内存空间的过程叫做共享内存

对于上面的原理,我们有几点理解:

  • 上面所说的操作,都是OS做的。 
  • OS提供上面的①②步骤的系统调用,供用户进程A、B来进行调用。
  •  共享内存在系统中可以是存在多份的,供不同对进程同时进行通信。
  • OS注定要对共享内存进行管理,先描述,在组织。共享内存不是简单的一段内存空间,也要有描述并管理共享内存的数据结构和匹配的算法。

我们通过  shmget  系统调用创建共享内存,我们来查看一下这个接口的说明:

其中,第二个参数size表示共享内存shm创建多大。第三个参数shmflg是标志位,通过位图的方式来传参,主要选项是IPC_CREAT和IPC_EXCL。IPC_CREAT:如果要创建的共享内存不存在,创建之,如果存在,获取该共享内存并返回,意味着总能获取一个shm;IPC_EXCL:单独使用无意义,只有和IPC_CREAT组合才有意义;IPC_CREAT|IPC_EXCL:如果要创建的共享内存不存在,创建之,如果存在,出错返回 ---- 这个选项组合的意义在于,如果成功返回了,就意味这个shm是全新的。

函数的返回值是共享内存的id。

上面说了这么多,有一个问题是,我(进程)怎么知道OS内共享内存是否存在呢?我们已经知道,共享内存是要有描述自己的数据结构的,而我们必须区分清楚某个共享内存是不是存在,那么我们可以大胆预言一下,共享内存的属性里一定要有标识共享内存唯一性的字段!!所以,我们可以通过共享内存的唯一性标识符判断是不是存在。

为了使另一个进程找到已经创建的共享内存,为此shmget的第一个参数key是由用户形成的,key是多少不重要,只要有唯一性即可!可以通过key来判断这个共享内存是不是存在。 

实际上,OS提供ftok用于生成key。

有了这个调用,这意味我们可以给AB通信的两个进程,使用同样的pathname,同样的proj_id,调用同样的ftok,我们就能形成同样的key了,然后,一个进程创建shm,另一个进程获取shm,就看看到同一个共享内存了。

代码

我们先需要让两个进程得到同一个key,于是我们设计了GetCommonKey函数:

const std::string gpathname = "/home/ghs/linux/linux/lesson22/4.shm";
const int gproj_id = 0x66;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;
}

server和client两个进程都调用这个函数,就得到了相同的key:

我们在运行代码的时候,发现,共享内存不随着进程的结束而自动释放!因为共享内存不属于进程,而属于OS。共享内存会一直存在,直到系统重启。需要我们手动释放(指令/其他系统调用)。也就是共享内存的生命周期随内核,文件的生命周期随进程

我们可以使用指令  ipcs -m  指令查共享内存,

嘿嘿,查到了之前创建的共享内存,然后可以按照 ipcrm -m shmid删除共享内存(而不是按照key删):

到现在,我们可以对比一下  key  和  shmid,

key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理。内核进行区分shm唯一性的。(类似struct file*)

shmid:内核给用户返回的一个标识符,用来进行用户级对shm进行管理的id值。(类似fd)

我们创建一个类Shm,

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 < 0){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;
};

server和client分别创建一个shm对象,

它们找到了同一块共享内存。

那么,我们怎么删除共享内存呢?其实,我们可以使用  shmctl  接口调用,

既然叫*ctl,那么它不仅可以删除shm,还可以做其他的操作(比如获取属性),cmd的选项为IPC_RMID时可以删除shm,因此,我们在析构函数中加入删除shm的操作。

现在,我们已经可以创建好共享内存,接下来就是让共享内存通过页表映射到地址空间(挂接到进程),那共享内存该如何挂接呢?有一个系统调用接口  shma(at-attach) ,还有一个调用时shmdt(dt-detach,去关联):

shmat:其中,shmid就是我们自己申请的共享内存。shmaddr就是我们想挂接到哪个地址上,但是现在我们不考虑,设为null。shmflg就是设置共享内存的访问权限,在这里我们设置为读写,设为0即可。那么其返回值void*是什么呢?其实是地址空间中共享内存的起始地址,这点和malloc很相似,malloc返回堆上开辟空间的起始地址。

shmdt:去挂接,shmaddr为shmat的返回值。 

接下来我们开始挂接共享内存:

void* AttachShm()
{void* shmaddr = shmat(_shmid,nullptr,0);if(shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;
}
void DetachShm(void* addr)
{if(addr == nullptr) return;shmdt(addr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
}

可以看到,server和client依次运行,先挂接后去挂接,共享内存的挂接数由0->1->2->1->0。

到此,别看我们做了很多,但是我们还没有开始通信!!根本原因在于我们是进程间通信,我们之前的工作只能叫通信的准备工作。

现在我们开始通信:

我们的server.cc是:

int main()
{Shm shm(gpathname,gproj_id,gCreater);char* addr = (char*)shm.Addr();while(true){std::cout << "shm memory content: " << addr << std::endl;sleep(1);}return 0;
}

我们的client.cc是:

int main()
{Shm shm(gpathname,gproj_id,gUser);shm.Zero(); char* addr = (char*)shm.Addr();sleep(3);//当成stringchar ch = 'A';while(ch <= 'Z'){addr[ch - 'A'] = ch;ch++;sleep(2);}return 0;
}

我们先运行server,发现server一直在读,

并没有阻塞在这里,然后,运行client,写数据,

这里两秒读一次,我们很奇怪的发现,第一次读完之后,第二次还可以读到一样的数据,这是因为共享内存不提供对共享内存的任何保护机制,这样可能导致我还没写完就被读走了,会导致数据不一致问题。其次,我们在访问共享内存的时候,没有用任何系统调用,所以,共享内存是所有进程IPC,速度最快的,因为,共享内存大大减少了数据内存的拷贝次数。那么,怎么对共享内存施加保护呢?当共享内存中没有数据时就不要读,等有数据的时候并且我想让你读的时候你再读!我们之前已经知道,管道是存在同步机制的,所以,server和client除了建立共享内存外,还要建立管道,server在从共享内存中读数据前,先去读管道,管道没数据的话,server就等着。当client写完数据后,通过管道通知server再来读,这不就完成了对共享内存的保护了吗?

在上面,我们设置了共享内存的大小是4096,我们建议其大小是4096*n,

 我们如何获取共享内存的属性呢?需要使用shmctl调用,

 shmctl的第二个参数我们设置成IPC_STAT,就可以获取共享内存的属性信息了。

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;
}

关于共享内存,这里有两点需要着重强调:

  1. 共享内存的key是在用户层面生成的,需要两个进程使用同一个算法生成相应的key。
  2. 两个进程,一个用来创建,一个用来获取,这和命名管道类似。

完整代码见下面:

Shm.hpp

#ifndef __SHM_HPP__
#define __SHM_HPP__#include <iostream>
#include <cstdio>
#include <cerrno>
#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/ghs/linux/linux/lesson22/4.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 < 0){perror("shmget");}return shmid;}std::string RoleToString(int who){if (who == gCreater)return "Creater";else if (who == gUser)return "User";elsereturn "None";}void *AttachShm(){if (_shmaddr != nullptr)DetachShm(_shmaddr);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}void DetachShm(void *addr){if (addr == nullptr)return;shmdt(addr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}public:Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _shmaddr(nullptr){_key = GetCommKey();if (_who == gCreater)GetShmUseCreate();else if (_who == gUser)GetShmForUse();_shmaddr = 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){std::cout << "shm create done..." << std::endl;return true;}}return false;}bool GetShmForUse(){if (_who == gUser){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);if (_shmid >= 0){std::cout << "shm use done..." << std::endl;return true;}}return false;}void *Addr(){return _shmaddr;}void Zero(){if(_shmaddr){memset(_shmaddr,0,gShmSize);}}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;}private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void *_shmaddr;
};#endif

namedPipe.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define Creater 1
#define User 2
#define BaseSize 4096
#define DefaultFd -1
#define Read O_RDONLY
#define Write O_WRONLYconst std::string comm_path = "./myfifo";class NamedPipe
{
private:bool OpenNamedPipe(int mode){_fd = open(_fifo_path.c_str(), mode);if (_fd < 0)return false;return true;}
public:NamedPipe(const std::string &path, int who): _fifo_path(path), _id(who), _fd(DefaultFd){if (who == Creater){int res = mkfifo(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);}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());}~NamedPipe(){if (_id == Creater){//sleep(5);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()
{//1.创建共享内存Shm shm(gpathname,gproj_id,gCreater);char* addr = (char*)shm.Addr();shm.DebugShm();// //2.创建管道// NamedPipe fifo(comm_path, Creater);// fifo.OpenForRead();// while(true)// {//     std::string temp;//     fifo.ReadNamedPipe(&temp);//     std::cout << "shm memory content: " << addr << std::endl;// }return 0;
}

client.cc

#include "Shm.hpp"
#include "namedPipe.hpp"int main()
{//1.创建共享内存    Shm shm(gpathname,gproj_id,gUser);shm.Zero(); char* addr = (char*)shm.Addr();sleep(3);//2.打开管道NamedPipe fifo(comm_path, User);fifo.OpenForWrite();//当成stringchar ch = 'A';while(ch <= 'Z'){addr[ch - 'A'] = ch;std::string temp = "wakeup";std::cout << "add " << ch << " into Shm, " << "wake up reader" << std::endl;fifo.WriteNamedPipe(temp);ch++;sleep(2);}return 0;
}

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

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

相关文章

ET6框架(七)Excel配置工具

文章目录 一、Excel表的基本规则&#xff1a;二、特殊特殊标记三、编译路径说明四、动态获取数据五、可导表类型查看: 一、Excel表的基本规则&#xff1a; 在框架中我们的Excel配置表在ET > Excel文件夹中 1.在表结构中需要注意的是起始点必须在第三行第三列&#xff0c;且…

鸿蒙开发 数组改变,ui渲染没有刷新

问题描述&#xff1a; 数组push, 数组长度改变&#xff0c;ui也没有刷新 打印出了数组 console.log(this.toDoData.map(item > ${item.name}).join(, ), this.toDoData.length) 原代码&#xff1a; Text().fontSize(36).margin({ right: 40 }).onClick(() > {TextPicker…

mysql学习教程,从入门到精通,MySQL介绍(1)

1、MySQL 教程 本教程是为初学者准备的&#xff0c;以帮助他们理解与MySQL语言相关的基础知识和高级概念。 mysql MySQL 是最流行的关系型数据库管理系统&#xff0c;在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System&#xff1a;关系数据库管理系…

如何使用IDEA远程访问家里或者公司中无公网IP的内网MySQL数据库

文章目录 前言1. 本地连接测试2. Windows安装Cpolar3. 配置Mysql公网地址4. IDEA远程连接Mysql5. 固定连接公网地址6. 固定地址连接测试 前言 本教程主要介绍如何使用Cpolar内网穿透工具实现在IDEA中也可以远程访问家里或者公司的数据库&#xff0c;提高开发效率&#xff01;无…

Monibuca实战:如何用Go语言打造高效的直播后端

简介 Monibuca&#xff08;简称&#xff1a;m7s&#xff09; 是一个开源的实时流媒体服务器开发框架&#xff0c;使用 Go 语言编写。 它的设计目标是提供一个高性能、可扩展、易于定制的实时流媒体服务器解决方案。 Monibuca 的核心理念是模块化&#xff0c;允许开发者根据需…

linux服务器/虚拟机安装redis

py3安装&#xff08;慢的一批无语了&#xff09; wget http://cdn.npm.taobao.org/dist/python/3.6.5/Python-3.6.5.tgz && tar -zxvf Python-3.6.5.tgz && cd Python-3.6.5/ && ./configure --prefix/usr/local/python3 --with-ssl && make …

Golang | Leetcode Golang题解之第373题查找和最小的K对数字

题目&#xff1a; 题解&#xff1a; func kSmallestPairs(nums1, nums2 []int, k int) (ans [][]int) {m, n : len(nums1), len(nums2)// 二分查找第 k 小的数对和left, right : nums1[0]nums2[0], nums1[m-1]nums2[n-1]1pairSum : left sort.Search(right-left, func(sum in…

ESP32-IDF http请求崩溃问题分析与解决

文章目录 esp32s3 http请求崩溃问题代码讨论修正后不崩溃的代码esp32相关文章 ESP32S3板子, 一运行http请求百度网站的例子, 就会panic死机, 记录下出现及解决过程. esp32s3 http请求崩溃 一执行http请求的perform就会崩溃, 打印如图 ESP32-IDF 的http请求代码是根据官方dem…

Qt:玩转QPainter序列六(图形)

前言 继续看源码。 正文 剩下的大部分都是画各种图形的函数&#xff0c;它们一般都有多个重载版本&#xff0c;我就不一 一介绍使用了&#xff0c;只挑其中的一部分使用一下。 在 QPainter 类中&#xff0c;这些方法涉及到绘图的各种功能&#xff0c;主要用于设置视图变换、…

kube-scheduler调度任务的执行过程分析与源码解读(二)

概述 摘要&#xff1a; 上文我们对Kube-scheduler的启动流程进行了分析&#xff0c;本文继续探究kube-scheduler执行pod的调度任务的过程。 正文 说明&#xff1a;基于 kubernetes v1.12.0 源码分析 上文讲到kube-scheduler组件通过sched.Run() 启动调度器实例。在sched.Run(…

校园牛奶订购配送小程序开发制作方案

校园牛奶订购配送小程序系统的开发方案&#xff0c;包括对用户需求的分析、目标用户的界定、使用场景的设定以及开发功能模块的规划。校园牛奶订购配送小程序系统主要是为校园内学生和教职工提供牛奶订购与配送服务。 目标用户 主要面向在校学生、教职工以及其他有牛奶订购需求…

这四种人不能合作做生意

合伙创业千万不要和这四种人合伙&#xff0c;不然公司做大了都不是你的&#xff01; 一、不愿出钱的人&#xff0c;不愿出钱就不会有决心。公司一旦有风吹草动&#xff0c;最先跑路的都是没有出钱的。 二、不愿付出时间的人&#xff0c;想用业余时间参与&#xff0c;不愿全身心…

如何使用Svg矢量图封装引用到vue3项目中

前言 在现代前端开发中&#xff0c;SVG&#xff08;可缩放矢量图形&#xff09;因其高质量和灵活性成为了图标和图形设计的热门选择。对于 Vue 3 项目而言&#xff0c;将 SVG 图标封装和引用到项目中不仅能提升性能&#xff0c;还能带来更高的可维护性和一致性。SVG 图标本质上…

卡西莫多的诗文集2022-2024.8月定稿

通过网盘分享的文件&#xff1a;卡西莫多的诗文集2022-2024.8月30-A5.pdf 链接: https://pan.baidu.com/s/1_BrcKvUthFLlty8dWNZxjQ?pwdutwd 提取码: utwd 自从解锁了一项新技能后&#xff0c;从藏内容诗开始&#xff0c;自己积攒到现在不知不觉也积累了一些诗&#xff0c;看…

Runway删库跑路,真的run away了!

没有任何通知&#xff0c;Runway在Hugging Face上的内容全部删除了&#xff01; 目前具体原因不明。Runway的主页只留下了一句话&#xff1a; 我们不再对HuggingFace账号进行维护。 据悉&#xff0c;Runway在Hugging Face上&#xff0c;最火的、也是争议最大的项目&#xff0c;…

【spring】学习笔记1:starter和application

https://spring.io/toolsSpring Boot Extension Pack vs版本 使用Spring Tool Suite初始化Spring项目

Python__面向对象__多态学习

目录 一、多态 1.多态定义理解 2.实例属性和类属性 3.类相关的函数 (1) 实例方法 (2)类方法 (3)静态方法 一、多态 1.多态定义理解 在Python中&#xff0c;多态是一种特性&#xff0c;类似于一个接口&#xff0c;允许在一个对象中的一个操作可作用在不同类型的对象上…

IT管理员的秘密武器:高效管理服务器的远程控制软件

如果你出外勤却紧急需要一份文件&#xff0c;是不是有种热锅上蚂蚁的感觉。这时候如果能远程公司的电脑就能获得马上获得那份心心念念的文件咯。今天我就分享几款轻松好操作的远程控制工具帮你走出困境。 1.向日葵远程控制 链接直通车&#xff1a;https://down.oray.com 这个…

进程间通信----管道篇

目录 一丶 无名管道 1. 特点 2. 读写特性 3. 函数接口 二丶有名管道 1.特点&#xff1a; 2.函数接口 3. 读写特性 一丶 无名管道 1. 特点 1. 只能用于具有亲缘关系的进程之间的通信 2. 半双工的通信模式&#xff0c;具有固定的读端和写端 3. 管道可以…

4G手机智能遥控开关

什么是4G手机智能遥控开关 4G手机智能遥控开关作为现代智能家居与工业自动化的重要组成部分&#xff0c;提供了便捷、高效的远程控制方案。它利用4G通信技术&#xff0c;允许用户随时随地通过智能手机或其他移动设备控制电器设备的开关状态&#xff0c;适用于家庭、办公、工业等…