进程间通信(二)/共享内存

news2025/7/19 1:02:07

⭐前言:在前面的博文中分析了什么的进程间通信和进程间通信的方式之一:管道(匿名管道和命名管道)。接下来分析第二种方式:共享内存。

要实现进程间通信,其前提是让不同进程之间看到同一份资源。所谓共享内存,那就是不同进程之间,可以看到内存中同一块资源,这就是共享内存的概念。

共享内存原理

用户通过操作系统提供的系统调用,让操作系统帮助用户去申请一块空间,跟C语言中malloc函数、C++的new的意思差不多。创建好后,将创建好的内存映射到进程地址空间中,然后返回这个地址的起始地址给用户。最后,当结束通信后,就会取消进程和内存的映射关系去掉,然后释放这段内存空间!

而这段内存,就称为共享内存!进程与内存关联的行为称为挂接。取消进程与内存的映射关系,称为去关联。释放这段内存,叫做释放共享内存。

理解共享内存的开辟

①用户申请开辟共享内存空间的系统接口,是专门为了进程间通信而设计出来的,可以让不同进程同时跟其建立关联。跟malloc,new等等的函数不一样,它们虽然也可以在物理内存上开辟空间,但是只能用于本身进程。

②共享内存是一种通信方式,意味着所有想通信的进程都可以使用它。

③既然共享内存是一种通信方式,因此在OS中,一定存在多个共享内存!

实例代码

共享内存函数

按照上图的步骤:第一步,创建共享内存。以下是创建共享内存的两个函数。

①shmget函数

功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);

头文件:#include<sys/ipc.h>  #include<sys/shm.h>
参数:
        key : 这个共享内存段名字。
        size : 共享内存大小
        shmflg : 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样

其中重要的两个:

IPC_CREAT:如果不存在,创建之。如果存在,获取之。

IPC_EXCL:无法单独使用。需要与IPC_CREAT结合使用,

IPC_CREAT | IPC_EXCL:如果不存在,创建之。如果存在,出错并返回。如果创建成功,那么一定是一个新的共享内存。

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 - 1

shmget函数中的参数key,它能够标定唯一性!因为需要保证一个进程去申请共享内存,另外的进程去获取这个共享内存,它们的共享内存是同一个共享内存!而获取key是通过ftok函数来获取的。

②ftok函数

功能:将一个路径明和一个项目标识符转化成一个IPC的key

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

头文件:#include<sys/ipc.h> #include<sys/types.h>

参数:

        pathname:传进来的字符串

        proj_id:项目标识符

返回值:成功返回key;失败返回-1

只要不同进程在调用ftok的时候,参数一模一样,获取相同的key,再去调用shmget函数,通过同一个key,就能访问同一个共享内存。

补充说明:

共享内存=物理内存块+共享内存的相关属性

上面谈到,OS中一定存在多个共享内存,而OS必须要对这些用户申请开辟的空间进行管理!即先描述再组织,因此,OS会对开辟的共享内存创建一个数据结构,一个共享内存一个数据结构,然后通过链表链接起来,统一管理。于是,在谈到申请开辟一块共享内存,就需要想到:共享内存 = 物理内存块 + 共享内存的相关属性!

key值被包含在了共享内存的属性中。

共享内存的相关属性被包含在共享内存的数据结构中,而其中的key值也包含在了里面。即key值是在shmget函数创建出来后被设置进入共享内存的属性当中,用来表示该共享内存,并表示该共享内存在内核中的唯一性!

shmid和key的关系区分

shmget函数返回值,假设命名为shmid。那么shmid与key的关系就如同在文件IO中的文件描述符fd和inode的关系一样,inode是一个文件一个inode,表示文件的唯一性,key是一个共享内存一个,表示的是共享内存的唯一性,它们都是底层访问目标的工具。但是上层是不用key或inode的,而是使用shmid和fd这样一个特定的整数来访问。一句话来说,一个是用户的,一个是系统的,两个互不干扰,这是它的好处。

查看共享内存指令

ipcs -m

ipc资源的特征

共享内存的生命周期是随操作系统的,不是随进程的,即使进程终止了,但没有去释放这段共享内存,那么它就会一直存在。

删除共享内存

ipcrm -m shmid

按照上图所示:以下是删除共享内存的函数。

③shmctl函数

功能:用于控制共享内存,即删除共享内存,设置共享内存属性等等
原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

头文件:#include<sys/ipc.h> #include<sys/shm.h>
参数:
        shmid:由shmget返回的共享内存标识码。
        cmd:将要采取的动作(有三个可取值)

