Linux : System V 共享内存

目录

一 前言

二 共享内存概念

 三 共享内存创建 

四 查看共享内存 

五 共享内存的删除

六 共享内存的关联 

七 共享内存去关联 

八 共享内存的使用(通信)

 九 共享内存的特点


一 前言

共享内存区是最快的IPC形式(进程间通信:IPC,InterProcess Communication) 一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用write()和read()来传递彼此的数据。


二 共享内存概念

 在上一篇进程间的管道通信中我们提到过,在进程间进行通信的时候,由于程序地址空间的存在,进程间的独立性使得他们之间的通信很麻烦,如果想要通信则需要两个进程看到同一份资源,上篇通过系统调用创建管道文件,使得进程之间看到共享资源(内存级文件),而本节进程间通信时进程之间看到的同一份资源是  共享内存。

🚀什么是共享内存呢? 

实际上,我们在学习程序地址空间的时候,如上图所示,我们已经看到了有一个区域的名字是共享区,在之前我们学习动静态库的时候,就说过动态库是在进程运行的时候加载到程序地址空间中的共享区的。当程序需要的时候,就会来到这部分读取数据。这一块内存就可以看作是一块只读共享区,共享内存进程通信实际上就是这个原理。

             共享内存进程通信就是在物理内存中开辟一块可以让所有进程都看到的内存空间,然后多个进程只需要向这块空间中读取或者写入数据,这样就达到了多个进程间一起通信的目的

也就是说,共享内存进程间的通信就是在物理内存中开辟一块空间当作共享内存,然后通信的进程们通过各自的页表将这块物理内存(共享内存)映射到各自的程序地址空间中 


 三 共享内存创建 

shmget()    (share memory  get)

这个接口的参数一共有三个 

  1. key_t key :  这是一个键值,key_t是一个整型,此参数其实是传入的是一个整数。通常这个键是通过  ftok() 函数从一个文件路径和一个项目ID生成的. 这个key值其实就是共享内存段在操作系统层面的唯一标识符。共享内存是Linux系统的一种进程通信的手段, 而操作系统中共享内存段一定是有许多的, 为了管理这些共享内存段, 操作系统一定会描述共享内存段的各种属性。类似其他管理方式,操作系统也会为共享内存维护一个结构体,在这个结构体内会维护一个key值,表示此共享内存在系统层面的唯一标识符,其一般由用户传入,为了区别每一块的共享内存,key的获取也是有一定的方法。

    ftok()函数的作用是将 一个文件 和 项目id 转换为一个System V IPC key值。用户就是使用这个函数来生成key值。

    他有两个参数,第一个参数显而易见是文件的路径,第二个参数则是随意的8bite位的数值。ftok()函数执行成功则会返回一个key值,这个key值是该函数通过传入文件的inode值和传入的proi_id值通过一定的算法计算出来的。由于每一个文件的inode值是唯一的,所以我们不用担心key值得重复。

  2. size_t size:  该参数传入的是想要开辟的共享内存的大小,单位是 byte字节。值得注意的是系统是按照4KB大小为单位开辟空间的,因为我们在磁盘一篇中学到系统I/O的单位大小就是4KB,也就是说无论这个参数传的是1、1024还是4096时,系统都会开辟4KB,但是虽然系统是按照4KB为单位开辟的空间,但是实际上用户能使用的空间的大小还是传入的size字节大小。

  3. int shmflg: 这里传入的是一组标识位,可以控制shemget的行为,它包括权限标志(类似0666)和命令标志,就像我们使用文件接口open时的O_WRONLY、O_RDONLY一样。共享内存接口标识符的两个最重要的宏是:IPC_CREAT、IPC_EXCL

    IPC_CREAT:传入该宏,表示创建一个新的共享内存段,若共享内存段已经存在,则获取此内存段;若不存在就创建一个新的内存段。

    IPC_EXCL:该宏需要和IPC_CREAT一起使用。表示如果创建的内存段不存在,则正常创建,若存在则返回错误。使用该宏保证的是此次使用shmget()接口创建成功时,创建出来的共享内存是全新的。

  4. shmget()函数的返回值,如果创建共享内存成功或者找到共享内存则返回共享内存的id,该id的作用是可以让通信的进程找到同一份块的资源。此id 是给上层用户使用,是为了标识共享内存。而key也是为了标识共享内存,但是是从系统层面来说。

