当前位置: 首页 > news >正文

初识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端是在一直读取,这也就是为什么快了,它不像管道那样约束很多,所以我们可以在共享内存里面引入管道,也就是增加管道机制即可。

 具体实现交给大家了~


感谢阅读!

相关文章:

初识Linux · 共享内存

目录 理解共享内存 Shared memmory code 理解共享内存 前文介绍的管道方式的通信&#xff0c;本文介绍的是进程通信的另外一种方式&#xff0c;即共享内存。但是这种通信方式的特点是只能本地通信&#xff0c;并且不像管道那样有保护机制&#xff0c;这里是没有的。 我们通…...

Illumina测序什么时候会测序到接头序列?

Storage-D: 一个支持实用及个性化 DNA 数据存储的用户友好型平台 iMeta主页&#xff1a;http://www.imeta.science 方法论文 ● 期刊&#xff1a;iMeta&#xff08;IF 23.7&#xff09; ● 原文链接DOI: https://doi.org/10.1002/imt2.168 ● 2024年1月21日&#xff0c;中国…...

Element表格show-overflow-tooltip属性

表格默认情况下若内容过多会折行显示&#xff0c;若需要单行显示可以使用show-overflow-tooltip属性&#xff0c;它接受一个Boolean&#xff0c;为true时多余的内容会在 hover 时以 tooltip 的形式显示出来。 <el-table v-loading"loading" :data"list"…...

蓝桥杯竞赛单片机组备赛【经验帖】

本人获奖情况说明 笔者本人曾参加过两次蓝桥杯电子赛&#xff0c;在第十二届蓝桥杯大赛单片机设计与开发组获得省级一等奖和国家级二等奖&#xff0c;在第十五届嵌入式设计开发组获得省级二等奖。如果跟着本帖的流程备赛&#xff0c;只要认真勤奋&#xff0c;拿个省二绝对没问…...

解密复杂系统:理论、模型与案例(3)

第五章&#xff1a;复杂系统的应用案例 复杂系统理论在多个领域中展现出其独特的分析能力和广泛的应用前景。本章将详细探讨复杂系统在生态系统、经济与金融系统、社会网络以及生物系统中的具体应用&#xff0c;通过丰富的案例分析&#xff0c;揭示复杂系统理论在实际问题解决…...

<项目代码>YOLOv8 番茄识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…...

docker安装到D盘

双击安装docker默认是安装在c盘&#xff0c;并且安装时我们没法选择位置&#xff0c;如果我们要安装在其他盘可以通过命令行安装 1、下载docker https://docs.docker.com/desktop/setup/install/windows-install/ Docker Desktop 可以使用 WSL 和 Hyper-V任意一种架构&#xf…...

【Java语言】String类

在C语言中字符串用字符可以表示&#xff0c;可在Java中有单独的类来表示字符串&#xff08;就是String&#xff09;&#xff0c;现在我来介绍介绍String类。 字符串构造 一般字符串都是直接赋值构造的&#xff0c;像这样&#xff1a; 还可以这样构造&#xff1a; 图更能直观的…...

【go从零单排】Directories、Temporary Files and Directories目录和临时目录、临时文件

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在 Go 语言中&#xff0c;path/filepath 包提供了一组用于处理文件路径的函数&am…...

Diff 算法的误判

起源&#xff1a; for循环的:key的值使用index绑定&#xff0c;当循环列表条目变化更新&#xff0c;导致虚拟 DOM Diff 算法认为原有项被替换&#xff0c;而不是更新。 // vue2写法 错误例子 <template><div><button click"addItem">添加项目<…...

odoo 17 后端路由接口认证自定义

odoo 17 后端路由接口认证自定义 在接口中, 我们都知道有3中常用的认证方式 user 用户级认证public 访问时赋予公共用户none 不做任何用户级处理 一般不做数据库重要数据校验, 仅做访问处理 以上是源码提供的三种方式 接下来我们自定义一个认证方式 首先找到的这认证是在…...

租赁回收系统小程序

1.需求分析&#xff1a;首先&#xff0c;需要明确系统的功能和特点。这包括确定租赁回收的物品类型、用户群体、业务流程等。通过需求分析&#xff0c;可以确保系统能够满足市场和用户的需求。 2.系统设计&#xff1a;在需求分析的基础上&#xff0c;进行系统的整体设计。这包…...

SQL 注入详解:原理、危害与防范措施

文章目录 一、什么是SQL注入&#xff1f;二、SQL注入的工作原理三、SQL注入的危害1. 数据泄露2. 数据篡改3. 拒绝服务4. 权限提升 四、SQL注入的类型1. 基于错误的信息泄露2. 联合查询注入3. 盲注(1). 基于布尔响应的盲注(2). 基于时间延迟的盲注 4. 基于带外的注入 五、防范SQ…...

如何用Java爬虫“采集”商品订单详情的编程旅程

在这个数据驱动的世界里&#xff0c;如果你不是数据&#xff0c;那么你一定是在收集数据。就像蜜蜂采集花粉一样&#xff0c;我们程序员也需要采集数据&#xff0c;以便分析、优化和做出明智的决策。今天&#xff0c;我们就来聊聊如何使用Java编写一个爬虫&#xff0c;这个爬虫…...

《FreeRTOS任务基础知识篇》

FreeRTOS任务基础知识 1. 什么是多任务系统&#xff1f;2. FreeRTOS任务3. 任务状态3.1 运行态3.2 就绪态3.3 阻塞态3.4 挂起态 4. 任务优先级5. 任务的实现6. 任务控制块7. 任务堆栈 FreeRTOS的核心是任务管理&#xff0c;以下介绍FreeRTOS任务的一些基础知识。 1. 什么是多任…...

前端面试笔试(二)

目录 一、数据结构算法等综合篇 1.HTTP/2、ETag有关 二、代码输出篇 1.new URL&#xff0c;url中的hostname&#xff0c;pathname&#xff0c;href 扩展说一下url的组成部分和属性 URL的组成部分 urlInfo 对象的属性 2.一个递归的输出例子 3.数组去重的不普通方法1 4.数…...

基于Python 和 pyecharts 制作招聘数据可视化分析大屏

在本教程中&#xff0c;我们将展示如何使用 Python 和 pyecharts 库&#xff0c;通过对招聘数据的分析&#xff0c;制作一个交互式的招聘数据分析大屏。此大屏将通过不同类型的图表&#xff08;如柱状图、饼图、词云图等&#xff09;展示招聘行业、职位要求、薪资分布等信息。 …...

探索光耦:晶体管光耦——智能家居的隐形桥梁,让未来生活更智能

在这个日新月异的科技时代&#xff0c;智能家居正以前所未有的速度融入我们的日常生活&#xff0c;从智能灯光到温控系统&#xff0c;从安防监控到语音助手&#xff0c;每一处细节都透露着科技的温度与智慧。而在这场智能化浪潮中&#xff0c;一个看似不起眼却至关重要的组件—…...

三、模板与配置(上)

三、模板与配置 1、WXML模板语法-数据、属性绑定 讲解&#xff1a; 1-1、数据绑定的基本原则 在data中定义数据 Page({data: {//这里是你需要定义的数据} })在WXML中使用数据 {{ 你定义的数据 }}1-2、在data中定义页面的数据 在页面对应的.js文件中&#xff0c;把数据定…...

基于SpringBoot和Vue的公司文档管理系统设计与开发(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...