动作:

①IPC_STAT:获取共享内存属性

②IPC_SET:设置共享内存属性

③IPC_RMID:删除共享内存
        buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

按照上图所示,以下是将共享内存映射到进程地址空间的函数。

④shmat函数

功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);

头文件:#include<sys/shm.h>   #include<sys/types.h>
参数:
        shmid: 共享内存标识,即想和哪个共享内存关联起来
        shmaddr:指定连接的地址。就是想把这个共享内存映射到哪个进程地址空间中,给出这个进程地址。
        shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY


返回值:成功返回一个指针,指向共享内存;失败返回-1

使用完后,不直接删除共享内存,而是先去关联。以下是去关联的函数。

⑤shmdt函数

功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);

头文件:#include<sys/shm.h>   #include<sys/types.h>
参数:shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

示例代码代码如下:

代码思路:创建一段共享内存,创建两个没有亲属关系的进程,client进程负责写入,server进程负责读取。

头文件comm.hpp:

#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAME "."
#define PROJ_ID 0X66

//设置共享内存大小:建议为4KB的整数倍
//因为系统分配共享内存是以4KB为单位的!
#define MAX_SIZE 4096

//获取key
key_t getKey()
{
    //通过ftok函数获取key
    key_t k = ftok(PATHNAME,PROJ_ID);//获得同一个key
    if(k < 0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(1);
    }
    return k;
}
//创建共享内存
int getShmHelper(key_t k,int flags)
{
    //通过shmget函数创建共享内存。
    //第一个参数是key,第二个参数是共享内存的大小。第三个参数是权限标志
    int shmid = shmget(k,MAX_SIZE,flags);//创建共享内存
    if(shmid<0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}
//通过封装函数给用户去使用,只需传入key值即可。
//获取共享内存,不一定要新的,因为不用调用它的进程去创建新的
int getShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT);
}
//创建共享内存,使用IPC_CREAT | IPC_EXCL,确定创建的共享内存一定是新的。需要给权限0600
int createShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0600);
}

//进程地址空间与共享内存相联
void* attachShm(int shmid)
{
    //通过shmat函数将共享内存段连接到进程地址空间
    //传入shmid和指定连接的进程地址的地址,但是这个一般不填,系统会自动去填
    //第三个参数是权限标志,是对内存只读还是读写。
    //在Linux系统中,一般是64位。我们这里需要将shmat函数返回的指针判断是否关联成功
    //强行转化为longlong
    void *men = shmat(shmid,nullptr,0);
    if((long long)men==-1L)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(3);
    }
    return men;//返回起始地址

}

void detachShm(void* start)
{
    //通过shmdt函数去关联
    if(shmdt(start)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}

void delShm(int shmid)
{
    //通过shmctl函数删除共享内存
    //第一个参数是函数是需要对哪个共享内存操作,那个共享内存
    //第二个参数是需要进行什么样的操作
    //第三个参数一般给nullptr
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}

负责写入的进程程序代码client.cc:

#include"comm.hpp"
#include<unistd.h>

int main()
{
    //第一步:创建key,创建共享内存
    key_t k = getKey();//获取key
    printf("key: 0x%x\n",k);//查看key值
    int shmid = getShm(k);//创建共享内存
    printf("shmid:%d\n",shmid);//查看shmid

    //第二步:关联内存和进程地址空间
    char* start = (char*)attachShm(shmid);
    printf("attach success,address start: %p\n",start);//查看起始地址

    //开始使用
    //写下需要往共享内存段写入的数据
    const char* message = "hello server,我是另一个进程,正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    while(true)
    {
        sleep(5);
        //写入到共享内存段,将共享内存段当字符串,不需要额外char buffer[];
        snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,cnt++);
    }

    //去关联
    detachShm(start);

    //这个工程项目不需要删除共享内存



    return 0;
}

负责读取的进程的程序代码server.cc

#include"comm.hpp"
#include<unistd.h>

int main()
{
    key_t k = getKey();//获取key值
    printf("key: 0x%x\n",k);//查看key值
    int shmid = createShm(k);//创建共享内存,必须是新的
    printf("shmid: %d\n",shmid);//查看共享内存

    //关联
    char* start = (char*)attachShm(shmid);
    printf("attach success, address start: %p\n", start);

    //使用
    while(true)
    {
        //读取共享内存中的数据
        printf("client say: %s\n",start);
        //获取共享内存中的属性数据(部分)
        struct shmid_ds ds;
        shmctl(shmid,IPC_STAT,&ds);
        printf("获取属性: size: %d, pid: %d, myself: %d, key: 0x%x",\
                ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);
        sleep(1);
    }

    //去关联
    detachShm(start);

    //删除共享内存
    delShm(shmid);

    return 0;
}

 结果如下:在第一个五秒时,共享内存中没有任何数据。第二个五秒,消息编号为1。第三个五秒,消息编号为2......

 对于从内核数据结构中获取共享内存的属性,发现没有直接显示key值。但实际上key值是在这个内核数据结构中里面的另外一个结构体里面。

共享内存的优缺点

优点:所有使用共享内存的进程通信,速度是最快的!能大大减少数据拷贝的次数!并且生命周期是随系统的!那么,如果我们考虑到同样一份代码,分别使用管道和共享内存的话,并且考虑键盘输入和显示器输出,那么管道有几次拷贝?共享内存有几次拷贝?

 如图,管道的话,需要创建buffer来获取数据,然后通过管道进行通信。而共享内存不需要,因为共享内存可以作为字符串空间,直接写入和读取数据。因此,根据上图所示,管道是6次拷贝,共享内存是4次拷贝。当然,代码不同,拷贝的次数也不会同。

缺点:共享内存没有同步和互斥!

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

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

相关文章

虹科直播 | 2月25日相约虹科Pico学院,教您CAN总线测试与波形诊断分析

​​2月25日 直播预告 网络总线的测试与波形诊断分析 2月18日 直播回顾 2月18日&#xff0c;虹科Pico汽车示波器学院成功开展第六课的直播课程。戈华飞老师向各位学员讲解了车身天线信号&#xff08;无钥匙进入系统&#xff09;、倒车雷达信号、车内超声波监控信号测试与波形…

操作系统真相还原_第6章:完善内核

文章目录6.1 函数调用约定简介6.2 汇编语言和C语言混合编程汇编调用CC调用汇编6.3 实现打印函数流程程序编译并写入硬盘执行6.4 内联汇编简介汇编语言AT&T语法基本内联汇编扩展内联汇编6.1 函数调用约定简介 调用约定&#xff1a; calling conventions 调用函数时的一套约…

OpenCV基础(28)使用OpenCV进行摄像机标定Python和C++

摄像头是机器人、监控、太空探索、社交媒体、工业自动化甚至娱乐业等多个领域不可或缺的一部分。 对于许多应用&#xff0c;必须了解相机的参数才能有效地将其用作视觉传感器。 在这篇文章中&#xff0c;您将了解相机校准所涉及的步骤及其意义。 我们还共享 C 和 Python 代码以…

2023面试准备之--mysql

文章目录mysql存储引擎索引聚簇索引和非聚簇索引事务锁MVCC机制&#xff08;类似于copy on write&#xff09;主从复制为什么要主从同步&#xff1f;怎么处理mysql的慢查询&#xff1f;mysql clint ---->server ----> 存储引擎 存储引擎 Innodb 是MySQL5.5版本及之后默…

1.3 Spring Boot 框架集成Knife4j(官网)

1.3 Spring Boot 框架集成Knife4j&#xff08;官网&#xff09; 本次示例使用Spring Boot作为脚手架来快速集成Knife4j,Spring Boot版本2.3.5.RELEASE,Knife4j版本2.0.7&#xff0c;完整代码可以去参考knife4j-spring-boot-fast-demo pom.xml完整文件代码如下 <?xml ver…

性能测试的二八原则

性能测试二八原则&#xff0c;响应时间2/5/8原则 所谓响应时间的“2-5-8原则”&#xff0c;简单说&#xff0c;就是 当用户能够在2秒以内得到响应时&#xff0c;会感觉系统的响应很快; 当用户在2-5秒之间得到响应时&#xff0c;会感觉系统的响应速度还可以; 当用户在5-8秒以内得…

电路模型和电路定律——“电路分析”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是我这学期的专业课噢&#xff0c;首先就学习了电路模型和电路定律&#xff0c;包括电路和电路模型、电流和电压的参考方向、电功率和能量、电路元件、电阻元件、电压源和电流源、基尔霍夫定律。那么现在&#xff0c;就让我…

FastReport .NET 2023.1.13 Crack

FastReport .NET 使用来自 ADO .NET 数据源的数据。它可以排序和过滤数据行&#xff0c;使用主从关系和查找数据列。一切都可以通过点击几下鼠标来完成。 直接连接到 ADO、MS SQL 或基于 xml 的数据库。其他连接器可作为插件使用。 能够从 IEnumerable 类型的业务对象中获取数…

