【Linux—进程间通信】共享内存的原理、创建及使用

什么是共享内存

      共享内存是一种计算机编程中的技术,它允许多个进程访问同一块内存区域,以此作为进程间通信(IPC, Inter-Process Communication)的一种方式。这种方式相对于管道、套接字等通信手段,具有更高的效率,因为数据不需要在用户空间和内核空间之间进行复制,也不需要经过序列化和反序列化的复杂过程。

特点:

  • 高速度:由于省去了数据复制和上下文切换的开销,共享内存提供了非常高的数据交换速度。
  • 低延迟:适用于需要快速响应和大数据量传输的场景。
  • 同步需求:虽然高效,但多个进程同时访问同一块内存可能会导致数据不一致。因此,需要使用如互斥锁、信号量等同步工具来确保数据的正确性和完整性。
  • 生命周期管理:共享内存段需要显式创建、映射到进程地址空间、使用后断开连接,并在不再需要时销毁,以避免资源泄露。
  • 共享内存在系统中可以存在多个,供不同进程之间进行通信

共享内存的原理

每一个进程都有属于自己的进程地址空间,假设操作系统在物理内存开辟了一段空间,该进程可以创建一段虚拟内存,将这段虚拟内存的起始与结束地址通过页表与物理内存的空间构建联系

如果另一个进程,也通过上述方式,通过页表映射到同一段物理内存,那就实现了让多个进程看到同一段空间,这样当一个进程向这段物理空间写入数据,另一个进程就可以马上从这段空间读取数据了,就可以实现进程间的通信了

共享内存的使用

(一)创建共享内存

#原型
 int shmget(key_t key, size_t size, int shmflg);
#参数
 key:用户自定义共享内存的标识
 size:共享内存大小
 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
#返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

【解释】:

# key:由于进程间具有独立性,所以共享内存一定不是某一个进程自己创建的,而是进程通过函数调用让操作系统创建的,而为了使另一个进程可以找到该共享内存,每一个共享内存一定有一个唯一性的标识,但是这个标识一定不能是操作系统自己独立生成的,因为这样只有要创建共享内存的那个进程能找到该共享内存。所以用户可以通过key自己设定唯一的标识,key一般使用函数调用生成

#include <sys/types.h>
#include <sys/ipc.h>

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

pathname为文件路径,proj_id为项目id,这两个都是由用户自己设定的,该函数会通过特定的算法将参数形成唯一的key值

#size:共享内存的大小,建议设置为4096个字节的倍数,例如:当我们将共享内存设置为4097个字节,OS会申请4096*2大小的空间,由于我们申请的是4097个字节,剩下的4095个字节我们不能使用,就会浪费掉

#shmflg: 标志位参数有两种:IPC_CREAT、IPC_EXCL,常用的反方式有两种

  • IPC_CREAT: 如果要创建的共享内存不存在那就创建,如果存在就返回该共享内存
  • IPC_CREAT | IPC_EXCL:如果创建的共享内存不存在那就创建,如果存在就报错
  • 在使用时后面一般还要加上权限,防止进程无法与共享内存联系(注意)

ps:第二种使用方法可以保证每次创建的共享内存都是新创建的,所以在使用上,IPC_CREAT | IPC_EXCL一般用于创建共享内存,IPC_CREAT一般用于获取共享内存

#返回值:共享内存创建成功就返回该共享内存的shmid,失败就返回-1

key和shmid都是标识共享内存的唯一性字段,不过key是用户自定义的,用于让内核区分shm唯一性的,用户不能通过key进行对shm管理,而shmid是有内核返回的一个值,是让用户对共享内存进行管理的id值

(二)删除共享内存

  • 查看所有的共享内存:
ipcs -m

  • 利用指令删除共享内存:
ipcrm -m shmid

  • 代码删除共享内存:

shmctl函数

#功能:用于控制共享内存
#原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
#参数:
 shmid:由shmget返回的共享内存标识码
 cmd:将要采取的动作(有三个可取值)
 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
#返回值:成功返回0;失败返回-1
命令说明
IPC_STAT把shmid_ds数据结构中的数据设置为共享内存当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

ps.删除共享内存关心第三个参数,可以直接把他设置为nullptr

(三)将共享内存连接到进程地址空间

shmat函数

#功能:将共享内存段连接到进程地址空间
#原型
 void *shmat(int shmid, const void *shmaddr, int shmflg);
#参数
 shmid: 共享内存标识
 shmaddr:指定连接的地址
 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
#返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

(四)将共享内存从进程地址空间脱离

#功能:将共享内存段与当前进程脱离
#原型
 int shmdt(const void *shmaddr);
#参数
 shmaddr: 由shmat所返回的指针
#返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段


补充:共享内存不具有进程之间的同步机制,假设一个进程负责读,一个进程负责写,就算共享内存中没有写入数据,读进程还是会一直读,这就可能会发生写进程才写了一半的数据就被读走了,造成数据不一致问题。我们可以利用管道解决这个问题,因为管道具有同步机制,我们让写端写完以后,通过管道传输信号,只有读端通过管道接受到信号以后,才会进行对共享内存的读取

(向管道中写的信号是什么不重要,只要向共享内存中写后,向管道中发送信息,在接收到管道信号后,才读取共享内存的内容,这样就可以让共享内存也存在向管道一样的同步机制,写端写一条,读端读一条)

代码完整使用

shm.hpp

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define Creater 1
#define User 2
const char *pathname = "/home/zyq/mydir/dir/Shm";
int proj_id = 0x666;

class Shm
{
private:
    key_t GetComkey()
    {
        key_t key = ftok(_pathname, proj_id);
        if (key < 0)
        {
            perror("ftok");
            return -1;
        }
        return key;
    }
    int GetShmid(key_t key, int size, int flag)
    {
        int shmid = shmget(_key, size, flag);
        if (shmid < 0)
            perror("shmget");
        return shmid;
    }

    void AttachShm()
    {
        _addrshm = shmat(_shmid, nullptr, 0);
        if (_addrshm == nullptr)
        {
            perror("shmat");
        }
    }

    void DetachShm()
    {
        if (_addrshm != nullptr)
        {
            int n = shmdt(_addrshm);
            if (n < 0)
                perror("shmdt");
        }
    }

public:
    Shm(const char *pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = GetComkey();
        if (who == Creater)
        {
            GetCreatershmid();
        }
        else
        {
            GetUsershmid();
        }
        //将共享内存连接到进程地址空间
        AttachShm();
        std::cout << "_key:" << _key << std::endl;
        std::cout << "_shmid:" << _shmid << std::endl;
    }
    ~Shm()
    {
        //删除进程地址空间
        shmctl(_shmid,IPC_RMID,nullptr);
        //将共享内存脱离进程地址空间
        DetachShm();
    }

    bool GetCreatershmid()
    {
        _shmid = GetShmid(_key, 4096, IPC_CREAT | IPC_EXCL|0666 );
        if (_shmid < 0)
            return false;
        else
        {
            std::cout << "Create shm done!" << std::endl;
            return true;
        }
    }

    bool GetUsershmid()
    {
        _shmid = GetShmid(_key, 4096, IPC_CREAT|0666);
        if (_shmid < 0)
            return false;
        else
        {
            std::cout << "Get shm done!" << std::endl;
            return true;
        }
    }

    void* Addr()
    {
        return _addrshm;
    }
private:
    key_t _key;
    int _shmid;

    const char *_pathname;
    int _proj_id;
    int _who;
    void *_addrshm;
};

server.cc

#include "shm.hpp"
#include "namedpipe.hpp"
int main()
{
    // 创建共享内存并连接
    Shm shm(pathname, proj_id, Creater);
    char *shmaddr = (char *)shm.Addr();
    // 创建管道
    NamedPipe fifo(path, Creater);
    fifo.OpenforRead();

    while (true)
    {
        //读共享内存前先获取唤醒信号
        std::string str;
        fifo.ReadNamedPipe(&str);

        std::cout << "shm content:" << shmaddr << std::endl;
        sleep(1);
    }

    sleep(10);
    return 0;
}

client.cc

#include"shm.hpp"
#include"namedpipe.hpp"
int main()
{
    //获取共享内并连接
    Shm shm(pathname,proj_id,User);
    char* shmaddr=(char*)shm.Addr();
    //获取管道
    NamedPipe fifo(path,User);
    fifo.OpenforWrite();

    char ch='A';
    while(ch<'Z')
    {
        shmaddr[ch-'A']=ch;
        //写完以后,向管道发送唤醒信息
        //向管道中写的内容不重要,主要是利用管道的同步机制
        std::cout<<"add "<<ch<<" into shm"<<std::endl;
        std::string str="WakeupRead";
        fifo.WriteNamedPipe(str);

        ch++;
        sleep(2);
    }
    sleep(10);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文辅助笔记:TimeLLM

1 __init__ 2 forward 3 FlattenHead 4 ReprogrammingLayer

总分420+专业140+哈工大哈尔滨工业大学803信号与系统和数字逻辑电路考研电子信息与通信工程,真题,大纲,参考书。

考研复习一路走来&#xff0c;成绩还是令人满意&#xff0c;专业803信号和数电140&#xff0c;总分420&#xff0c;顺利上岸&#xff0c;总结一下自己这一年复习经历&#xff0c;希望大家可以所有参考&#xff0c;这一年复习跌跌拌拌&#xff0c;有时面对压力也会焦虑&#xff…

【软件设计师】上午题

【软考】软件设计师plus 「软件设计师」 2022年下半年上午真题解析视频 计算机系统知识 22下 考点&#xff1a;指令系统之CISC vs RISC RISC指令系统整体特点是简单、精简 》指令种类少&#xff0c;但是指令功能强 考点&#xff1a;计算机系统组成 A属于运算器&#xff0c;…

第四节课《XTuner作业》

Tutorial/xtuner/personal_assistant_document.md at camp2 InternLM/Tutorial GitHub Tutorial/xtuner/personal_assistant_document.md at camp2 InternLM/Tutorial GitHub GitHub - InternLM/Tutorial at camp2 视频链接&#xff1a;https://b23.tv/BrTSfsl PDF链接&a…

【Delphi 爬虫库 3】使用封装好的 HTML 解析库对 HTML 数据进行解析

文章目录 解析HTML的意义1、简单解析HTML代码2、实战解析HTML代码 解析HTML的意义 HTML是Web页面的构建语言&#xff0c;每个Web开发者都需要了解HTML的基础知识。但是&#xff0c;通过手动阅读和解析需要极大的心智和时间投入。这时候&#xff0c;我们就需要使用HTML在线解析…

Mac 电脑安装 Raptor 流程图软件的方法

0. 安装逻辑 &#xff08;1&#xff09;运行 raptor&#xff0c;本质上需要 mac 能够运行 windows 程序&#xff0c;因此需要安装 .NET Runtime 7.0&#xff0c;这是微软程序运行必须的文件。 &#xff08;2&#xff09;运行 raptor 还需要安装依赖文件 mono-libgdiplus。 &am…

【C++】一篇文章带你熟练掌握<智能指针>及其模拟实现

目录 一、引入 二、智能指针的使用及原理 1、RAII 2、智能指针的原理 3、auto_ptr 4、unique_ptr 5、shared_ptr 6、weak_ptr 一、引入 我们先分析一下为什么需要智能指针&#xff1f; double Division(int a, int b) {// 当b 0时抛出异常if (b 0){throw invalid_a…

Day30:热帖排行、生成长图、将文件上传到云服务器、优化热门帖子列表、压力测试

热帖排行 不同的算分方式&#xff1a; 只存变化的帖子到redis中&#xff0c;每五分钟算一次分&#xff0c;定时任务 存redis 构建redis键 //统计帖子分数 //key:post:score -> value:postId public static String getPostScoreKey() {return PREFIX_POST SPLIT "…

【解决】docker一键部署报错

项目场景见&#xff1a;【记录】Springboot项目集成docker实现一键部署-CSDN博客 问题&#xff1a; 1.docker images 有tag为none的镜像存在。 2.有同事反馈&#xff0c;第一次启动docker-compose up -d 项目无法正常启动。后续正常。 原因&#xff1a; 1.服务中指定了镜像m…

mqtt上行数据传送

{"id": "123","version": "1.0","params": {"wendu": {"value": 25.0},"humi": {"value": 23.6}} } 不要time!!!!!!!!!!!!!!!!!!!!!!!!!!! 下面是官方文档的代码&#xff0c;我用…

自制RAG工具:docx文档读取工具

自制RAG工具&#xff1a;docx文档读取工具 1. 介绍2. 源码2.1 chunk2.2 DocReader 3. 使用方法3.1 文档格式设置3.2 代码使用方法 1. 介绍 在RAG相关的工作中&#xff0c;经常会用到读取docx文档的功能&#xff0c;为了更好地管理文档中的各个分块&#xff0c;以提供更高质量的…

伺服电机初识

目录 一、伺服电机的介绍二、伺服电机的基本原理三、伺服电机的技术特点四、伺服电机的分类五、实际产品介绍1、基本技术规格&#xff1a;2、MD42电机硬件接口3、通讯协议介绍3.1 通讯控制速度运行3.2 通讯控制位置运行3.3 通讯控制转矩运行 4、状态灯与报警信息 一、伺服电机的…

MyScaleDB:SQL+向量驱动大模型和大数据新范式

大模型和 AI 数据库双剑合璧&#xff0c;成为大模型降本增效&#xff0c;大数据真正智能的制胜法宝。 大模型&#xff08;LLM&#xff09;的浪潮已经涌动一年多了&#xff0c;尤其是以 GPT-4、Gemini-1.5、Claude-3 等为代表的模型你方唱罢我登场&#xff0c;成为当之无愧的风口…

富文本编辑器CKEditor4简单使用-07(处理浏览器不支持通过工具栏粘贴问题 和 首行缩进的问题)

富文本编辑器CKEditor4简单使用-07&#xff08;处理浏览器不支持通过工具栏粘贴问题 和 首行缩进的问题&#xff09; 1. 前言——CKEditor4快速入门2. 默认情况下的粘贴2.1 先看控制粘贴的3个按钮2.1.1 工具栏粘贴按钮2.1.2 存在的问题 2.2 不解决按钮问题的情况下2.2.1 使用ct…

Linux——基础IO2

引入 之前在Linux——基础IO(1)中我们讲的都是(进程打开的文件)被打开的文件 那些未被打开的文件呢&#xff1f; 大部分的文件都是没有被打开的文件&#xff0c;这些文件在哪保存&#xff1f;磁盘(SSD) OS要不要管理磁盘上的文件&#xff1f;(如何让OS快速定位一个文件) 要…

设计模式之拦截过滤器模式

想象一下&#xff0c;在你的Java应用里&#xff0c;每个请求就像一场冒险旅程&#xff0c;途中需要经过层层安检和特殊处理。这时候&#xff0c;拦截过滤器模式就化身为你最可靠的特工团队&#xff0c;悄无声息地为每一个请求保驾护航&#xff0c;确保它们安全、高效地到达目的…

Endnote X9 20 21如何把中文引用的et al 换(变)成 等

描述 随着毕业的临近&#xff0c;我在写论文时可能会遇到在引用的中文参考文献中出现“et al”字样。有的学校事比较多&#xff0c;非让改成等等&#xff0c;这就麻烦了。 本身人家endnote都是老美的软件&#xff0c;人家本身就是针对英文文献&#xff0c;你现在让改成等等&a…

JavaScript的操作符运算符

前言&#xff1a; JavaScript的运算符与C/C一致 算数运算符&#xff1a; 算数运算符说明加-减*乘%除/取余 递增递减运算符&#xff1a; 运算符说明递增1-- 递减1 补充&#xff1a; 令a1&#xff0c;b1 运算a b ab12ab22ab--10a--b00 比较(关系)运算符&#xff1a; 运算…

【ChatGPT with Date】使用 ChatGPT 时显示消息时间的插件

文章目录 1. 介绍2. 使用方法2.1 安装 Tampermonkey2.2 安装脚本2.3 使用 3. 配置3.1 时间格式3.2 时间位置3.3 高级配置(1) 生命周期钩子函数(2) 示例 4. 反馈5. 未来计划6. 开源协议7. 供给开发者自定义修改脚本的文档7.1 项目组织架构7.2 定义新的 Component(1) 定义一个新的…

提示找不到msvcr110.dll怎么办,分享多种靠谱的解决方法

当用户在操作计算机时遇到系统提示“找不到msvcr110.dll&#xff0c;无法继续执行代码”这一错误信息&#xff0c;这个问题会导致软件无法启动运行。本文将介绍计算机找不到msvcr110.dll的5种详细的解决方法&#xff0c;帮助读者解决这个问题。 一&#xff0c;关于msvcr110.dll…