🚍:接下来我们来学习和认识共享内存的创建

///comm.hpp/
#ifndef _COMM_HPP_
#define COMM_HPP_#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>#define PATHNAME "."
#define PROJ_ID 0x66           //目标标识符 int proj_id 就是一个整型
#define MAX_SIZE 4096
key_t getKey()
{key_t k = ftok(PATHNAME,PROJ_ID);//可以获取同一个keyif(k < 0){//cin ,cout,cerr -------->stdin stdout stderr--->0 1 2std::cerr<<errno<<":"<<strerror(errno)<<std::endl;//strerror(errno) 打印错误码对应的错误信息exit(1);}return k;
}
/shm_client.cpp///
#include "comm.hpp"
int main()
{key_t k=getKey();printf("key: 0x%x\n",k); return 0;
}
///shm_server.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);return 0;
}

 运行结果:

 🚋:key的值是什么并不重要,重要的是能进行唯一性标识。

有了key之后,我们就可以用唯一的key进行共享内存的创建

//comm.hpp
//将一些函数进行封装到comm.hpp,然后client和server进行调用
int getShmHelper(key_t k,int flags)
{int shmid=shmget(k,MAX_SIZE,flags);//创建共享内存函数shmgetif(shmid < 0){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(2);}return shmid;
}//获取共享内存
int getShm(key_t k)
{return getShmHelper(k,IPC_CREAT); //如果存在就获取,所以在客户端client,我们可以通过这个函数,来获取共享内存
}
////创建共享内存
int createShm(key_t k)//创建共享内存的工作,有服务端server来做
{return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0600);//如果存在就创建失败,保证了我们创建的一定是新的共享内存,0600 代表创建的共享内存可读可写
}
//server.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=createShm(k);//服务端进行创建共享内存printf("shmid: %d\n",shmid);return 0;
}
///client.cpp
#include "comm.hpp"
int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=getShm(k);//客户端进行获取共享内存printf("shmid: %d\n",shmid);return 0;
}

 运行结果:

 🍉这里的 key shmid 有什么区别呢?

key:是系统层面的,系统通过key来创建共享内存。

shmid:是上层用户层面,用户通过shmid来找到共享内存。


四 查看共享内存 

可是当我们再次运行服务端的时候,会发现出现如下问题:文件已存在

 这是什么原因呢?事实上,共享内存并不会随着进程的退出而退出,在创建共享内存的进程退出之后,共享内存是依旧存在于操作系统中的。而我们的服务端用key创建共享内存的时候,必须要求创建一个新的,如果当前的key对应的共享内存已经存在,则报错。

我们可以通过命令查看共享内存资源: ipcs -m  

这表明共享内存的生命周期是随着OS的,并不会因为进程的退出,而把共享内存删除。


五 共享内存的删除

我们可以使用 ipcrm -m (InterProcess Communication Remove Memory) 命令来删除。

那我们是通过 key 删除共享内存还是 shmid呢? 前面我们也说了key是内核层面,操作系统使用key,而删除共享内存,是指令操作,属于用户层面,所以我们通过shmid删除共享内存。

 我们还可以通过调用系统函数  shmctl 来对共享内存进行删除

我们在comm.hpp中对 shmctl进行封装成删除共享内存的函数。

/comm.hpp
void delShm(int shmid)
{if(shmctl(shmid,IPC_RMID,nullptr)==-1)//返回-1代表调用失败{std::cerr<<"shmctl"<<errno<<":"<<strerror(errno)<<std::endl;}
}
/sever.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=createShm(k);//服务端进行创建共享内存printf("shmid: %d\n",shmid);sleep(5);delShm(shmid);//调用删除共享内存函数}

 再次测试:


六 共享内存的关联 

🌏:前面我们讲述了服务端对共享内存的创建以及删除,但是要想使得两个进程进行通信,我们还需要进行共享内存对两个进程关联起来。

 系统调用函数 shmat (attach)

 我们在comm.hpp中对 shmat进行封装成关联共享内存的函数。