hadoop入门介绍及各组件功能运行关系

文章目录Hadoop 组成部分1.HDFS2.MapReduce 架构概述3. yarn 架构概述4.HDFS、YARN、MapReduce三者关系Hadoop 组成部分 1.HDFS Hadoop Distributed File System&#xff0c;简称 HDFS&#xff0c;是一个分布式文件系统。 HDFS 架构概述 主要分为 NameNode (mn):存储文件的元…

SQL高级 --优化

一、SQL查询的解析 关联查询过多索引失效&#xff08;单值、符合&#xff09; 二、mysql explain使用简介 1、关于id的说明&#xff1a; 2 、select_type 常见和常用的值有如下几种&#xff1a; 分别用来表示查询的类型&#xff0c;主要是用于区别普通查询、联合查询、子…

Java常用框架(二)

三、Netty 1.概述 1.1 为什么要选Netty? 使用Netty不必编写复杂的逻辑代码去实现通信&#xff0c;再也不需要去考虑性能问题&#xff0c;不需要考虑编码问题&#xff0c;半包读写等问题。Netty是最流行的NIO框架&#xff0c;它的健壮性、功能、性能、可定制性和可扩展性在同…

关于android中有关JNI无法生成.so文件的问题解决方法

问题&#xff1a;在配置NDK开发环境时&#xff0c;会发现无法正常生成so文件的情况&#xff0c;可能时AS本身的bug&#xff0c;这时候提供一种方法手动生成so文件 一 配置NDK 1.1 在File | Settings | Appearance & Behavior | System Settings | Android SDK | SDK Tool…

【Linux】常用命令大全(二)

目录 4. Linux常用命令 4.1 Linux命令初体验 4.2 文件目录操作命令 4.3 拷贝移动命令 4.4 打包压缩命令 4.5 文本编辑命令 4.6 查找命令 4. Linux常用命令 4.1 Linux命令初体验 4.1.1 常用命令演示 在这一部分中&#xff0c;我们主要介绍几个常用的命令&#xff0c…

深度学习基础训练流程

深度学习基础训练流程 前言 本文只是本人学习笔记记录&#xff0c;文中部分图片来源网络&#xff0c;如有侵权请联系我进行删除 1、深度学习基础 1.1 深度学习 深度学习是机器学习的一个分支&#xff0c;目的是找到一组良好的参数θ&#xff0c;使得θ表示的数学模型能够很…

K8S多节点二进制部署

目录 一、多 Maser 集群架构的了解 二、master2 节点部署 1、从 master01 节点上拷贝证书文件、各master组件的配置文件和服务管理文件到 master02 节点 2、修改配置文件kube-apiserver中的IP 3、在 master02 节点上启动各服务并设置开机自启 4、查看node节点状态 三、负…

1-MATLAB APP Design-图像的输入与输出

一、APP 界面设计展示 新建一个空白的APP,在此次的学习中,我们会用到编辑字段(文本框)、 按钮、坐标区和面板,首先在界面中拖入一个编辑字段(文本框),在文本框中输入内容:图形的输入与输出,调整背景颜色,字体的颜色为黑色,字体的大小调为25.

开通腾讯位置服务

文章目录一、开通腾讯位置服务步骤二、腾讯位置服务SDK三、小程序项目中&#xff0c;创建 lib 目录&#xff0c;把SDK文件放入其中一、开通腾讯位置服务步骤 因为签到流程中要获取用户当前所在地址的信息&#xff0c;所以需要把定位坐标缓存成地址&#xff0c;恰好腾讯位置服务…

【 java 8】Lambda 表达式

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

MySQL 性能优化思路和工具

MySQL 性能优化思路和工具 一、优化思路 作为架构师或者开发人员&#xff0c;说到数据库性能优化&#xff0c;你的思路是什么样的&#xff1f; 或者具体一点&#xff0c;如果在面试的时候遇到这个问题&#xff1a;你会从哪些维度来优化数据库&#xff0c;你会怎么回答&#x…

Hive内部表与外部表的区别具体说明

目录 1.在/opt/atguigu/目录下&#xff0c;新建两个txt文件 2.在hadoop的web端递归创建一个目录&#xff0c;存储这两个文件 3.查看web端的文件 一、内部表&#xff1a; 1.创建一个内部表&#xff0c;并指定内部表的存储位置 2.查看内部表&#xff0c;内部表中没有数据 …