【Linux】详解如何利用共享内存实现进程间通信

news/2024/5/1 7:00:15

一、共享内存(Shared Memory)的认识

        共享内存(Shared Memory)是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间,从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信,因为数据不需要在不同的进程间进行拷贝。

        在操作系统中,共享内存通常是通过映射一段能被其他进程所访问的内存实现的。一个进程可以创建一个共享内存段,并将该段连接到其地址空间中。其他进程也可以将这段共享内存连接到它们的地址空间中。这样,所有进程都可以访问同一段内存,实现数据的共享。

        在内核中共享内存可以存在很多个,操作系统必须先创建描述共享内存的结构体,再把这些结构体组织起来管理。为了保证两个或者是多个进程看到同一个共享内存,就要给每一个共享内存提供唯一性的标识

二、创建共享内存的方法

        创建共享内存的方法为shmget,其中第一个参数为key,key就是共享内存在内核中的唯一标识。size是要设置的共享内存的大小(在内核中,共享内存是以4kb为基本单位的,我们在给共享内存分配大小的时候最好也是分配4kb的整数倍的大小。)。还有一个参数shmflg,shmflg可以有很多选项,但最常见的有两个:

  1. IPC_CREAT:如果共享内存不存在, 就创建之, 如果共享内存已经存在, 直接获取它。
  2. IPC_EXCL:不能单独使用, 没意义。
  3. IPC_CREAT | IPC_EXCL:如果共享内存不存在, 就创建之, 如果共享内存已经存在,就出错返回!!如果共享内存创建是成功的, 则一定是一个新的共享内存! 

        如果shmget成功获取或创建了共享内存段,它会返回一个非负整数,这个整数是共享内存段的标识符(也称为共享内存段的ID)。这个标识符在后续的共享内存操作中(如shmat和shmdt)会被使用。 

 2.1、key的获取

        这里的pathname是一串文件路径,proj_id是一个整数,这两个参数由用户随意指定,操作系统底层通过特定的算法帮我们形成一个key值,如果形成失败-1被返回。如果成功这个key值就会被设置进描述共享内存的结构体中用来标识这块共享内存的唯一性。通过给两个进程或者是多个进程传入同样的pathname和proj_id就能让它们看到同一块共享内存。 

三、查看共享内存的方法

采用ipcs指令可以查看系统中指定用户创建的共享内存,消息队列和信号量。

ipcs -m:查看系统中指定用户创建的共享内存

 ipcs -q:查看系统中指定用户创建的消息队列

  ipcs -s:查看系统中指定用户创建的信号量

四、指令删除共享内存的方法

ipcrm -m shmid(共享内存id):删除用户指定的共享内存。 

五、代码实现共享内存通信

5.1、获取key值

其实获取key可以封装成函数也可以不封装,这里我是将其封装成函数了。

key_t get_key(const char* pathname, int proj_id)
{key_t key = ftok(pathname, proj_id);//成功返回key值,失败返回-1if(key == -1){cout << "获取key值失败,原因是:" << strerror(errno) << endl;exit(1);}return key;
}

5.2、创建共享内存

        共享内存是为了实现两方或是多方通信的,这里我就设置成为两方通信。所以一定是一方创建共享内存,另一方获取共享内存。要注意的是,共享内存也是有权限的,所以创建的一方需要指明创建的共享内存的权限。

int get_or_create_shared_memory(key_t key, int size, int flag)
{int shmid = shmget(key, size, flag);//成功返回共享内存标识符,失败返回-1if(shmid == -1){cout << "共享内存创建失败,原因是:" << strerror(errno) << endl;exit(2);}return shmid;
}int create_shared_memory(key_t key, int size)
{return get_or_create_shared_memory(key, size, IPC_CREAT | IPC_EXCL | 0666);
}int get_shared_memory(key_t key, int size)
{return get_or_create_shared_memory(key, size, IPC_CREAT);
}

  5.3、挂接共享内存/去挂接共享内存

        shmid表示要挂接的共享内存的shmid,shmaddr表示要将该共享内存挂接到进程地址空间的什么位置,其实这个我们不用管,操作系统会自行帮我们挂接,可以直接设置为nullptr,shmflg表示可以对该共享内存做什么操作,设置为0默认是可读可写。 如果挂接成功,返回挂接到进程地址空间的地址,如果挂接失败,返回-1。