/comm.hpp//
//关联共享内存
void* attachShm(int shmid)
{void* mem =shmat(shmid,nullptr,0);if((long long)mem==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(3);}return mem;
}
///server.cpp
#include "comm.hpp"int main()
{key_t k=getKey();printf("key: 0x%x\n",k);int shmid=createShm(k);//服务端进行创建共享内存printf("shmid: %d\n",shmid);sleep(5);//关联共享内存char* start=(char*)attachShm(shmid);//返回值是共享内存地址printf("attach success,address start:%p\n",start);//删除sleep(5);delShm(shmid);return 0;
}

测试结果


七 共享内存去关联 

既然共享内存可以关联,自然也可以去关联,去关联并不是删除共享内存,而是去除进程与共享内存的联系。

系统调用函数 shmdt()    (detach)

我们在comm.hpp中对 shmdt进行封装成去关联共享内存的函数。

//comm.cpp////去关联共享内存
void detachShm(void* start)
{if(shmdt(start)==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;}
}

八 共享内存的使用(通信)

🌿,在前面我们做了以下工作

共享内存的创建------------->关联共享内存---------->(这里我们将进行共享内存的通信)---------------->关联共享内存------------------------------------>删除共享内存 

//client.cpp
// 4.使用即通信const char* message="hello server, 我是另外一个进程正在和你通信";pid_t id=getpid();int count =0;while(true){sleep(1);//向共享内存输入消息snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,count++);//pid count message}
/server.cpp///
//4.使用while(true){printf("client say: %s\n",start);//打印由客户端发来的消息,直接打印共享内存地址即可sleep(1);}

测试结果:

 

 完整测试如下

/comm.hpp
#ifndef _COMM_HPP_
#define COMM_HPP_#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>#define PATHNAME "."
#define PROJ_ID 0x66           //目标标识符 int proj_id 就是一个整型
#define MAX_SIZE 4096
key_t getKey()
{key_t k = ftok(PATHNAME,PROJ_ID);//可以获取同一个keyif(k < 0){//cin ,cout,cerr -------->stdin stdout stderr--->0 1 2std::cerr<<errno<<":"<<strerror(errno)<<std::endl;//strerror(errno) 打印错误码对应的错误信息exit(1);}return k;
}int getShmHelper(key_t k,int flags)
{int shmid=shmget(k,MAX_SIZE,flags);//创建共享内存函数shmgetif(shmid < 0){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(2);}return shmid;
}//获取共享内存
int getShm(key_t k)
{return getShmHelper(k,IPC_CREAT); //如果存在就获取,所以在客户端client,我们可以通过这个函数,来获取共享内存
}
////创建共享内存
int createShm(key_t k)//创建共享内存的工作,有服务端server来做
{return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0666);//如果存在就创建失败,保证了我们创建的一定是新的共享内存
}//关联共享内存
void* attachShm(int shmid)
{void* mem =shmat(shmid,nullptr,0);if((long long)mem==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(3);}return mem;
}//去关联共享内存
void detachShm(void* start)
{if(shmdt(start)==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;}
}
//删除共享内存
void delShm(int shmid)
{if(shmctl(shmid,IPC_RMID,nullptr)==-1)//返回-1代表调用失败{std::cerr<<"shmctl"<<errno<<":"<<strerror(errno)<<std::endl;}
}#endif
client.cpp///
#include "comm.hpp"
int main()
{//1.获取keykey_t k=getKey();printf("key: 0x%x\n",k);//2.获取共享内存int shmid=getShm(k);//客户端进行获取共享内存printf("shmid: %d\n",shmid);sleep(5);//3.进行关联char* start=(char*)attachShm(shmid);printf("attach success,address start:%p\n",start);sleep(5);// 4.使用即通信const char* message="hello server, 我是另外一个进程正在和你通信";pid_t id=getpid();int count =0;while(true){sleep(1);//向共享内存输入消息snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,count++);//pid count message}// sleep(5);//5.去关联detachShm(start);return 0;
}
/server.cpp
#include "comm.hpp"int main()
{//1.创建keykey_t k=getKey();printf("key: 0x%x\n",k);//2.创建共享内存int shmid=createShm(k);//服务端进行创建共享内存printf("shmid: %d\n",shmid);sleep(5);//3. 关联共享内存char* start=(char*)attachShm(shmid);//返回值是共享内存地址printf("attach success,address start:%p\n",start);//4.使用while(true){printf("client say: %s\n",start);//打印由客户端发来的消息,直接打印共享内存地址即可sleep(1);}// // 5.去关联detachShm(start);sleep(5);//删除sleep(10);delShm(shmid);return 0;
}

 九 共享内存的特点

