【Linux操作系统】共享内存

news2024/12/23 0:09:38

文章目录

  • 什么是共享内存?共享内存的原理
  • 共享内存的知识点构建
    • 创建共享内存的前提-key值
    • 共享内存的创建
    • ipcs命令
    • 共享内存的释放
    • 共享内存的关联与解除
    • 代码演示
    • 共享内存的大小
    • 共享内存的特点


什么是共享内存?共享内存的原理

共享内存让不同进程看到同一份资源的方式就是,在物理内存当中申请一块内存空间,然后将这块内存空间分别与对应的进程各自的页表之间建立映射,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起对应关系,至此这些进程便看到了同一份物理内存,这块物理内存就叫做共享内存
在这里插入图片描述

  • 使用步骤:先创建共享内存,再关联对应的进程,最后取消共享内存和进程的关联,释放共享内存块。
  • 共享内存不是只要在内存中开辟空间,系统还要为了管理shm共享内存,构建对应的描述共享内存的结构体对象。
  • 开辟物理内存,建立映射等操作都是通过我们调用系统函数接口完成的,也就是说这些操作都是由操作系统完成。
  • 对于虚拟地址空间中栈和堆之间的共享区,这里不仅可用于之前说到的存储动态库,共享内存,还可以用于加载网络映像,共享数据以及存储某些不需要保存在磁盘上的临时数据。

共享内存的结构

在系统当中可能会有大量的进程在进行通信,因此系统当中就可能存在大量的共享内存,那么操作系统必然要对其进行管理,所以共享内存除了在内存当中真正开辟空间之外,系统一定还要为共享内存维护相关的内核数据结构。

struct shmid_ds {
	struct ipc_perm     shm_perm;   /* operation perms */
	int         shm_segsz;  /* size of segment (bytes) */
	__kernel_time_t     shm_atime;  /* last attach time */
	__kernel_time_t     shm_dtime;  /* last detach time */
	__kernel_time_t     shm_ctime;  /* last change time */
	__kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
	__kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
	unsigned short      shm_nattch; /* no. of current attaches */
	unsigned short      shm_unused; /* compatibility */
	void            *shm_unused2;   /* ditto - used by DIPC */
	void            *shm_unused3;   /* unused */
};

共享内存数据结构的第一个成员是shm_perm,shm_perm是一个ipc_perm类型的结构体变量,每个共享内存的key值存储在shm_perm这个结构体变量当中

共享内存的知识点构建

创建共享内存的前提-key值

问:当我们调用系统接口申请了一块共享内存,我们要保证对应的进程能够访问到同一块共享内存,那么如何做到这一点呢?

答案:每一个共享内存被申请的时候都会有一个key值,这个key值用于标识系统中共享内存的唯一性。

在这里插入图片描述
传入shmget函数(shmget函数用于创建共享内存)的第一个参数key,需要我们使用ftok函数进行获取。ftok函数的函数原型如下:

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

ftok函数的作用:通过数学运算将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中。需要注意的是,pathname所指定的文件必须存在且可存取。

ftok运算出来的key值可能会产生冲突,不过概率很小。如果产生冲突,就对ftok的参数进行修改即可。

需要访问同一块共享内存的进程(进程间通信),在使用ftok函数获取key值时,都要用同样的路径字符串和整数标识符,这样才能生成同一key值,然后不同的进程之间才能找到同一块共享内存,进而开始通信。

以下代码模拟了两个不同的进程,如何获取同一共享内存(key值)的过程:
在这里插入图片描述

运行结果:发现俩进程获取的是一样的key值!!!
在这里插入图片描述

共享内存的创建

创建共享内存我们需要用shmget函数,shmget函数的函数原型如下:

int shmget(key_t key, size_t size, int shmflg);

参数解读

  • key:共享内存段的名字(就是上面我们通过ftok函数获取的key值)
  • size:共享内存的大小
  • shmflg:创建共享内存的方式

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