5.4、同步操作

        如果读写共享内存的进程间没有进行同步操作,可能就会发生脏读,即写入的数据和读到的数据不一致。所以要进行进程同步操作。这里我借助了管道来进行同步操作,即写方写完了再唤醒读方来读。

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cstdio>using namespace std;#define MODE 0666 //权限
#define NAME "./fifo.txt"//定义命名管道结构体
class Fifo
{
private:string _name; // 文件路径加文件名
public:Fifo(const string &name): _name(name){int n = mkfifo(_name.c_str(), MODE);if (n == 0)cout << "创建管道成功!" << endl;elsecout << "创建管道失败!原因是:" << strerror(errno) << endl;};~Fifo(){int n = unlink(_name.c_str());if (n == 0)cout << "删除管道成功!" << endl;elsecout << "删除管道失败!原因是:" << strerror(errno) << endl;};
};//同步结构体
class Sync
{
private:int rfd;int wfd;
public:void open_read(){rfd = open(NAME, O_RDONLY);if (rfd == -1){cout << "读打开管道失败!" << endl;exit(1);}}void open_write(){wfd = open(NAME, O_WRONLY);if (wfd == -1){cout << "写打开管道失败!" << endl;exit(1);}}int wait(){int ret = 0;int n = read(rfd, &ret, sizeof(int));return n;}void wake_up(){int ret = 0;int n = write(wfd, &ret, sizeof(int));}
};

读写方分别创立一个sync对象,在读写的时候分别调用wait和wake_up方法进行同步。 

5.5、删除共享内存

         进程创建的共享内存如果在进程结束时没有释放,则共享内存会一直存在。也就是说,共享内存的声明周期是随内核的,如果我们没有主动去释放共享内存,除非重启系统,否则共享内存一直存在。所以在写端当你已经不写了时要将共享内存删掉。

 shmctl系统调用加上IPC_RMID选项可以删除共享内存。

void shm_del(int shmid)
{int ret = shmctl(shmid, IPC_RMID, nullptr);if (ret == -1)cerr << "删除共享内存失败" << endl;elsecout << "删除共享内存成功" << endl;
}

         shmctl的第三个选项可以传入一个描述共享内存的对象的地址来获取该共享内存的属性,如果只是删除共享内存,直接设置为nullptr即可。

六、总结 

         共享内存不提供进程间协同的任何机制。但是共享内存是所有进程间通信机制中速度最快的。因为共享内存是通过页表直接与进程地址空间中的地址产生关联的,写方只需要将数据拷贝到共享内存中,读方直接通过地址就能访问内容,无需进行数据的拷贝,直接就提高了访问数据的速度。也就是说共享内存进行进程间通信只需要一次数据的拷贝,而我们之前提到的管道通信,都是读方调用write函数将数据写入内存(进行了一次拷贝),读方再调用read函数将数据拷贝到用户层,要进行两次数据的拷贝。

七、说明

        因为实现共享内存的文件数较多,所以以上并不是全部代码,如果想获取全部实现代码,请移步到本人码云:C++代码: C++代码保存的地方 - Gitee.com


http://www.mrgr.cn/p/26836853

相关文章

Qt 4 QPushButton

Qt 常用控件 QPushButton 实例 Push Button:命令按钮。 入口文件 main.cpp #include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);MainWindow w;w.show();return a.exec(); }头文件 mainwindow.h …

jenkins通过pipeline部署springboot项目

部署方案&#xff1a; 1、springboot项目不保存部署的pipeline或dockerfile构建脚本等与部署相关的问文件&#xff0c;业务项目只需关心业务&#xff0c;能够正常构建为jar包即可 2、新建一个代码仓库&#xff0c;用于保存项目需要构建的Jenkinsfile 3、jenkins配置pipeline地址…

异常体系

java中有许多异常: 异常捕获: 小例子: 灵魂一问: 灵魂二问: 当在try中出现异常,会new一个异常对象去与catch的异常对比,若可以接收new出来的异常对象那么就执行该catch中的内容 灵魂三问: 灵魂四问:抛出异常: 总结: throw写在方法内,try catch写在调用方法处 …

C++进阶——继承

前言&#xff1a;从这篇文章开始&#xff0c;我们进入C进阶知识的分享&#xff0c;在此之前&#xff0c;我们需要先来回顾一个知识&#xff1a; C语言有三大特性&#xff0c;分别是封装、继承和多态&#xff0c;而我们前边所分享的各种容器类&#xff0c;迭代器等&#xff0c;…

团队博客

项目原型展示说明 “冀网社区聘”——社区招聘项目 是否为日常生活问题感到困扰?是否在因不熟悉的工作愁眉苦脸?我们“冀网社区聘”平台致力于为客户提供快捷,直接的服务。在我们的“冀网社区聘”平台上,雇主可以轻松发布社区内或者跨社区招聘需求,而相应的求职者则可以浏…

JS混淆代码数据集构建方法

数据获取(1)公开JS数据集,比如CodeSearchNet; (2)自行构建JS数据集,爬取Github开源前端项目;数据描述Github中采集前端项目文件分类如下,从中提取JS文件数据预处理其中比较重要的步骤:(1)代码混淆:使用现有工具,如UglifyJS、Terser、babel-minify、JS-Obfuscator…

Java API之查询文档

