Linux——共享内存

Linux——共享内存

  • 什么是共享内存
  • 共享内存原理
  • Linux下共享内存的接口
    • 创建/获取共享内存:shmget
      • ftok函数
    • 映射共享内存到进程地址空间:shmat
  • 解除共享内存映射:shmdt
    • 删除共享内存段:shmctl
  • 利用共享内存进行通信

我们之前学习了匿名管道命名管道来进行进程之间的通信,其实除了管道之外,我们还有一种方式就是共享内存

什么是共享内存

共享内存(Shared Memory)是进程间通信(Inter-Process Communication, IPC)的一种方式,它允许两个或多个进程访问同一块物理内存区域,从而实现数据的快速、直接交换。在操作系统层面,共享内存指的是操作系统创建或映射到多个进程地址空间的同一块内存区域,使得这些进程可以直接读写这块内存,如同访问本进程的私有内存一样。

共享内存的工作机制通常涉及以下步骤:
创建共享内存:通过系统调用(如Unix/Linux下的shmget函数)创建一个共享内存段,并指定其大小和权限。
映射共享内存:每个需要访问共享内存的进程都需通过系统调用(如shmat函数)将共享内存段映射到自身的地址空间。
访问共享内存:映射成功后,进程就可以像访问普通内存一样读写这块共享内存区域,从而实现实时的数据交换。
同步与互斥:由于多个进程可以同时访问同一内存区域,为了避免数据竞争和不一致,通常需要借助其他同步机制(如信号量、互斥锁等)来保证对共享内存的有序和安全访问。

共享内存的优点在于速度快,因为它是内存级别的通信,没有额外的复制开销。缺点则是需要用户程序自行处理同步问题,否则容易引发竞态条件和死锁等问题。

共享内存原理

共享内存是操作系统支持的一种进程间通信(IPC,Inter-Process Communication)机制,它允许多个进程访问同一块物理内存区域,从而实现高效的数据共享和通信。以下是共享内存的基本原理:

  1. 内存区域创建
    在操作系统层面,通过系统调用(如Unix/Linux下的shmget())创建一块共享内存区域。创建时需要指定一个键值(通常通过ftok()函数生成)来标识这块内存,同时指定内存区域的大小。
  2. 内存映射
    一旦共享内存区域被创建,各个希望参与通信的进程可以调用shmat()函数,将这块共享内存映射到它们各自的地址空间。映射成功后,每个进程都可以通过本地内存地址访问这块共享内存,就像访问普通的内存一样。
  3. 数据同步
    由于多个进程可以直接读写同一块内存区域,因此必须有适当的同步机制来保证数据的一致性和完整性,如互斥锁(mutexes)、信号量(semaphores)或其他同步原语,以避免数据竞争(race conditions)。
  4. 内存解除映射和删除
    当进程不再需要访问共享内存时,可以调用shmdt()函数来解除映射关系,解除映射后,进程无法再通过本地地址访问共享内存。当所有进程都解除映射后,如果有必要,可以通过shmctl()函数并设置适当的命令来删除共享内存区域。
  5. 优点
  • 高效性:由于数据不需要在进程间复制,共享内存是最快捷的IPC方式之一。
  • 低开销:相比消息队列、管道等其他IPC机制,共享内存不需要额外的复制和包装开销。
  1. 挑战
  • 同步复杂性:确保多个进程对共享内存的并发访问是一致的是一项复杂任务,需要良好的同步策略和编程技巧。
  • 内存管理:操作系统需要跟踪哪些进程正在使用共享内存,何时应该回收内存资源。

简而言之,共享内存的核心原理是利用操作系统提供的功能,让多个进程可以直接读写同一块物理内存区域,从而实现进程间的数据交换。为了正确使用共享内存,程序员需要谨慎处理同步问题,并且在进程生命周期中妥善管理内存映射和解除映射。
在这里插入图片描述

Linux下共享内存的接口

在Linux系统中,使用共享内存进行进程间通信涉及以下几个关键的系统调用接口:

创建/获取共享内存:shmget

在这里插入图片描述

shmget 函数用于创建一个新的共享内存段或者获取已存在的共享内存标识符(shmid)。参数说明如下:
key:通常是通过 ftok() 函数生成的一个键值,用来唯一标识共享内存段。
size:要创建的共享内存段的大小(字节数)。
shmflg:标志位,可以指定创建模式(如 IPC_CREAT 表示若不存在则创建)、权限位(如 S_IRUSR | S_IWUSR 表示所有者具有读写权限)和其他选项。

ftok函数

ftok() 函数在 Unix 和 Linux 系统中用于生成一个用于进程间通信(IPC)的唯一键值(key),尤其是配合 System V IPC 机制中的消息队列(message queues)、信号量(semaphores)以及共享内存(shared memory)。这个键值是系统内核用来识别不同 IPC 资源的关键标识符。

函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int id);

参数说明:

  • pathname:是一个字符串,代表系统中一个已存在的文件的路径名。通常会选择应用程序能够访问并知道其稳定的路径,例如可执行文件或配置文件。
  • id:一个整数值,作为项目的子序列号。它可以被用来区分同一文件的不同 IPC 资源,不过通常设置为非零的常数值即可。
    函数返回:
  • 如果成功,返回一个类型为 key_t 的 IPC 键值,该键值是基于给定的文件路径和项目 ID 计算得出的,理论上在同一系统上应当是唯一的。
  • 如果失败,返回 (key_t) -1,并且会设置 errno 以指示出错原因。

我们结合这两个来创建一块共享内存

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<iostream>const char* path_name = "../include";
#define MY_PROJECT_ID 1
#define MYSIZE 4096//获取key值
key_t GetKey()
{key_t key = ftok(path_name,MY_PROJECT_ID);if(key < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(1);}return key;
}//获取shmid值
int Getshmid(const key_t& key)
{int shmid = shmget(key,MYSIZE,IPC_CREAT | 0666);if( shmid == -1){perror("shmget fail");exit(EXIT_FAILURE);}return shmid;
}

这个时候,我们再来看:

#include"shmnt.hpp"int main()
{key_t key = GetKey();int shmid = Getshmid(key);return 0;
}

我们创建好了共享内存,我们可以用ipcs -m来查看:
在这里插入图片描述

我们创建好了共享内存,下一步就是将共享内存连接到进程的地址空间:

映射共享内存到进程地址空间:shmat

在这里插入图片描述

shmat 函数将共享内存段连接到调用进程的地址空间中。
shmid:由 shmget 返回的共享内存标识符。
shmaddr:通常设为 NULL,表示让系统选择合适的地址来映射;也可以指定特定地址,但这样做有风险且需要额外注意。
shmflg:标志位,比如 SHM_RDONLY 表示以只读方式映射共享内存。

 //将该shmid挂到虚拟地址空间
//将该shmid挂到虚拟地址空间void* Attachshmid(int shmid){return shmat(shmid,nullptr,0);}

这个时候,我们再来看:

int main()
{key_t key = GetKey();int shmid = Getshmid(key);void *share_adderss = Attachshmid(shmid);if(share_adderss == (void*)-1){perror("shmat fail");return 1;}else{std::cout<<"has be attached"<<std::endl;sleep(10);}return 0;
}

在这里插入图片描述
我们看到,我们的连接数从0变成了1,意味着程序运行期间,进程已经将该共享内存段映射到了它们自己的地址空间

解除共享内存映射:shmdt

对共享内存段进行取消映射。

int shmdt(const void * __shmaddr);

shmaddr:这是通过 shmat() 函数成功映射共享内存时返回的地址指针。成功返回0,错误返回1

我们写这样一段函数:

 //解除void Disattachshmid(const void* share_address){if(shmdt(share_address) == -1){perror("shmdt fail");return;}std::cout<<"has be disattached process"<<std::endl;sleep(10);}
#include"shmnt.hpp"int main()
{key_t key = GetKey();int shmid = Getshmid(key);void *share_adderss = Attachshmid(shmid);if(share_adderss == (void*)-1){perror("shmat fail");return 1;}else{std::cout<<"has be attached"<<std::endl;sleep(10);}Disattachshmid(share_adderss);return 0;
}

在这里插入图片描述
执行这段脚本,我们可以监视共享内存的使用情况:
在这里插入图片描述
我们看到,已经成功将映射关系消除了。

删除共享内存段:shmctl

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

对共享内存段进行控制操作,其中 cmd 参数可以是 IPC_RMID 来删除共享内存段。
shmid:共享内存标识符。
cmd:控制命令,如 IPC_RMID 表示删除。
buf:如果是其他命令可能需要指向 shmid_ds 结构体的指针,但在删除操作中通常设置为NULL。

//删除共享内存void Deletshare(int shmid){if(shmctl(shmid,IPC_RMID,nullptr) == -1){perror("shcmtl fail");exit(EXIT_FAILURE);}else{std::cout<<"has successfully deleted"<<std::endl;}}
#include"shmnt.hpp"int main()
{key_t key = GetKey();int shmid = Getshmid(key);void *share_adderss = Attachshmid(shmid);if(share_adderss == (void*)-1){perror("shmat fail");return 1;}else{std::cout<<"has be attached"<<std::endl;sleep(10);}Disattachshmid(share_adderss);Deletshare(shmid);return 0;
}

在这里插入图片描述

利用共享内存进行通信

我们之前的大部分工作都只是把准备工作做好了,我们还没有进行通信,我们可以利用共享内存进行通信:
我们准备一个client.cc:

#include"shmnt.hpp"int main()
{key_t key = GetKey();int shmid = Getshmid(key);//挂载int* share_adderss = (int*)Attachshmid(shmid); //强转为int*类型//进行通信for(int i = 0; i < 10; i++){share_adderss[i] = i; //写入数据,以便读取std::cout<<"client say "<< share_adderss[i] <<std::endl;sleep(1);}//取消挂载Disattachshmid(share_adderss);return 0;
}

再准备一个server.cc,读取client.cc写入共享内存中的内容:

#include"shmnt.hpp"int main()
{key_t key = GetKey();int shmid = Getshmid(key);int* share_adderss = (int*)Attachshmid(shmid); //强转为int*类型if(share_adderss == (void*)-1){perror("shmat fail");return 1;}else{std::cout<<"has be attached"<<std::endl;//sleep(10);}//进行通信int i = 0;while(true){if(i < 10){std::cout<<"server say: "<< share_adderss[i] << std::endl;i++;sleep(1);}else{break;}}Disattachshmid(share_adderss);Deletshare(shmid);return 0;
}

在这里插入图片描述
这里注意,共享内存实现通信并不保证同步机制,如果我这里写入的速度变慢一点:
在这里插入图片描述就会出现乱读,这时候我们要保证手动保证同步机制。

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

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

相关文章

关于未来自我的发展和一些学习方法(嵌入式方向)

我是一名大二的学生&#xff0c;考研还是就业&#xff0c;到底是重视专业课还是重视数学英语&#xff0c;这些问题一直困扰了我很久&#xff0c;但如今已经有了一些浅显的认识&#xff0c;所以才会想写这样一篇文章来记录一下自己的状态和未来的规划 下面的看法都是个人的看法&…

蓝桥杯刷题_day7_动态规划_路径问题

文章目录 DAY7下降路径最小和最小路径和地下城游戏 DAY7 下降路径最小和 【题目描述】 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始&#xff0c;并从每一行中选择一个元…

通过node 后端实现颜色窃贼 (取出某个图片的主体rgb颜色 )

1.需求 我前端轮播图的背景色 想通过每一张轮播图片的颜色作为背景色 这样的话 需要通过一张图片 取出图片的颜色 这个工作通过前端去处理 也可以通过后端去处理 前端我试了试 color-thief 的插件 但是 这个插件是基于canvas 的模式来的 我需要在小程序中使用这个插件 而且是…

Qt 完成图片的缩放拖动

1. 事件和函数 主要使用事件paintEvent(QPaintEvent *event)和drawTiledPixmap函数实现绘图。 paintEvent事件在改变窗口大小、移动窗口、手动调用update等情形下会被调用。需先了解下绘图该函数的用法。 - QPainter::drawTiledPixmap(int x, int y, int w, int h, const QPi…

web布局——说清楚fixed布局

极限省流 想要fixed做导航页面&#xff1a;指定清楚top、left、right、bottom&#xff0c;没指定清楚布局位置就会采用默认的方式&#xff1a; 0&#xff09;父元素的padding&#xff1a;fixed元素相对位移 1&#xff09;同级元素是fixed元素&#xff1a;覆盖 2&#xff09…

自定义类型:【联合体和枚举】

一.联合体 1.联合体类型的声明 联合体像结构体一样&#xff0c;也是有一个或者多个成员组成&#xff0c;当然也可以不同的类型。但不同的是&#xff0c;比编译器只为最大的成员分配足够的内存空间&#xff0c;所有成员共用同一块内存空间。所以联合体也叫做&#xff1a;共用体…

GEE22:基于目视解译的土地利用分类(随机森林监督分类)

采样点信息&#xff1a; 设置一下采样点参数&#xff1a; 代码&#xff1a; //设置研究区位置 var table ee.FeatureCollection("users/cduthes1991/boundry/China_province_2019"); var roi table.filter(ee.Filter.eq(provinces,beijing)); Map.centerObjec…

公司服务器被.rmallox攻击了如何挽救数据?

公司服务器被.rmallox攻击了如何挽救数据&#xff1f; .rmallox这种病毒与之前的勒索病毒变种有何不同&#xff1f;它有哪些新的特点或功能&#xff1f; .rmallox勒索病毒与之前的勒索病毒变种相比&#xff0c;具有一些新的特点和功能。这种病毒主要利用加密技术来威胁用户&am…

一、图片隐写[Stegsolve、binwalk、010editor、WaterMark、BlindWaterMark、文件头尾]

工具配置 1.Stegsolve 工具地址&#xff1a;http://www.caesum.com/handbook/Stegsolve.jar 解释&#xff1a;该工具需要安装jar包后才能配合使用&#xff0c;下面同时会给出快速打开工具的代码&#xff0c;需要两个文件&#xff0c;启动的时候启动vbs文件 start.bat java …

基于SpringBoot的“财务管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“财务管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统登录界面图 管理员功能界面图…

解决GNU Radio+USRP实现OFDM收发在接收端QPSK星座图映射无“抖动”问题

文章目录 前言一、遇到的问题二、解决方案三、重新编译安装四、验证五、资源自取 前言 本文记录在 GNU RadioUSRP 实现 OFDM 收发时&#xff0c;在接收端 QPSK 星座图映射无“抖动”问题的解决方法&#xff0c; 一、遇到的问题 我遇到的问题是&#xff0c;现在搭建的 OFDM 模…

定义类强化——定义Goods类表示商品

现需要编写一个计算商品总价值的程序&#xff0c;现要求&#xff1a; 1、定义一个表示商品的类&#xff1a;Goods&#xff0c;Goods类要包含&#xff1a; 一个私有成员变量String name表示商品的名称&#xff1b;一个私有成员变量float price表示商品的价格&#xff0c;并定义…