进程间通信--共享内存篇

news2024/11/22 15:02:31

文章目录

  • 共享内存的概念
  • 共享内存使用须知
    • 创建共享内存
    • 共享内存的映射与链接
    • 共享内存的映射取消
    • 共享内存的删除
  • 共享内存实现进程通信
  • 总结

共享内存的概念

共享内存字面理解就是进程间共同享有的存储空间,不同于管道通信,共享内存就像是进程自己的空间一样,不像管道文件还得使用文件描述符去访问文件,通过文件交流信息。共享内存则是实时信息交流,几乎不存在信息的中间转换。那么共享内存在哪里呢?就真的是在每个进程中都保留一份么?也不是,开辟的共享内存在整个内存空间中独一份,但是可以通过页表映射到不同的进程中去,让各个进程都能够看到这份资源,实现通信。下面是结构图式的理解:

image-20221212220923003

这里要注意区别于父子进程的写时拷贝现象,与其相反,共享内存内的数据在被一个进程修改时,其余进程所看到的资源都会是被修改过的。只有这样,进程间的通信才成为可能。

🙋‍♂️:为什么父子进程不像共享内存那样处理呢,舍弃写时拷贝,这样父子进程间不就可以直接通过一些变量进行通信了嘛?

👨‍🏫:理论上在设计时可以这么处理,但是有些场景下我们可能并不需要父子进程间的变量强相关,这会增加使用某些变量的风险。假如一个变量在父进程中作为计数器,在子进程中作为判断条件,那么就会造成严重的逻辑BUG,处理不好整个程序直接玩完。因此为了降低风险,就有了写时拷贝并设计了共享内存这样的结构,只有在需要的时候,用户自己去使用共享内存,就会安全不少。

共享内存使用须知

根据共享内存的概念特性,我们不难发现,共享内存的使用就像是堆上申请的空间一样,可以直接进行访问和修改,那么也就意味着空间的使用不受控制,用户想怎么来就怎么来,进程间容易出现空间操作混乱的情况,因此使用时需要控制一下空间的使用时序。

除了使用要受到控制这个特点之外,貌似也没有什么需要特别注意的地方,整个流程如下:

image-20221212224142102

创建共享内存

image-20221212224451870

创建共享内存需要使用shmget函数,在内存中开辟空间。这里的参数有点含金量,下面阐述一下各个参数的含义。

key:共享内存的唯一标识。共享内存有存在多个的可能情况,因此得各自区分开来,为创建的与创建好的也得区分开,因此唯一标识符的存在就很好理解了。这个唯一标识符并不是系统自动生成的,而是用户自己提供的,为了方便多个进程想准确快速的使用同一个共享内存,又给我们提供了另一个函数ftok函数:

image-20221212225733548

根据已经存在的文件名pathname(目录也是文件!),再加上一个非零的id值(随意,看用户自己创建不同共享内存的区别规则),系统会根据这两个参数生成一个唯一确定的key值。只要pathname和proj_id相同,生成的key值就会相同。这也就保证了不同进程可以根据同一个路径名和id值,访问到同一个共享内存。

size:共享内存的大小,单位是字节数。这也是共享内存的特点之一,内存空间是按字节数来创建与访问的。

shmflg:共享内存开辟时的属性设置,共有三个参数可以传:

image-20221214163752211

IPC_CREAT:创建一块共享内存,如果已经存在了,就获取它,如果不存在,就黄建一块新的共享内存,并获取它。

IPC_EXCL:与IPC_CREAT搭配使用,根据key值,如果该共享内存存在了,就会出错。不存在的话正常创建并获取它,这也就保证了如果创建内存成功,该共享内存一定是一个全新的。

mode_flags:开辟的空间的权限,与open函数中的mode参数是一个意思,对应 所有者、所属组、其它 三个组的权限。例如:0x666就是所有使用者都能进行读和写的操作。

返回值:返回一个整型数字,类似于文件描述符的东西,是提供给我们用户使用的共享内存的标识,注意区别key这个系统调用接口的标识符,虽然是一个意思,但数值不同,面向的对象也不同。

共享内存的映射与链接

一个进程无论创建不创建共享内存,要想使用共享内存,就必须得将要使用的共享内存映射到自己的共享区上,保证在使用的时候可以找到该共享内存的首地址。

image-20221214173243540

映射与链接的话需要使用shmat函数。

参数:

shmid:就是shmget的返回值,也就是用户级的共享内存标识符,这个参数意义就是链接对应的共享内存。

shmaddr:指针参数,如果shmaddr为NULL,系统将选择一个合适的(未使用的)地址来附加段。一般我们在不涉及到特殊情况下默认传空指针就行。

shmflg:默认传0(还没涉及到复杂的场景,这里就先这样用着)。

返回值:共享内存的首地址,类型是void* ,用户可以自己转换成需要的类型。如果出错的话会返回void* ( -1)。

拿到返回值就意味着可以正常使用该共享内存了。

共享内存的映射取消

如果某个进程不想使用某个共享内存了,就可以将其在共享区的映射给删掉。

image-20221215101950956

参数:

shmaddr:shmat的返回值。

返回值:成功时返回0;错误时返回-1,并设置errno以指示错误的原因。

共享内存的删除

共享内存删除意味着所有的进程都不能使用该内存了,注意区分于映射的取消。

image-20221215101843154

参数:

shmid:共享内存的用户级标识符

cmd:一般传IPC_RMID,当最后一个进程取消映射后,直接删除掉共享内存。

buf:默认我们传空指针。

共享内存实现进程通信

这里我们使用客户端于服务端之间的通信来进行测试。

准备工作:两个可执行程序(客户端与服务端)、两个程序共同看到的头文件(方便看见同一个共享内存)。

注意事项:通信的时候需要进行访问控制,这里我使用的是管道的阻塞式读来控制。

头文件Common.hpp:

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


using namespace std;
#define PATH_NAME "/home/jia/learning_code-college-age"//创建共享内存所使用的文件名
#define PROJ_ID 0x46 //创建共享内存的id值
#define SHM_SIZE 4096 //共享内存的大小
#define FIFO_FILE ".fifo"//管道文件的名字
#define READER O_RDONLY//管道文件的打开方式
#define WRITER O_WRONLY//管道文件的打开方式
key_t CreateKey()//创建key值
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        cerr << "ftok: " << strerror(errno) << endl;
        exit(1);
    }
    return key;
}
void CreateFifo(const string fifofile)//创建管道文件
{
    umask(0);
    if(mkfifo(fifofile.c_str(),0666)<0)
    {
        cerr<<"mkfifo: "<<strerror(errno)<<endl;
        exit(2);
    }
}
int Open(const string fifofile, int mode)//以某种方式打开管道文件
{
    return open(fifofile.c_str(),mode);
}

int Wait(int fd)//阻塞式等待
{
    uint32_t values=0;
    return read(fd,&values,sizeof(values));
}

void Signal(int fd)//向文件中写入信息,使得阻塞式等待解除
{
    uint32_t values=1;
    write(fd,&values,sizeof(values));
}

int Close(int fd,const string fifofile)//关闭管道,并删除管道文件
{
    close(fd);
    unlink(fifofile.c_str());
}

服务端IpcShmSer.cc:

#include "Common.hpp"
int main()
{
    cout<<"Ser Begin"<<endl;
    CreateFifo(FIFO_FILE);//创建管道文件
    key_t key = CreateKey();
    cout << "key: " << key << endl;
    int shmid=shmget(key,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);//创建共享内存
    if(shmid<0)
    {
        cerr<<"shmget: "<<strerror(errno)<<endl;
        return 2;
    }
    cout<<"shmget success, shmid: "<<shmid<<endl;
    int fd=Open(FIFO_FILE,READER);//服务端以读的方式打开管道文件
    cout <<"Open Success,fd: "<<fd<<endl;
    char* str=(char*)shmat(shmid,nullptr,0);//建立映射关系,并获取共享内存首地址
    while(true)
    {
        if(Wait(fd)<=0) break;//阻塞式等待,只要客户端没有向管道写入内容,就一直卡在此处,不进行输出操作。
        printf("%s",str);//使用共享内存,这里是直接输出
    }

    shmdt(str);//取消映射

    shmctl(shmid,IPC_RMID,nullptr);//服务端退出的话要删除共享内存
    Close(fd,FIFO_FILE);//关闭管道文件并删除管道文件
    return 0;
}

客户端IpcShmCli.cc:

#include "Common.hpp"
int main()
{
    cout << "Cli Begin" << endl;
    key_t key = CreateKey();
    cout << "key: " << key << endl;
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT);//获取客户端已经创建好的共享内存
    if (shmid < 0)
    {
        cerr << "shmget: " << strerror(errno) << endl;
        return 2;
    }
    cout << "shmget success, shmid: " << shmid << endl;
    int fd = Open(FIFO_FILE, WRITER);//以写的方式打开管道文件
    cout << "Open Success,fd: " << fd << endl;
    char *str = (char *)shmat(shmid, nullptr, 0);//与共享内存建立映射
    while (true)
    {
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, str, SHM_SIZE);//从标准输入流中拿取数据放到str中,也就是向共享内存写入数据
        if (s > 0 && s < SHM_SIZE)
        {
            str[s] = '\0';
        }
        Signal(fd);//给服务端发信号,表示可以使用共享内存了
    }
    shmdt(str);//结束的话取消映射,客户端并不负责共享内存与管道文件的处理。
    return 0;
}

运行结果:

image-20221215123345706

总结

共享内存的使用其实并不是重点,重点在于如何理解共享内存的概念与特性。

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

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

相关文章

磷脂PEG化靶向蛋白肽系列 DSPE-PEG- RGR(CRGRRST)/ TH/ R8/ NGR 为华生物提供

品牌&#xff1a;为华生物 产地&#xff1a;广州 中文名称:磷脂-聚乙二醇-肿瘤靶向蛋白 肿瘤靶向蛋白-聚乙二醇-磷脂 英文名称: DSPE-PEG- RGR&#xff08;CRGRRST&#xff09; PEG分子量400、600、1k、2k、3.4k、5k、10k其他分子量可定制 分子量&#xff1a;根据客户需求定制…

002.爬楼梯

1.题目链接&#xff1a; 70. 爬楼梯 2.解题思路&#xff1a; 2.1.题目要求&#xff1a; 给个阶数 n&#xff0c;要求返回爬完 n 阶有几种方法。 一次可以爬 1 步 或者 2 步。 示例 1 输入&#xff1a;n 1 输出&#xff1a;1 解释&#xff1a;有一种方法可以爬到楼顶。 1.…

springBoot集成webSocket并使用postMan进行测试

简单描述 简单来讲&#xff0c;webSocket是一种在http协议基础上的另一种新协议&#xff0c;叫ws协议。 http协议是单工通信&#xff0c;客户端发起请求&#xff0c;服务端收到请求并处理&#xff0c;返回给客户端&#xff0c;然后客户端收到服务端的请求。 ws协议是全双工通…

代码随想录训练营第51天|LeetCode 309.最佳买卖股票时机含冷冻期、 714.买卖股票的最佳时机含手续费

参考 代码随想录 题目一&#xff1a;LeetCode 309.最佳买卖股票时机含冷冻期 如果没有冷冻期&#xff0c;就可以只定义两个状态&#xff0c;加入冷冻期后定义下面的四个状态&#xff1a; 状态一&#xff08; j 0&#xff09;&#xff1a;买入状态&#xff0c;不一定当天买…

设计模式-java

设计模式: 纸上得来终觉浅&#xff0c;绝知此事要躬行 设计原则 1) 单一职责原则 com.zh.designpatterns.design_principles.demo01_single_principle 概念&#xff1a;对类来说的&#xff0c;即一个类应该只负责一项职责。如类A负责两个不同职责&#xff1a;职责1&#x…

[AAAI 2022]多角度意图分解图网络的捆绑推荐

Multi-View Intent Disentangle Graph Networks for Bundle Recommendation捆绑推荐是什么&#xff1f;Bundle recommendation offers promotions of bundled items instead of a single one, which is a common strategy for sales revenue increase and latent customer mini…

谈谈对IO多路复用的select机制的理解

一、技术背景 如果要彻底明白select机制&#xff0c;还是要首先去了解IO&#xff0c;网络编程、Blocking IO、No Blocking IO的相关概念及底层实现。下面只是作为技术背景去介绍这几个概念。 1、理解IO本质 IO从英文本身去解释就是输入输出(Input/Output)&#xff0c;这里不…