1、查询指定id文档import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.client.RequestOptions; import org.util.ConnectElasticsearch;public class GetDoc {public static void main(String[] arg…

基于STM32智能垃圾桶系统设计(论文)_kaic

摘 要 城市的不断扩张建设&#xff0c;生活中产生了大量垃圾&#xff0c;垃圾桶现在是非常多的在我们生活中是不可或缺的一部分&#xff0c;环卫工人每天都是在整理收拾垃圾&#xff0c;会造成很多的麻烦&#xff0c;为了解决这个问题我们研究出了基于STM32智能垃圾桶系统设计…

[9] UE C++ Snake

思维导图背景地图制作 创建瓦片集角色素材GameMode功能 游戏开始控制食物的生成食物生成池(性能优化) /**形参如果是一个引用,且没有添加const关键字,代表实参想要借助形参修改值* param 是否指定生成时候的地址*/ void ASnakeGameModeBase::SpawnFood(FVector& SpawnLoc…

服务器raid卡,守护数据安全,赋能新质生产力

RAID卡,全称为独立冗余磁盘阵列卡,在数据中心、服务器、网络存储等领域得到广泛应用,RAID卡通过不同的RAID级别实现数据容错和冗余。例如,RAID 0主要适用于需要高速数据传输但对数据安全要求不高的场景,如数据的缓存;RAID 1使用镜像备份确保数据不因硬盘故障而丢失。然而…

Spring Boot 拦截器

拦截器是Spring 框架提供的核心功能之一&#xff0c;主要用于拦截用户的请求&#xff0c;在指定方法的前后根据业务需要执行代码。 例如登录场景&#xff0c;有可能我们访问一个网页时&#xff0c;我们的登录信息过期了&#xff0c;就需要重新登录&#xff0c;那么就可使用拦截…

夜莺监控 V7 第二个 beta 版本发布,内置集成故障自愈能力,简化部署

经过一个半月的打磨改进,夜莺监控 V7 第二个 beta 版本发布了,本次发布的主要亮点是内置集成故障自愈能力,简化架构,同时做了其他 19 项改进。一些重要的改进如下:feat: 集成故障自愈的能力,不需要再单独部署 ibex 模块了 refactor: 内置仪表盘和内置规则页面重构 refact…

cowa新的数据筛选代码

cowa新的数据筛选代码 代码地址&#xff1a; https://git.cowarobot.com/lhb/data_extracting 一阶段筛选 修改配置文件 config/common_stage.yamlversion: 3 services:de:image: harbor.cowarobot.cn/lhb/data:crpilot2.5-torch2.2environment:- CRPILOT_INSTALL_VERSIONx86…

JavaFx项目打包成exe,并集成Jre,使Java项目在任意机器运行

1.关键点:通过springboot打包插件,将项目依赖都打到一个jar包内。 以下是pom配置文件:<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1…

每天学点儿Python(6) -- 列表和枚举

列表是Python中内置的可变序列&#xff0c;类使用C/C中的数组&#xff0c;使用 [ ] 定义列表&#xff0c;列表中的元素与元素之间用英文逗号&#xff08; , &#xff09;分隔&#xff0c; 但是Python中列表可以存储任意类型的数据&#xff0c;且可以混存&#xff08;即类型可以…

react17 + antd4 如何实现Card组件与左侧内容对齐并撑满高度

在使用antd进行页面布局时&#xff0c;经常会遇到需要将内容区域进行左右分栏&#xff0c;并在右侧区域内放置一个或多个Card组件的情况。然而&#xff0c;有时我们会发现右侧的Card组件并不能与左侧的栏目对齐&#xff0c;尤其是当左侧栏目高度动态变化时。本文将介绍如何使用…

vue简单使用三(class样式绑定)

目录 对象的形式绑定&#xff1a; 数组的形式绑定&#xff1a; 内联样式Style 对象的形式绑定&#xff1a; 可以看到class中有两个值 数组的形式绑定&#xff1a; 可以看到也有两个值 内联样式Style style样式设置成功 完整代码&#xff1a; <!DOCTYPE html> <html…

小程序技术实现前端热更新的优势

小程序作为轻量级的移动应用形态,凭借其无需下载安装、即用即走的特性,迅速获得用户的青睐。同时,小程序技术也为前端热更新提供了天然的优势。通过 Service Worker 等机制,小程序可以拦截网络请求,动态更新前端代码,而无需用户重新下载应用。小程序技术是一种很有前景的…

互联网轻量级框架整合之MyBatis核心组件

在看本篇内容之前&#xff0c;最好先理解一下Hibernate和MyBatis的本质区别&#xff0c;这篇Hibernate和MyBatis使用对比实例做了实际的代码级对比&#xff0c;而MyBatis作为更适合互联网产品的持久层首选必定有必然的原因 MyBatis核心组件 MyBatis能够成为数据持久层首选框&a…

2024.4.16(周二)腾讯公益赛构思展示

团队成员:郑天羽 张晨旭 孙怡然产品介绍: