初识Linux · 共享内存

目录

理解共享内存

Shared memmory code


理解共享内存

前文介绍的管道方式的通信,本文介绍的是进程通信的另外一种方式,即共享内存。但是这种通信方式的特点是只能本地通信,并且不像管道那样有保护机制,这里是没有的。

我们通过这个图,引出我们今日的话题:

在Linux中,万物皆是文件的概念已经深深的刻入到了我们的大脑里面,在文件系统里面我们介绍了进程,介绍了地址空间,介绍了页表,介绍了物理内存之间的映射关系,知道了代码和数据的地址通过页表,将虚拟地址和物理地址完美联系在了一起,那么物理内存里面是否存在进程间通信需要的空间 -- 共享内存呢?

当然是存在的,其实在动静态库的部分,我们就知道了动态库就是将库的内容加载到了物理内存上,不同间的进程通过页表可以找到对于的库的内容,这在博主看来其实是一种共享内存,可是,共享内存的开辟由谁来做?怎么知道共享内存开辟的空间的地址

上面两个问题对应的操作其实都是由OS来完成的,但是OS是肯定不能自己来完成的,因为OS是要根据用户的需求实施对应的操作,所以这两个操作,OS给我们提供了系统调用,由我们用户来执行即可。

那么新的问题来了:是否存在多个共享内存?如果存在多个共享内存,那么OS是否有必要对共享内存进行管理?如果要实施管理,OS是如何进行管理的?

对于第一个问题,答案是肯定的,因为不只是有AB两个进行需要使用共享内存进行通信,还有CD,还有EF需要使用共享内存进行通信。

对于第二个问题,OS肯定是有必要对共享内存进行管理的,不然内存导致的问题由谁来负责呢?

对于第三个问题,我们直接call back前面的文件部分了,想要对某种对象进行管理,那么使用到的一定是六字真言,先描述,再组织!!!

在Linux源码里面是有共享内存对应的结构体的,这里因为不介绍,所以不放出对应的源码了,肯定就有人说了,怎么又又又是结构体?因为Linux就是C语言写的呀,并且,C语言想要对某个对象管理,结构体不是最好的选择吗?

所以我们得出一个结论,共享内存 = 共享内存的数据 + 共享内存的属性!!

那么我们现在就可以直接进入到了代码部分了。


Shared memmory code

对于共享内存的代码,我们使用的是和命名管道一样的方式,一个客户端,一个服务端,一个hpp文件,我们首先最关心的,就是如何创建共享内存?

也就是第一个问题,使用的系统调用是2号手册的shmget:

对于头文件部分不用解释,对于三个参数部分,一个是key_t类型的key,一个是size,一个是shmflg。

size代表的是开辟的共享内存的大小,对于shmflg,也就是共享内存的标志,我们这里就介绍两个常用的,一个是IPC_CREAT 一个是IPC_EXCL,使用时候我们可以分为IPC_CREAT使用,IPC_EXCL单独使用没有意义,IPC_CREAT | IPC_EXCL使用。

对于第一种模式,IPC_CREAT,代表的是如果创建的共享内存不存在,就创建,如果存在共享内存,就获取该共享内存并返回,说白了就是总能够获取一个共享内存,但是不一定是全新的。

对于第二种模式,IPC_CREAT | IPC_EXCL,代表的是如果创建的共享内存不存在,就创建,如果存在了对应的共享内存,就出错返回,也就是说,这个模式获取到的共享内存一定是全新的。

最后一个参数,key,我们首先思考一个问题,开辟了共享内存之后,进程通过什么方式知道共享内存呢?难道是A进程开辟了这个共享内存,然后打电话给B进程说:喂,我开辟了一个共享内存,地址是0x34381fec。这样肯定是不可以了,因为我们探究的就是进程通信,这还没有通信呢,怎么让他们告知对方呢?

所以获取共享内存标识符的方法是不能让进程生成的,肯定是要让用户自己形成的,所以需要介绍到一个函数为ftok:

我们需要给一串路径,一个id,那么在ftok内部,就可以通过某种算法,实现key的生成。

那么对于函数shmget的返回值的描述是:

返回的值如果成功了,返回的是共享内存的唯一标识符,如果开辟共享内存失败了,返回的就是就是-1。

话不多说,我们先创建一个,并且打印出来看看:


const char *pathname = "/home/lazy/linux/lower_code/shm";
const int proj_id = 0x11;
std::string ToHex(key_t key)
{char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", key);return buffer;
}int main()
{key_t key = ftok(pathname, proj_id);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL);std::cout << "key: " << ToHex(key) << " shmid " << shmid << std::endl;return 0;
}

转换为16进制是为了后面方便观察,我们使用混合模式创建了共享内存:

最开始使用宏只有IPC_CREAT,后面使用了IPC_EXCL,我们会发现前面创建的共享内存还是存在,所以会报错,可是,明明我们的进程已经结束了,为什么共享内存还在呢!!

所以,我们得出一个结论,共享内存的生命周期不随进程终止而终止。那么后面就势必会牵扯到共享内存的回收问题。

我们通过代码系统调用的方式,已经能成功创建了,但是我们想拿出来看看怎么办,我们使用命令行ipcs -m就可以进行查看相关信息了:

其中key是16进程的,所以我们前面会转成16进程的方便观察,shmid是0,owner是lazy,perms权限为0,共享内存的大小是4096,nattch对应的是0,代表的意思是挂接的进程为0,status状态。

那么我们想要删除,使用的命令是ipcrm,这里提问了就,我们使用key删除还是shmid进行删除呢?

当然是shmid了,对于key不过是共享内存的一个标识符,告诉OS可以通过key来找到对应的共享内存,对于shmid,是可以实现用户级别进行管理的一个值,所以我们作为用户,肯定是通过shmid进行管理的:

这样就行了。

说了那么多,我们对共享内存的函数也了解了,我们也是时候应该对它进行一些封装了。

因为我们是用C++语言实现的,所以仍然使用类的方式进行实现:

#ifndef __SHM_HPP__
#define __SHM_HPP__
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>const char* pathname = "/home/lazy/linux/lower_code/shm";
const size_t shm_size = 4096;
const int proc_id = 0x66;class shm
{
public:private:key_t _key;int _shmid;std::string _pathname;int proc_id;};#endif

先将基本的框架搭建好。 

然后首先构造函数部分,因为key _shmid都是用户层面自己提供的,所以我们在构造函数提供。

并且考虑到分为了服务端和用户端,我们就可以新增一个_who,用来表明身份。

    shm(const std::string& pathname, int proc_id, int who):_pathname(pathname),_proc_id(proc_id),_who(who){}

那么获取到id,我们如果在构造函数里面直接写就有点不美观了,我们可以单独封装一个函数出来:

    key_t Getkey(){key_t key = ftok(_pathname.c_str(), _proc_id);if (key < 0){perror("ftok");}return key;}

并且因为这个函数是用来获取key的,用户不应该直接调用,所以为了增加用户的体验,我们应该将这种类型的函数设置为私有的。

    // 获取shmidint GetShmid(key_t key, size_t size, int shmflg){int shmid = shmget(key, size, shmflg);if (shmid < 0){perror("shmget");}return shmid;}// Creater的封装bool GetUserForCreate(){if (_who == Creater){_shmid = GetShmid(_key, shm_size, IPC_CREAT | IPC_EXCL | 0666);if (_shmid > 0)return true;}std::cout << "shm fail create..." << std::endl;return false;}

对于Creater获取shmid,我们仍然是在构造函数里面实现,并且,简单的通过两层封装实现,在IPC_EXCL后面的0666本质上是permission,这里暂时先不用管。

那么对于Creater的函数到这里了,对于user来说,构造函数还没有实现,我们要清楚user使用该类的时候要干什么,好吧,其实也没有什么特别要干的,只是它需要知道shmid罢了。

    // user的封装bool GetUserForUser(){if (_who == User){_shmid = GetShmid(_key, shm_size, IPC_CREAT | IPC_EXCL | 0666);if (_shmid > 0)return true;}std::cout << "shm fail create..." << std::endl;return false;}

就像这样。

较为完整的构造函数就是:

    Shm(const std::string &pathname, int proc_id, int who): _pathname(pathname), _proc_id(proc_id), _who(who){_key = Getkey();if (_who == Creater)GetUserForCreate();elseGetUserForUser();}

那么我们不妨使用server来试试? 

#include "shm.hpp"
#include <iostream>int main()
{//创建共享内存Shm shm(pathname,proc_id,Creater);return 0;
}

那么实验成功了,但是,这里提问:

到现在位置,进程这里是否开始通信呢?

答案是:没有!!!

因为进程之间使用共享内存是要进行挂接的,也就是将共享内存的地址給进程。

那么我们得知道地址吧?

  • shmid:这是由shmget函数返回的共享内存对象的系统标识符。
  • shmaddr:这是一个可选参数,用于指定共享内存区域在进程的虚拟地址空间中的起始地址。如果设置为NULL,则由系统选择地址。
  • shmflg:这是一个标志参数,用于控制连接的行为。例如,它可以指定是否允许共享内存区域在调用进程的地址空间中固定位置,或者是否允许读写访问等

那么为了获得地址,我们在类的私有成员变量里面新增一个_addrshm。

   // 获取共享内存的地址void *AttachShm(){void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}return shmaddr;}

并且在构造函数里面使用一个函数用来初始化_addrshm。

可是当我们不再想使用该内存了,我们就可以使用函数shmdt,将该共享内存空间分离出去,也就是当_addrshm不为空的时候:

    // 获取共享内存的地址void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}return shmaddr;}void DetachShm(void *shmaddr){if(shmaddr == nullptr)return;shmdt(shmaddr);}

这样,地址我们就知道了,可是仍然没有挂接上,挂接使用的函数是shmctl:

其中也有共享内存的结构体信息:

有了地址,就可以通信了。

那么通信只需要server和client端口都获取到共享内存的地址就可以了:

#include "shm.hpp"int main()
{//创建共享内存Shm shm(pathname,proc_id,creater);char* shmaddr = (char*)shm.Addr();while(true){std::cout << "shm memory content: " << shmaddr << std::endl;sleep(1);}return 0;
}
#include "shm.hpp"int main()
{Shm shm(pathname, proc_id, user);shm.Zero();char *shmaddr = (char *)shm.Addr();char ch = 'A';while (ch <= 'Z'){shmaddr[ch - 'A'] = ch;std::cout << "client write " << ch << std::endl;sleep(2);ch++;}return 0;
}

主要操作是shmaddr获取到地址,获取到了地址就可以了,那么为了方便观察,我们使用sleep函数休眠上一秒两秒。

现象就是:

它都不带有任何保护机制的,所以server端是在一直读取,这也就是为什么快了,它不像管道那样约束很多,所以我们可以在共享内存里面引入管道,也就是增加管道机制即可。

 具体实现交给大家了~


感谢阅读!

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

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

相关文章

机器学习day5-随机森林和线性代数1

十 集成学习方法之随机森林 集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。大致可以分为&#xff1a;Bagging&#xff0c;Boosting 和 Stacking 三大类型。 &#xff08;1&#xff09;每次有放回地从训练集中取出 n 个训练样本&…

Essential Cell Biology--Fifth Edition--Chapter one (6)

1.1.4.4 Internal Membranes Create Intracellular Compartments with Different Functions [细胞膜形成具有不同功能的细胞内隔室] 细胞核、线粒体和叶绿体并不是真核细胞中唯一的膜包围细胞器。细胞质中含有大量的[ a profusion of]其他细胞器&#xff0c;这些细胞器被单层膜…

基于VUE实现语音通话:边录边转发送语言消息、 播放pcm 音频

文章目录 引言I 音频协议音频格式:音频协议:II 实现协议创建ws对象初始化边录边转发送语言消息 setupPCM按下通话按钮时开始讲话,松开后停止讲话播放pcm 音频III 第三库recorderplayer调试引言 需求:电台通讯网(电台远程遥控软件-超短波)该系统通过网络、超短波终端等无线…

政务数据治理专栏开搞!

写在前面 忙忙碌碌干了一年政务数据治理的工作&#xff0c;从法人数据到自然人&#xff0c;从交通到地理信息等等&#xff0c;突发想法开一个专栏讲一讲政务数据遇到的问题&#xff0c;以及治理的成效&#xff0c;或许有朋友爱看。 政务数据&#xff0c;又称之为政务数据资源&a…

CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法

已经执行了conda init&#xff0c;但是还是会报错CondaError: Run ‘conda init’ before ‘conda activate’ 原因&#xff1a;权限不够 解决办法&#xff1a;以管理员身份运行cmd&#xff0c;然后进入要操作的文件夹下&#xff0c;重新执行 conda init 和 conda activate 就可…

【全面系统性介绍】虚拟机VM中CentOS 7 安装和网络配置指南

一、CentOS 7下载源 华为源&#xff1a;https://mirrors.huaweicloud.com/centos/7/isos/x86_64/ 阿里云源&#xff1a;centos-vault-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云 百度网盘源&#xff1a;https://pan.baidu.com/s/1MjFPWS2P2pIRMLA2ioDlVg?pwdfudi &…

软考教材重点内容 信息安全工程师 第 4 章 网络安全体系与网络安全模型

4,1 网络安全体系的主要特征: (1)整体性。网络安全体系从全局、长远的角度实现安全保障&#xff0c;网络安全单元按照一定的规则&#xff0c;相互依赖、相互约束、相互作用而形成人机物一体化的网络安全保护方式。 (2)协同性。网络安全体系依赖于多种安全机制&#xff0c;通过各…

让空间计算触手可及,VR手套何以点石成金?

引言 如何让一位母亲与她去世的小女儿“重逢”&#xff1f;韩国MBC电视台《I Met You》节目实现了一个“不可能”心愿。 在空旷的绿幕中&#xff0c;母亲Jang Ji-sung透过VR头显&#xff0c;看到了三年前因白血病去世的女儿Nayeon。当她伸出双手&#xff0c;居然能摸到女儿的…

[Admin] Dashboard Filter for Mix Report Types

Background RevOps team has built a dashboard for sales team to track team members’ performance, but they’re blocked by how to provide a manager view based on sales’ hierarchy. Therefore, they seek for dev team’s help to clear their blocker. From foll…

WPF中如何使用区域导航

1.创建一个Prism框架的项目并设计好数据源 User如下&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace WPF练习17区域导航.Models {public class User{public int UserId { get; …

AR眼镜方案_AR智能眼镜阵列/衍射光波导显示方案

在当今AR智能眼镜的发展中&#xff0c;显示和光学组件成为了技术攻坚的主要领域。由于这些组件的高制造难度和成本&#xff0c;其光学显示模块在整个设备的成本中约占40%。 采用光波导技术的AR眼镜显示方案&#xff0c;核心结构通常由光机、波导和耦合器组成。光机内的微型显示…

一文学会docker中搭建kali

一文学会docker中搭建kali 本文环境&#xff1a;部署好docker的ubuntu系统主机一台 直接pull对应的镜像&#xff1a; docker pull kalilinux/kali-rolling 然后通过端口映射&#xff0c;将本地100端口映射到容器的22端口&#xff0c;就可以ssh了 docker run -it -p 100:22…

git上传文件到远程仓库

git上传项目到远程仓库 1. 生成SSH公钥(ssh-keygen),一直回车即可 2. 将公钥复制下来,粘贴至码云仓库 公钥默认地址: C:\Users\Administrator\.ssh3. 克隆项目到本地(复制SSH地址) 4. 上传文件到刚创建的项目(这里取名为test.py) 5. 上传需要做的几个步骤 (1) git add . 添…

STM32 串口输出调试信息

软硬件信息 CubeMX version 6.12.1Keil uVision V5.41.0.0 注意 串口有多种&#xff1a; TTL232485 串口的相关知识&#xff1a; 01-【HAL库】STM32实现串口打印&#xff08;printf方式) &#xff0c; 内含 TTL 和 232 区别。 我把 232 串口连进 STM32 串口助手收到的信息…

【计算机网络】TCP协议特点3

心跳机制 什么是心跳机制 心跳机制是在计算机系统、网络通信和许多其他技术领域广泛应用的一种机制&#xff0c;用于检测两个实体之间的连接是否仍然活跃&#xff0c;或者设备是否还在正常运行。就是每隔一段时间发送一个固定的消息给服务端&#xff0c;服务端回复一个固定…

STM32单片机CAN总线汽车线路通断检测

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着汽车电子技术的不断发展&#xff0c;车辆通信接口在汽车电子控…

Easyui ComboBox 数据加载完成之后过滤数据

Easyui ComboBox 数据加载完成之后过滤数据 需求 在ComboBox 下拉框中过滤包含"物联网"三个字的选项 现状 期望 实现方式 使用 combobox 的方法在加载时过滤 loadFilter 方式一&#xff1a;HTML中编写 <input id"enterpriseDepartmentCode" name&…

Oracle19C AWR报告分析之Instance Efficiency Percentages (Target 100%)

Oracle19C AWR报告分析之Instance Efficiency Percentages 一、分析数据二、详细分析2.1 Instance Efficiency Percentages (Target 100%)各项指标及其解释2.2 分析和总结 一、分析数据 二、详细分析 在 Oracle AWR (Automatic Workload Repository) 报告中&#xff0c;每个性能…

【因果分析方法】MATLAB计算Liang-Kleeman信息流

【因果分析方法】MATLAB计算Liang-Kleeman信息流 1 Liang-Kleeman信息流2 MATLAB代码2.1 函数代码2.2 案例参考Liang-Kleeman 信息流(Liang-Kleeman Information Flow)是由 Liang 和 Kleeman 提出的基于信息论的因果分析方法。该方法用于量化变量之间的因果关系,通过计算信息…

DB-GPT系列(四):DB-GPT六大基础应用场景part1

一、基础问答 进入DB-GPT后&#xff0c;再在线对话默认的基础功能就是对话功能。这里我们可以和使用通义千问、文心一言等在线大模型类似的方法&#xff0c; 来和DB-GPT进行对话。 但是值得注意的是&#xff0c;DB-GPT的输出结果是在内置提示词基础之上进行的回答&#xff0c…