共享内存的优点:所以进程间通信,速度是最快的,能大大减少拷贝次数。

同样的代码,考虑到键盘输入和显示器输出 ,如果用管道来实现,会对数据进行几次拷贝?

共享内存的缺点:不给我们进行同步和互斥的操作,没有对数据做任何保护。

即客户端不进行写,服务端也一直进行读取,服务端不进行读取,客户端依然进行写入。 

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

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

相关文章

Spring Cloud 2023.x安全升级:OAuth2.1与JWT动态轮换实战

引言&#xff1a;当安全遇上云原生&#xff0c;零停机密钥轮换成为刚需 在微服务架构中&#xff0c;OAuth2.1与JWT已成为身份验证的黄金标准&#xff0c;但传统方案存在两大痛点&#xff1a; 密钥轮换风险&#xff1a;手动替换JWT密钥需重启服务&#xff0c;导致短暂鉴权中断&…

创建私人阿里云docker镜像仓库

一.登录阿里云 https://cr.console.aliyun.com/cn-hangzhou/instances 二.创建个人实例 【实例列表】 》【创建个人实例】 》【设置Registry登录密码】 三.创建命名空间 步骤&#xff1a;【个人实例】》【命名空间】》【创建命名空间】 注:一个账号最多可以创建3个命名空…

oracle基础知识视图的定义和应用

1.1 视图的定义 视图(View)是数据库中非常重要的内容&#xff0c;在实际开发中必须学会视图的编写。 用于产生视图的表叫做该视图的基表。一个视图也可以从另一个视图中产生。视图是可以嵌套的。 视图的定义存在数据库中&#xff0c;与此定义相关的数据并没有再存一份于数据库中…

边缘计算:工业自动化的智能新引擎

在工业4.0的浪潮中&#xff0c;工业自动化正经历着前所未有的变革。随着物联网&#xff08;IoT&#xff09;技术的普及&#xff0c;越来越多的工业设备被连接到网络中&#xff0c;产生了海量的数据。然而&#xff0c;传统的云计算架构在处理这些实时性要求极高的工业数据时&…

12-SpringBoot3入门-项目打包和运行

1、打包 1&#xff09;打包插件 pom.xml <!--SpringBoot应用打包插件--> <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plu…

【PCIE711-214】基于PCIe总线架构的4路HD-SDI/3G-SDI视频图像模拟源

产品概述 PCIE711-214是一款基于PCIE总线架构的4路SDI视频模拟源。该板卡为标准的PCIE插卡&#xff0c;全高尺寸&#xff0c;适合与PCIE总线的工控机或者服务器&#xff0c;板载协议处理器&#xff0c;可以通过PCIE总线将上位机的YUV 422格式视频数据下发通过SDI接口播放出去&…

PipeWire 音频设计与实现分析一——介绍

PipeWire 是一个基于图的媒体处理引擎&#xff0c;一个可以运行多媒体节点图的媒体服务器&#xff0c;是 Linux 的音频/视频总线&#xff0c;它管理 Linux 系统中&#xff0c;不同应用程序对音频和视频设备的共享访问。它提供了一个本地客户端音频 API&#xff0c;但也提供兼容…

使用卷积神经网络识别MNIST数据集

卷积神经网络 卷积神经网络本质是共享权重稀疏链接的全连接网络 编写步骤 构建一个神经网络&#xff0c;步骤是几乎不变的&#xff0c;大概有以下几步 准备数据集 #更高级的CNN网络 import torch import torch.nn as nn import torch.nn.functional as F import torchvisi…

力扣125.验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &#xff1b;否则&#…

UR机械臂sim2real推荐包

推荐一个和ur机械臂配套的interface&#xff1a; ur_rtde Universal Robots RTDE C Interface — ur_rtde 1.6.0 documentation 也欢迎大家提供新想法和bug

CST学习笔记(三)MATLAB与CST联合仿真-远场数据批量导出

CST学习笔记&#xff08;三&#xff09;MATLAB与CST联合仿真-远场数据批量导出 一、直接数据导出 &#xff08;1&#xff09;打开远场&#xff0c;调至笛卡尔坐标系 &#xff08;2&#xff09;然后点击post processing → Import/Export → Plot Data (ASCII) &#xff0c;即…

蓝桥杯---BFS解决FloofFill算法1---图像渲染

文章目录 1.算法简介2.题目概述3.算法原理4.代码分析 1.算法简介 这个算法是关于我们的floodfill的相关的问题&#xff0c;这个算法其实从名字就可以看出来&#xff1a;洪水灌溉&#xff0c;其实这个算法的过程就和他的名字非常相似&#xff0c;下面的这个图就生动的展示了这个…

我与数学建模之启程

下面的时间线就是从我的大二上开始 9月开学就迎来了本科阶段最重要的数学建模竞赛——国赛&#xff0c;这个比赛一般是在9月的第二周开始。 2021年国赛是我第一次参加国赛&#xff0c;在报名前我还在纠结队友&#xff0c;后来经学长推荐找了另外两个学长。其实第一次国赛没啥…

利用 SSRF 和 Redis 未授权访问进行内网渗透

目录 环境搭建 ​编辑 发现内网存活主机 ​编辑 扫描内网端口 ​编辑 利用 Redis 未授权访问进行 Webshell 写入 步骤1&#xff1a;生成 payload 方式1&#xff1a;使用python生成 payload 方式二&#xff1a;使用 Gopher 工具 步骤 2&#xff1a;写入 Webshell&#xf…

【Vue2插槽】

Vue2插槽 Vue2插槽默认插槽子组件代码&#xff08;Child.vue&#xff09;父组件代码&#xff08;Parent.vue&#xff09; 命名插槽子组件代码&#xff08;ChildNamed.vue&#xff09;父组件代码&#xff08;ParentNamed.vue&#xff09; 代码解释 Vue2插槽 Vue2插槽 下面为你详…

ORB-SLAM学习感悟记录

orb特征点的旋转不变性 利用灰度质心法求出的质心后&#xff0c;与形心连线所形成的角度如下图所示&#xff1a; 这里容易对上图进行误解&#xff1a; 为了保证旋转不变性&#xff0c;这里注意ORB-slam是利用这个角度旋转坐标系&#xff0c;以新坐标系为标准从图像中采点进行…

搜索算法------深度优先搜索

1. 介绍 深度优先搜索&#xff08;Depth-First Search&#xff0c;DFS&#xff09;是一种用于遍历或搜索树或图的算法。这种算法通过尽可能深地搜索图的分支来探索解决方案空间&#xff0c;直到达到一个没有分支的点&#xff0c;然后回溯 1.1 原理 选择起始点&#xff1a;从…

4.2 单相机引导机器人放料-仅考虑角度变化

【案例说明】 本案例产品在托盘中,角度变化不大(<15度);抓取没有问题,只是放的穴位只能容许3度的角度偏差,因此需要测量产品的角度。 思路是:机器人抓料后、去固定拍照位拍照(找到与标准照片的角度偏差),机器人在放料的位置上多旋转这个角度偏差,把产品放进去。 …

六级词汇量积累day13

commend 表扬 exhaust 耗尽&#xff0c;用尽 weary 疲惫的&#xff0c;劳累的 fatigue 疲惫&#xff0c;劳累 obese 臃肿的&#xff0c;肥胖的 adopt 采纳&#xff0c;收养 adapt 适应 accomplish 完成&#xff0c;实现 accomplishment 成就 achieve 实现&#xff0c;完成 achi…

医院信息系统与AI赋能的介绍

随着医疗行业的不断发展&#xff0c;医院信息系统&#xff08;HIS&#xff0c;Hospital Information System&#xff09;已经成为现代医疗服务不可或缺的一部分。医院信息系统通过数字化、信息化手段&#xff0c;有效地整合了医院内部的医疗、财务、后勤等各个业务环节&#xf…