初识Linux · 共享内存

news2024/11/15 14:47:52

目录

理解共享内存

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的,用户不应该直接调用,所以为了增加用户的体验,我们应该将这种类型的函数设置为私有的。

    // 获取shmid
    int 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();
        else
            GetUserForUser();
    }

那么我们不妨使用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.coloradmin.cn/o/2240897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【竞技宝】CS2-上海majorRMR:美洲区最后门票争夺战

北京时间2024年11月15日&#xff0c;上海major美洲区RMR正在如火如荼的进行之中。昨日一共进行了三场2-1组的比赛以及三场1-2组的比赛&#xff0c;决出三个正赛参赛名额的同时也确定了今日2-2组的参赛队伍&#xff0c;那么昨日的比赛战果如何呢&#xff1f;接下来小宝就为大家带…

实战:深入探讨 MySQL 和 SQL Server 全文索引的使用及其弊端

在数据库中处理大量文本数据时,包含搜索(例如查找包含特定单词的文本)往往是必需的。然而,直接使用 LIKE %text% 的方式在大数据量中进行模糊查询会造成性能瓶颈。为了解决这一问题,MySQL 和 SQL Server 提供了全文索引(Full-Text Indexing)功能,可以显著加速文本数据的…

蓝桥杯——数组

1、移动数组元素 package day3;import java.util.Arrays;public class Demo1 {public static void main(String[] args) {int[] arr {1,2,3,4,5,6};int k 2;int[] arr_new f(arr,k);for (int i : arr_new) {System.out.print(i",");}//或System.out.println();St…

人体存在感应器设置时间开启感应人存在开灯,失效

环境&#xff1a; 领普人体存在感应器 问题描述&#xff1a; 人体存在感应器设置时间开启感应人存在开灯,失效&#xff0c;设置下午5点&#xff0c;如果有人在5点前一直在这个区域&#xff0c;这个时候到了5点&#xff0c;就触发不了感应自动打开灯光。 解决方案&#xff1a…

常用命令之LinuxOracleHivePython

1. 用户改密 passwd app_adm chage -l app_adm passwd -x 90 app_adm -> 执行操作后&#xff0c;app_adm用户的密码时间改为90天有效期--查看该euser用户过期信息使用chage命令 --chage的参数包括 ---m 密码可更改的最小天数。为零时代表任何时候都可以更改密码。 ---M 密码…

基于yolov8、yolov5的车型检测识别系统(含UI界面、训练好的模型、Python代码、数据集)

摘要&#xff1a;车型识别在交通管理、智能监控和车辆管理中起着至关重要的作用&#xff0c;不仅能帮助相关部门快速识别车辆类型&#xff0c;还为自动化交通监控提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的车型识别模型&#xff0c;该模型使用了…

解决因为TortoiseSVN未安装cmmand line client tools组件,导致idea无法使用svn更新、提交代码

一.错误信息 1.更新代码时&#xff1a;SVN: 更新错误 找不到要更新的版本管理目录。 2.提交代码&#xff1a;检测不到任何更新&#xff08;实际上有代码修改&#xff09;。 3.Cannot run program "svn"。 二.原因分析 在电脑上新安装的的客户端TortoiseSVN、ide…

高效稳定!新加坡服务器托管方案助力企业全球化布局

在全球化的商业环境中&#xff0c;企业对于高效、稳定的服务器托管方案的需求日益迫切。作为亚洲的服务器托管中心&#xff0c;新加坡凭借其独特的地理位置、稳定的政治环境、先进的科技设施以及开放的市场政策&#xff0c;为企业提供了理想的服务器托管解决方案&#xff0c;助…

NVR管理平台EasyNVR多品牌NVR管理工具/设备:为什么IPC白天图像正常,夜视漆黑?

在安防监控系统中&#xff0c;IPC&#xff08;网络摄像机&#xff09;扮演着至关重要的角色。然而&#xff0c;有时用户可能会遇到这样的问题&#xff1a;IPC在白天时图像清晰正常&#xff0c;但到了夜晚却变得漆黑一片&#xff0c;无法看清监控画面。 为什么IPC白天图像正常&a…

安卓aab包的安装教程,附带adb环境的配置

一、ADB环境配置 安装aab包的前提是需要有adb环境&#xff0c;下面先介绍adb环境的配置 ADB通常位于/platform-tools/。 在Windows上&#xff0c;你可以通过以下步骤添加到环境变量&#xff1a; 右键点击“我的电脑”或“此电脑”&#xff0c;选择“属性”。 点击“高级系…

研究生如何远控实验室电脑?远程办公功能使用教程

如果你是研究生&#xff0c;是不是会遇到需要远程控制实验室电脑进行查看文献、调代码和拉数据的时候&#xff1f;有时候就是这么棘手&#xff0c;不过你可以借助一些工具来帮助你随时随地远控实验室电脑。这样就不用担心导师催促&#xff0c;无法及时完成科研了。常见的工具比…

计算机视觉和机器人技术中的下一个标记预测与视频扩散相结合

一种新方法可以训练神经网络对损坏的数据进行分类&#xff0c;同时预测下一步操作。 它可以为机器人制定灵活的计划&#xff0c;生成高质量的视频&#xff0c;并帮助人工智能代理导航数字环境。 Diffusion Forcing 方法可以对嘈杂的数据进行分类&#xff0c;并可靠地预测任务的…

云计算研究实训室建设方案

一、引言 随着云计算技术的迅速发展和广泛应用&#xff0c;职业院校面临着培养云计算领域专业人才的迫切需求。本方案旨在构建一个先进的云计算研究实训室&#xff0c;为学生提供一个集理论学习、实践操作、技术研发与创新于一体的综合性学习平台&#xff0c;以促进云计算技术…

React Native 全栈开发实战班 - 核心组件与导航

在 React Native 中&#xff0c;组件是构建用户界面的基本单元。React Native 提供了丰富的内置组件&#xff0c;涵盖了从基础布局到复杂交互的各种需求。本章节将详细介绍常用的内置组件&#xff0c;并重点讲解列表与滚动视图的使用。 1. 常用内置组件详解 React Native 提供…

【2025最新计算机毕业设计】基于SpringBoot+Vue电脑在线装机指南教程网站【源码+文档】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

C语言——段管理

一、复习一下 1.指针的概念&#xff1f; 存储地址的基本数据类型 2.什么是数据类型&#xff1f; 在内存空间上框出一定空间的模子&#xff0c;比如int在内存空间上框出4个字节&#xff0c;int就是基本的数据类型 3.基本数据类型&#xff0c;多个数据类型&#xff0c;多个同…

SpringCloud 微服务消息队列灰度方案 (RocketMQ 4.x)

目录 背景遇到的问题 RocketMQ 基础基础消息模型扩展后的消息模型部署模型相关概念点 方案对比影子Topic的方案Tag的方案UserProperty的方案影子Group的方案灰度分区的方案方案对比 灰度分区方案设计适配只有部分灰度的情况所做的功能扩展消费者&#xff08;无灰度&#xff09;…

YOLOv8改进,YOLOv8结合DynamicConv(动态卷积),CVPR2024,二次创新C2f结构

摘要 大规模视觉预训练显著提高了大规模视觉模型的性能。现有的低 FLOPs 模型无法从大规模预训练中受益。在本文中,作者提出了一种新的设计原则,称为 ParameterNet,旨在通过最小化FLOPs的增加来增加大规模视觉预训练模型中的参数数量。利用 DynamicConv 动态卷积将额外的参…

【C++】在windows下配置一个小巧实用的C/C++调试环境

目录 1.准备环境 2.cgdb 3. gdb-dashboard 4.常用命令 4.1 cgdb命令 4.2 gdb常用命令 虽然在大部分常用的C/C编辑器中&#xff0c;调试功能已经很方便且完善&#xff0c;但是&#xff0c;如果你还需要一个小巧一点&#xff0c;调试信息还完善的调试环境的&#xff0c;可以…

Dolby TrueHD和Dolby Digital Plus (E-AC-3)编码介绍

文章目录 1. Dolby TrueHD特点总结 2. Dolby Digital Plus (E-AC-3)特点总结 Dolby TrueHD 与 Dolby Digital Plus (E-AC-3) 的对比 Dolby TrueHD和Dolby Digital Plus (E-AC-3) 是两种高级的杜比音频编码格式&#xff0c;常用于蓝光影碟、流媒体、影院等高品质音频传输场景。它…