关于共享内存,一定是一个进程创建,另一个进程获取,并且最好是创建新的共享内存。关于shmget函数的第一个参数key前面已经解析过了,关于第二个参数,我们暂时将共享内存的大小设置为4096个字节,接下来我们开始讲解shmget函数的第三个参数,并教大家如何创建共享内存,那就开始吧!!!

第三个参数的组合方式:

组合方式作用
IPC_CREAT如果内核中对应key值的共享内存不存在,则新建一个共享内存并返回该共享内存的句柄;如果已存在,则直接返回该共享内存的句柄
IPC_CREAT IPC_EXCL如果不存在对应key值的共享内存则新建一个共享内存并返回该共享内存的句柄;如果已存在,则出错返回

注意:

  • IPC_CREAT:一定可以获得一个共享内存,但是无法不一定是新建的共享内存。
  • IPC_CREAT | IPC_EXCL:只有shmget调用成功才会返回共享内存,并且该共享内存一定是新的。

创建共享内存shm,示例代码:server进程代表服务端,client代表客户端,server进程使用 IPC_CREAT | IPC_EXCL组合创建新的共享内存,client直接用 IPC_CREAT获取对应key值的共享内存即可。

//makefile

  1 .PHONY:all
  2 all:client server
  3 
  4 client:client.cc
  5     g++ -o $@ $^ -std=c++11
  6 server:server.cc
  7     g++ -o $@ $^ -std=c++11                                                                                                                                                      
  8 
  9 .PHONY:clean
 10 clean:
 11     rm -f client server
//comm.hpp

    1 #ifndef __COMM_HPP__
    2 #define __COMM_HPP__ 
    3 
    4 #include <iostream>
    5 #include <cerrno>
    6 #include <cstdio>
    7 #include <cstring>
    8 #include <cassert>
    9 #include <string>
   10 #include <sys/ipc.h>
   11 #include <sys/shm.h>
   12 #include <sys/types.h>
   13 #include <sys/stat.h>
   14 
   15 
   16 
   17 using namespace std;
   18 
   19 #define PATHNAME "."
   20 #define PROJID 0x6666
   21 
   22 const int gsize = 4096;//共享内存的大小
   23 
   24 //获取key值
   25 key_t getKey()
   26 {
   27     key_t k = ftok(PATHNAME,PROJID);
   28     if(k==-1)
   29     {
   30         cerr << errno << ":" << strerror(errno) << endl;
   31         exit(1);
   32     }
   33     return k;
   34 }
   35 //将key值以十六进制的形式输出
   36 string toHex(int x)
   37 {
   38     char buffer[64];
   39     snprintf(buffer,sizeof(buffer),"0x%x",x);
   40     return buffer;
   41 }                                                                                                                                                                              
   42 
   43 static int createShmHelper(key_t k, int size, int flag)
   44 {
   45     int shmid = shmget(k, gsize, flag);
   46     if(shmid == -1)
   47     {
   48         cerr << "error: "<<errno<<":"<<strerror(errno)<<endl;
   49         exit(2);
   50     }
   51     return shmid;
   52 }
   53 //一个进程创建共享内存,一个进程获取
   54 //这里server服务端创建,客户端获取
   55 int createShm(key_t k,int size)
   56 {
   57     umask(0);
   58     return createShmHelper(k,size,IPC_CREAT | IPC_EXCL | 0666);
   59 }
   60 int getShm(key_t k,int size)
   61 {
   62     return createShmHelper(k,size,IPC_CREAT);
   63 
   64 } 
   65 
   66 #endif
//server.cc服务端

  1 #include"comm.hpp"
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     //1.创建key
  7     key_t k = getKey();
  8     cout << "server: " << toHex(k) << endl;
  9 
 10     //2.创建共享内存
 11     int shmid = createShm(k,gsize);
 12     cout << "server shmid: "<< shmid << endl;
 13 
 14                                                                                                                                                                                  
 15 
 16     return 0;
 17 }
//client.cc

  1 #include"comm.hpp"
  2 #include<unistd.h>
  3 int main()
  4 {
  5     //1.获取key值
  6     key_t k = getKey();
  7     cout << "client: "<<toHex(k) << endl;
  8 
  9     int shmid = getShm(k,gsize);
 10     cout << "client shmid: " << shmid << endl;
 11                                                                                                                                                                                  
 12     return 0;
 13 }

运行结果:客户端和服务端获取到的key值和shmid都是一样的
在这里插入图片描述
注意: key是在内核层面上保证共享内存唯一性的方式,而shmid是在用户层面上保证共享内存的唯一性,key和shmid之间的关系类似于文件inode和文件描述符fd之间的的关系。

ipcs命令

使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看它们之间某一个的相关信息,可以选择携带以下选项:

  • -q:列出消息队列相关信息。
  • -m:列出共享内存相关信息。
  • -s:列出信号量相关信息。
    在这里插入图片描述

共享内存的释放

当我们在某一个程序中使用shmget()接口创建了共享内存之后,当该进程退出,此时共享内存的生命周期并不会像匿名管道和命名管道一样,随着进程的结束而释放。
在这里插入图片描述

我们再次运行这两个进程,由上图可以看出,server进程显示该共享内存已存在,因此得出结论:共享内存的生命周期不随进程,而是随OS操作系统,进程需要主动删除所创建的共享内存,同时也说明了IPC资源是由内核提供并维护的。

那么接下来介绍两种释放共享内存的方法:

1.使用命令释放共享内存资源

使用ipcrm命令释放共享资源:

ipcrm -m shmid

在这里插入图片描述

2.使用程序函数释放对应的共享内存资源

控制共享内存我们需要用shmctl函数,shmctl函数的函数原型如下:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

解读3个函数参数

  • shmid:是由shmget(创建共享内存函数)返回的共享内存标识码-句柄
  • cmd:表示控制动作
  • buf:指向一个保存着(共享内存的模式状态和访问权限-即属性)的数据结构

返回值:成功返回0,失败返回-1

第2个参数的有关选项如下:

命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

在comm.hpp文件中添加删除共享内存函数,并在服务端调用该函数,随着进程的退出,共享内存也会被释放。

//删除共享内存段
void delShm(int shmid)
{
    int n = shmctl(shmid,IPC_RMID, nullptr);
    assert(n!=-1);
   (void)n;                                                                                                                                                                   
}       

共享内存的关联与解除

我们不是单单把共享内存创建出来就行了,我们还要将需要获取这块共享内存的进程与对应的共享内存关联起来,通信结束后,解除关联,最后进行共享内存的释放。

共享内存的关联

关联共享内存需要用的函数是:shmat,shmat函数的函数原型如下:

void *shmat(int shmid, const void *shmaddr, int shmflg);

解读参数:

  • shmid:是由shmget(创建共享内存函数)返回的共享内存标识码-句柄。
  • shmaddr:按我们的指定将共享内存映射到进程地址空间的某一地址处,通常设置为NULL,让OS自助决定一个合理的位置。
  • shmflg:表示关联共享内存设置的属性。设置为0,默认具有读写权限;SHM_RDONLY表示只读操作

返回值:

  • 调用成功,返回共享内存映射到虚拟地址进程空间的起始地址。
  • 调用失败,返回-1

示例代码:
在这里插入图片描述

解除与共享内存的关联

取消共享内存与进程地址空间之间的关联我们需要用shmdt函数,shmdt函数的函数原型如下:

int shmdt(const void *shmaddr);

解读参数:

  • shmaddr:进程所关联的共享内存的起始地址,即调用shmat函数时得到的返回值

返回值:

  • shmdt调用成功,返回0。
  • shmdt调用失败,返回-1。

示例代码:
在这里插入图片描述

代码演示

下面是认识,使用,验证共享内存的简单的测试代码,以便我们更好的理解共享内存。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

C++版封装

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

共享内存的大小

共享内存的大小是以PAGE页(4kb)为单位的,用户需要多大,OS给予多大,不过如果超过特定的页,OS直接向上对齐到4kb的整数倍。

共享内存的特点

共享内存没有任何的保护机制(同步互斥),当共享内存中无数据时,进程依旧可以从共享内存中读取数据,而我们之前学过的管道,当管道当中的数据被读完了,操作系统read接口就会返回-1。其原因在于管道是通过系统接口通信的,共享内存是直接通信。

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

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

相关文章

C++ --引用与函高级

引用 定义&#xff1a;给变量起别名。 语法&#xff1a;数据类型&别名原名。 int a10; int &ba; //(此时的a和b都指向了同一块空间&#xff0c;都可对里面的元素进行修改) 对这两个变量名字任意一个进行修改&#xff0c;两变量的结果都会发生变化 引用注意事项 1.引…

211大学,4个计算机应届生,真实求职情况~

每年6月&#xff0c;毕业就业是一个绕不开的永恒话题&#xff01;2024届秋招即将开始&#xff0c;你们都找到工作了吗&#xff1f;是否还在犹豫、焦虑、没有头绪…… 先知先觉的那一批人&#xff0c;总会未雨绸缪&#xff0c;从前辈那里汲取经验。 播妞采访了4位211计算机应届…

vue+leaflet笔记之地图放大镜

vueleaflet笔记之地图放大镜 文章目录 vueleaflet笔记之地图放大镜开发环境使用教程安装依赖库使用简介 详细源码(Vue3) 本文介绍了Web端使用 Leaflet开发库显示地图放大镜的方法 (底图来源:中科星图)&#xff0c;结合 leaflet.magnifyingglass插件可以为Leaflet图层添加“放…

SHAP显示原始特征

1.问题描述 SHAP用于特征解释&#xff0c;对于机器学习方法往往需要对原始特征进行编码&#xff0c;而SHAP在绘制单个样本时&#xff0c;会显示每个特征及其取值&#xff0c;而这个取值已经是编码后的&#xff0c;通常无法确定其含义。如&#xff1a; 下图所示的拍卖公司、城市…

【软件设计师暴击考点】数据结构高频考点暴击系列

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

MarkDown语法学习 —— LaTex数学公式 数学符号

文章目录 01 | &#x1f6d1; LaTex公式模式02 | &#x1f6a6;上下标符号 & 数学符号03 | &#x1f6a5; 运算符 LaTeX&#xff0c;作为广义上的计算机标记语言&#xff08;比如HTML&#xff09;&#xff0c;它继承了计算机语言的光荣传统&#xff0c;通过一些简单的代码表…

入门防火墙基本原理

防火墙是可信和不可信网络之间的一道屏障&#xff0c;通常用在LAN和WAN之间。它通常放置在转发路径中&#xff0c;目的是让所有数据包都必须由防火墙检查&#xff0c;然后根据策略来决定是丢弃或允许这些数据包通过。例如&#xff1a; 如上图&#xff0c;LAN有一台主机和一台交…

Nginx基础配置以及实操

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、nginx主配置文件1.全局配置2.I/O事件配置3.HTTP配置4.检查文件是否配置正确5.浏览器测试6.总配置文件图示6.1 nginx总配置文件的三个模块6.2 HTTP文件配置的图示…

通过Python+Selenium获取我的所有文章质量分

文章目录 前言一、背景二、设计三、环境准备四、开发1.查询文章数量1.1请求url1.2响应结果1.3代码 2.获取每页文章列表2.1请求url2.2响应结果 3.获取所有文章url3.1代码 4.保存excle方法4.1代码 5.查询所有文章质量分并写入excle5.1代码 6.程序入口6.1代码 7.需要引入的模块 五…

Copilot是GPT的理想应用模式吗?

自OpenAI发布ChatGPT以来&#xff0c;LLM持续火热&#xff0c;各大公司纷纷入场。但近一段时间以来&#xff0c;我观测到的LLM应用场景&#xff0c;基本都是Copilot的形式&#xff0c;即以对话为基础的辅助应用。尽管体验起来十分的高大上&#xff0c;但我能明确感受到&#xf…

微信小程序发布上线全流程 注册 开发 上传审核

微信小程序接入全流程 微信小程序从零到上线&#xff0c;要经历小程序账号注册开通、完善小程序信息、开发小程序、提交小程序审核及发布四个过程&#xff1a; 以下是微信小程序发布上线的详细流程&#xff1a; 确认小程序信息&#xff1a;在微信公众平台注册并登录后&#…

【C语言进阶】预处理那些事儿

文章目录 &#x1f4d6;预定义符号&#x1f4d6;预处理指令&#x1f4d6;#define &#x1f516;#define定义标识符&#x1f516;#define定义宏&#x1f516;#define替换规则&#x1f516;#和##&#x1f516;带有副作用的宏参数&#x1f516;宏和函数的对比&#x1f516;命名约定…

正点原子 STM32F4/F7水星 开发板资料连载第二十一章LTDC LCD实验

正点原子 STM32F4/F7水星 开发板资料连载第二十一章LTDC LCD实验 - 知乎 (zhihu.com) 本章我们将通过 STM32F767 的 LTDC 接口来驱动 RGBLCD 的显示&#xff0c;另外&#xff0c;STM32F767 的 LTDC 还有DMA2D 图形加速&#xff0c;我们也顺带进行介绍。本节分为三个部分&#…

Kubernetes学习笔记-kubernetes应用扩展(1)-自定义API对象20230622

1、CustomResourceDefinitions介绍 开发者只需要只需向kubernetes api服务器提交CRD对象&#xff0c;即可定义新的资源类型。成功提交CRD之后&#xff0c;就能通过API服务器提交JSON清单或者YAML清单的方式创建自定义资源&#xff0c;以及其他kubernetes资源实例 创建一个CRD…

SpringCloud Alibaba入门6之Nacos配置

一、基本概念 1.命名空间 用于区分环境&#xff0c;开发、测试、生产环境等。 2.配置分组 多个配置文件放在一起&#xff0c;形成组。 3.配置集 一般指一个配置文件 4.配置集ID 这个配置文件全局唯一ID 5.配置项 配置的键值对 二、引入Nacos配置中心 我们在用户模块…

探索安卓内容提供者:构建、访问和管理数据【复习】

文章目录 一 ContentProvider1.1 数据模型- **ContentProvider 使用基于数据库模型的简单表格来提供需要共享的数据**&#xff0c;在该表格中&#xff0c;每一表示一条记录&#xff0c;而每一列代表特定类型和含义的数据&#xff0c;并且其中每一条数据记录都包含一个名为“_ID…

团体程序设计天梯赛-练习集L1篇⑤

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

概率论与数理统计教程第五章节笔记

参考书籍&#xff1a;概率论与数理统计教程第三版 茆诗松 程依明 濮晓龙 编著 文章声明&#xff1a;如有错误还望批评指正 文章目录 ξ 5.1 \xi5.1 ξ5.1总体与样本 ξ 5.2 \xi5.2 ξ5.2样本数据的整理与显示Python绘制直方图Python绘制茎叶图 ξ 5.3 \xi5.3 ξ5.3统计量及其分…

非煤电子封条系统 yolov7

非煤电子封条系统通过yolov7python网络模型技术&#xff0c;非煤电子封条系统利用智能化AI视频分析&#xff0c;实时监测分析矿井出入井人员人数变化、非煤及煤矿生产作业状态等情况&#xff0c;自动生成、推送报警信息&#xff0c;提示相关人员采取应急措施。Python是一种由Gu…

最火的几款STM32 F系列对比

最火的几款STM32 F系列对比 最常用且相对热门STM32F103系列STM32F407系列STM32F429系列STM32F746系列 最常用且相对热门 在STM32F系列中&#xff0c;以下是一些最常用且相对热门的芯片&#xff1a; STM32F103系列&#xff1a;这个系列是STM32F系列中最受欢迎的型号之一。它基于…