web前端期末大作业:基于HTML+CSS+JavaScript实现网上鲜花店网站设计(14页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

手机进销存网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 功能模块包括&#xff1a;员工模块、手机类型模块、供应商模块、采购模块、客户模块、销售模块、统计模块、库存模块 (1…

Springboot内置的工具类之FileCopyUtils

前言 Spring内置的工具类里&#xff0c;最喜欢用的就是文件读写这一部分&#xff0c;虽然原生的写法也没几句&#xff0c;但是就是懒&#xff0c;不想循环、判断什么的&#xff0c;直接调用现成的静态方法&#xff0c;多高效&#xff0c;哈哈&#xff0c;这就是懒人必备。 Res…

UE5——动画混合(2)

一、引言 接上文《UE5——动画混合》&#xff0c;UE5还提供了 遮罩混合、惯性化两种混合&#xff0c;下面将讲述这两种动画混合方式 二、UE5上实现 1、遮罩混合 遮罩混合与《UE5——动画混合》中的 “骨骼的分层混合” 的运行逻辑其实是的一样的。相比起 “骨骼的分层混合”…

【SpringBoot+MyBatis】二级缓存以及使用Redis缓存数据

在MyBatis中&#xff0c;存在一级缓存以及二级缓存。一级缓存是默认自动开启&#xff0c;而二级缓存需要我们手动去开启。但看到这篇文章的人&#xff0c;大部分都是在做项目才发现的问题&#xff1a;每次访问数据都要查询一遍又一遍的数据库&#xff0c;这是在控制台上可以发现…

叶酸偶联N-季铵化壳聚糖(FA-HTCC)

叶酸偶联N-季铵化壳聚糖(FA-HTCC) 中文名称&#xff1a;叶酸偶联N-季铵化壳聚糖 英文名称&#xff1a;FA-HTCC 纯度&#xff1a;95% 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观:固体或粘性液体 包装&#xff1a;瓶装/袋装 溶解性&#xff1a;溶于大…

MYSQL 主从复制 --- binlog

一个MYSQL数据库存在的问题 在谈主从复制之前&#xff0c;应该都会有一个疑问&#xff0c;那么就是一个MYSQL数据库存在的问题呢&#xff1f; 1. 读和写所有压力都由一台数据库承担&#xff0c;压力大 2. 数据库服务器磁盘损坏则数据丢失&#xff0c;单点故障 为了解决我们可以…

Spark RDD介绍

RDD 引出问题 Spark是如何将多台机器上的数据通过一个类型来关联实现的&#xff1f; 答&#xff1a;通过RDD类型来实现关联 一、RDD简介 RDD&#xff08;Resilient Distributed Dataset&#xff09;&#xff1a;弹性分布式数据集 RDD的本质&#xff1a; 一个抽象的逻辑上的…

项目运行久了很卡,手动执行垃圾回收机制,秒丝滑

问题 当项目运行久了&#xff0c;内存会被大量占用。如何不重启项目&#xff0c;释放内存&#xff0c;继续丝滑开发呢&#xff1f; 回答 手动执行垃圾回收机制 打开任务管理器康康&#xff1a; 巨卡&#xff0c;无敌卡&#xff0c;解决它&#xff01; 打开命令行工具&…

Kaggle竞赛——Titanic泰坦尼克之灾(保姆级基础版)

Kaggle竞赛网址&#xff1a;https://www.kaggle.com/c/titanic 本次Kaggle泰坦尼克之灾分析过程大致分为&#xff1a; 第1步&#xff1a;了解数据 第2步&#xff1a;分析数据之间的关系 第3步&#xff1a;缺失项数据处理 第4步&#xff1a;特征因子化 第5步&#xff1a;处理特…

DBCO-SS-活性酯|DBCO-SS-NHS酯

DBCO-SS-活性酯|DBCO-SS-NHS酯 名称;DBCO-SS-活性酯|DBCO-SS-NHS酯 CAS NO&#xff1a;1435934-53-4 分子量&#xff1a;565.66 分子式&#xff1a;C28H27N3O6S2 含 量&#xff1a;>95% 外 观&#xff1a;固体粉末 保存&#xff1a;-20避光避湿 结构式&#xff1a; …

记录--手把手带你开发一个uni-app日历插件(并发布)

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 相信我们在开发各类小程序或者H5&#xff0c;甚至APP时&#xff0c;会把uni-app作为一个技术选型&#xff0c;其优点在于一键打包多端运行&#xff0c;较为强大的跨平台的性能。但是&#xff0c;只要开…

我为什么建议前端将Python 作为第二语言?

前言 “如何摆脱不停切图的困局&#xff1f;” 这不是一篇制造焦虑的文章&#xff0c;而是充满真诚建议的Python推广文。 当谈论到编程入门语言时&#xff0c;大多数都会推荐Python和JavaScript。 实际上&#xff0c;两种语言在方方面面都非常强大。 而如今我们熟知的ES6语…