[Linux]进程间通信-共享内存与消息队列

目录

一、共享内存

1.共享内存的原理

2.共享内存的接口

命令行

创建共享内存

共享内存的挂接

去掉挂接

共享内存的控制

3.共享内存的使用代码

Comm.hpp--封装了操作接口

客户端--写入端

 服务器--读取端

4.管道实现共享内存的同步机制

二、消息队列

1.底层原理

2.使用接口

创建消息队列

发送消息

接收消息 

消息队列的控制


一、共享内存

1.共享内存的原理

        共享内存是进程间通信中最快的一个形式了,对于管道来说是复用了文件系统的代码,而共享内存(System V)是操作系统单独设计的一个模块用来实现进程间通信。

        对于进程来说通信的前提条件就是不同的进程要看到同一份资源空间,共享内存的实现则是操作系统在内存中单独使用了一块空间用于进程之间的通信。因为是在物理内存中的空间,所以如果进程想要使用的话,还是要通过页表的映射到虚拟地址空间当中,映射的位置在栈区和堆区之间的共享区内。

        一个计算机中,不可能只有两个进程进行通信,也不可能之有两个进程使用共享内存进行通信,那么对于多个共享内存,每个共享内存是哪些进程在使用,权限是什么等等都需要操作系统直到,所以还是先组织在描述的方式,使用结构体将共享内存描述起来,并使用链表等数据结构把多个共享内存管理起来。使用引用计数计数,当没有进程使用共享内存的时候在释放共享内存。

        那么当一个进程创建了共享内存之后,如何保证其他想要通信的进程能够找到该共享区呢?就需要提供一个唯一的标识符key标志一个共享内存区域。两个进程在创建共享区的时候,会约定一个key,一个进程用接口创建好共享内存之后,把这个key放入操作系统管理共享内存的结构体中,另一个进程通过key向操作系统索要该共享内存的起始地址,操作系统通过遍历共享内存链表找到该key值的共享内存,然后将起始地址返回给进程。这样两个进程都获取到了该共享内存的起始地址,就访问到了一份资源,就实现了进程间的通信了。

2.共享内存的接口

命令行

ipcs -m     查看系统中的System V共享内存

ipcrm -m  XX(shmid) 用于释放共享内存

创建共享内存

头文件  <sys/ipc.h>  <sys/shm.h>

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

        该函数用来创建或获取共享内存的标识符。这里的key值就用于表示共享内存段,size则是该共享内存的大小,shmflg是用于设置共享内存段的权限和其他属性。成功返回标识符,错误返回-1同时错误码被设置。

        key字段:这个可以是用户自定义的非零整数,但是这样的话,很容易重复,我们最好是让系统帮我们生成一个唯一的不重复是最好的选择。系统为我们提供了ftok函数,会根据传递的路径名和项目标识符来生成一个唯一的键值key。

头文件  <sys/type.h>  <sys/ipc.h>

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

        size字段:共享内存的大小尽量设置为4096的整数倍,因为底层是以4096byte为单位进行共享内存空间的申请,如果传递的是4097byte会为该共享内存申请两个4096byte大小的空间的。

        shmfig字段:当为IPC_CREAT时,表示如果共享内存段不存在就创建,如果存在就获取这个共享内存段的标识符,当为IPC_EXCL时不单独使用,通常配合IPC_CREAT,表示如果不存在就创建一个共享内存,该内存段已经存在了就返回-1,用于确保创建一个新的,唯一的共享内存段。而且可以设置共享内存的操作权限,和设置文件的权限类似,可以采用8进制传参。

共享内存的挂接

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

        该函数时用于将共享内存段连接到进程的地址空间当中,使进程可以访问当这块区域。成功会返回一个指向共享内存段的指针,用于访问共享内存,但是返回的是void*类型,和malloc一样需要强转后再使用。错误返回(void*)-1,同时错误码被设置,如果错误码是EACCES表示权限不足,EINVAL表示参数无效。

        shmid是shmget函数返回的共享内存段标识符,用于确定要访问哪一个共享内存段。shmaddr指定共享内存连接到进程虚拟地址空间的起始地址,通常设置为nullptr让系统帮我们确定该起始地址,方位我们设置的地址有冲突。shmflg表示用于控制内存段的连接方式,可以设置读、写等权限,但是需要本身该共享内存就要有这种权限才可以,否则会出错,通常设置为0即可。

去掉挂接

int shmdt(const void* shmaddr); 

        用于将共享内存段和共享区之间的映射断开,传递的参数就是只需要是共享内存的起始地址就可以,因为系统直到共享内存的大小等各种字段,所以也就会根据大小,删除页表部分映射关系就实现了去掉映射的操作。

共享内存的控制

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

        这个函数就像是共享内存段的 “控制面板”,可以对共享内存段进行各种管理操作,如获取共享内存段的状态信息、修改其属性或者标记它为删除等。shmid还是共享内存唯一标识字段,cmd是一个命令码,用于指定对共享内存段要执行的操作。buf则是根据cmd的不同而设置不同的值。

        cmd与buf字段:当cmd为IPC_STAT时,表示获取共享内存段的当前状态信息,此时buf就必须传递一个struct shmid_ds结构体用来接收;当cmd为IPC_SET时用于设置共享内存段的属性,所以要将修改的属性设置到buf中,传递给系统,让操作系统去修改共享内存的属性;而当cmd为IPC_RMID的时候,则表示删除该共享内存,不是解除映射,而是真正的删除物理内存中的共享内存,所以不用传递buf了,设置为nullptr即可。而且共享内存的声明周期是跟随内核的,所以我们必须手动释放才可以。

3.共享内存的使用代码

Comm.hpp--封装了操作接口
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>//项目路径和工程id
const std::string pathname = "./Comm.hpp";
const int proj_id = 0x11223344;
//共享内存大小
const int size = 4096;
//管道文件名称
const std::string filename = "fifo_file";//创建key值
key_t GetKey()
{key_t key = ftok(pathname.c_str(), proj_id);if(key < 0){std::cout << "ftok craet key fail" << std::endl;exit(1);}return key;
}int ShmgetHelper(key_t key, int flag)
{int shmid = shmget(key, size, flag);if(shmid < 0){std::cout << "shmget create system v shared memory fail" << std::endl;exit(2);}return shmid;
}//创建共享内存
int CreateShm(key_t key)
{return ShmgetHelper(key, IPC_CREAT | IPC_EXCL | 0666);
}
//获取共享内存
int GetShm(key_t key)
{return ShmgetHelper(key, IPC_CREAT);
}
//创建管道文件
void Makefile()
{int n = mkfifo(filename.c_str(), 0666);if(n < 0){//已经存在了if(errno == EEXIST)return;//真的出错了std::cout << "mkfifo create pipe file fail" << std::endl;exit(3);}
}
客户端--写入端
#include "Comm.hpp"int main()
{//获取key值key_t key = GetKey();//获取共享内存的标识int shmid = GetShm(key);//和共享内存建立映射char* ptr = (char*)shmat(shmid, nullptr, 0);//以写的方式打开命名管道int fd = open(filename.c_str(), O_WRONLY);//写入数据for(char ch = 'a'; ch <= 'z'; ch++){ptr[ch -'a'] = ch;//该字段没有任何作用,只是用来唤醒服务端进行读取操作int code = 1;ssize_t n = write(fd, &code, sizeof(int));if(n< 0){std::cout << "write fail" << std::endl;break;}sleep(1);}//关闭共享内存的映射以及管道文件shmdt(ptr);close(fd);return 0;
}
 服务器--读取端
#include "Comm.hpp"int main()
{//创建命名管道Makefile();//获取key值key_t key = GetKey();//创建共享内存int shmid = CreateShm(key);//建立映射char* ptr = (char*)shmat(shmid, nullptr, 0);//以读的方式打开文件int fd = open(filename.c_str(), O_RDONLY);//进行通信while(true){int code = 0;ssize_t n = read(fd, &code, sizeof(code));if(n < 0){std::cout << "read fail" << std::endl;break;}else if(n == 0){std::cout << "client quit" << std::endl;break;}else{std::cout << ptr << std::endl;}}//关闭文件close(fd);//取消映射shmdt(ptr);//释放共享内存shmctl(shmid, IPC_RMID, nullptr);return 0;
}


4.管道实现共享内存的同步机制

        共享内存是没有任何的同步机制的,所以说读取端不知道写入端什么时候写入了数据,也不知道共享内存中有没有数据,所以为读写两端提供了命名管道的机制,当写入端向共享内存写入的同时会向管道文件写入一个无意义的数字,表示告诉服务器我写入了数据;服务器会一直阻塞式的读取管道文件,如果说客户端写入了,那么管道文件会有数据,read可以读取后,会表明有数据了,服务端就会去访问共享内存了。

二、消息队列

1.底层原理

        (System V)消息队列的底层原理和共享内存基本一致,只是消息队列是在物理内存中维护了一个队列这样的数据结构,而共享内存只是一块空间而已。

        因为是队列的数据结构,写入的数据会以数据块的形式作为队列中的一个一个的元素,而共享内存在读取的时候没办法将共享区中的数据进行分割,只能我们在应用层去分割数据。而且采用这种结构的话,是对数据的分割,就可以记录每个数据块是哪个进程发过来的了。

2.使用接口

        接口的使用和形式上基本上也没有什么太大的区别,这样的话也方便了使用者去记忆。

创建消息队列

        消息队列没有挂载的单独接口,当消息队列被创建出来之后,就会映射到虚拟地址空间当中,所以说该接口也是做了两个工作。

int msgget(key_t key, int msgflg);

发送消息

        因为写入的是结构化的数据,所以要提供接口去写入。

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 

        msqid用来标识向哪个消息队列写入;msgp通常是用户自定义的一个结构体,结构体内部的成员为一个long类型的字段表示消息的类型和一个任意字段表示存放发送的消息;msgsz是传递消息的长度;msflg用于设置发送操作的属性,通常是0,也可以设置为IPC_NOWAIT就表示非阻塞式的发送数据,如果队列满了,会返回-1,同时错误码被设置为EAGAIN。

接收消息 

int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

        msgtyp字段:用于指定接收消息的类型,当msgtyp大于0的时候,表示接收的消息类型为msgtyp的消息;msgtyp等于0的时候,表示接收消息队列中的第一个消息,不考虑类型;msgtyp小于0的时候则表示,接收的消息类型为小于或等于msgtyp绝对值的消息中类型值最小的消息。所以说发送消息传递的结构体中的类型并非是标志着int,double,而是用户在功能上自己定义区分了发送的消息。

消息队列的控制

        消息队列没有取消挂载(映射)的概念,只有释放的概念,和共享内存一样cmd为IPC_RMID的时候就是释放消息队列的操作,但是这里并不是真正的时候,而是引用计数减少,只有当最后一个使用该消息队列的进程释放了消息队列的时候,引用计数减少到0,才会真的释放该消息队列。

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

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

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

相关文章

凸包(convex hull)简述

凸包&#xff08;convex hull&#xff09;简述 这里主要介绍二维凸包&#xff0c;二维凸多边形是指所有内角都在 [ 0 , Π ] [0,\Pi ] [0,Π]范围内的简单多边形。 凸包是指在平面上包含所有给定点的最小凸多边形。 数学定义&#xff1a;对于给定集合 X X X&#xff0c;所有…

【ArcGISPro/GeoScenePro】检查多光谱影像的属性并优化其外观

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 操作 其他数据 检查影像的属性 熟悉检查您正在使用的栅格属性非常重要。

提升汽车金融租赁系统的效率与风险管理策略探讨

内容概要 在汽车金融租赁系统这个复杂的生态中&#xff0c;提升整体效率是每个企业都渴望达成的目标。首先&#xff0c;优化业务流程是实现高效运行的基础。通过分析目前的流程&#xff0c;找出冗余环节并进行简化&#xff0c;能够帮助企业缩短审批时间&#xff0c;提高客户满…

以太网UDP协议栈实现(支持ARP、ICMP、UDP)--FPGA学习笔记26

纯verilog实现&#xff0c;仅使用锁相环IP、FIFO IP&#xff0c;方便跨平台移植。支持ping指令。 以太网系列文章&#xff1a; 以太网ICMP协议(ping指令)——FPGA学习笔记25-CSDN博客 以太网ARP协议——FPGA学习笔记23-CSDN博客 以太网PHY_MDIO通信&#xff08;基于RTL821…

edeg插件/扩展推荐:助力生活工作

WeTab 此插件在我看来有2个作用 1.改变edeg的主页布局和样式,使其更加精简,无广告 2.提供付费webtab Ai(底层是chatGpt) 沉浸式翻译 此插件可翻译网页的内容 假设我们浏览github 翻译前 翻译后 Better Ruler 可以对网页的距离进行测量 适合写前端的小伙伴 用法示例:

java项目之校园管理系统的设计与实现(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的校园管理系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; springboot校园…

设计模式 结构型 适配器模式(Adapter Pattern)与 常见技术框架应用 解析

适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口&#xff0c;从而使原本因接口不兼容而无法一起工作的类能够协同工作。这种设计模式在软件开发中非常有用&#xff0c;尤其是在需要集成…

打造三甲医院人工智能矩阵新引擎(一):文本大模型篇--基于GPT-4o的探索

一、引言 当今时代,人工智能技术正以前所未有的速度蓬勃发展,深刻且广泛地渗透至各个领域,医疗行业更是这场变革的前沿阵地。在人口老龄化加剧、慢性疾病患病率上升以及人们对健康需求日益增长的大背景下,三甲医院作为医疗体系的核心力量,承担着极为繁重且复杂的医疗任务。…

S7-200采集频率信号

S7-200可以借助高速计数器完成频率信号采集&#xff0c;接入流量计、转速等信号。官方给出的程序块无法完成多路同时采集&#xff0c;需要自己进行修改。 首先下载官方的频率采集库 SIOS 下载后导入library&#xff0c;在library中出现Frequency(v1.0) 拖进ladder后&#xf…

专家混合(MoE)大语言模型:免费的嵌入模型新宠

专家混合&#xff08;MoE&#xff09;大语言模型&#xff1a;免费的嵌入模型新宠 今天&#xff0c;我们深入探讨一种备受瞩目的架构——专家混合&#xff08;Mixture-of-Experts&#xff0c;MoE&#xff09;大语言模型&#xff0c;它在嵌入模型领域展现出了独特的魅力。 一、M…

【Vue】分享一个快速入门的前端框架以及如何搭建

先上效果图: 登录 菜单: 下载地址: 链接&#xff1a;https://pan.baidu.com/s/1m-ZlBARWU6_2n8jZil_RAQ 提取码&#xff1a;ui20 … 主要是可以自定义设置token,更改后端请求地址较为方便。 应用设置: 登录与token设置: 在这里设置不用登录,可以请求的接口: request.js i…

MySQL叶子节点为啥使用双向链表?不使用单向呢?

文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ MySQL 中的 B 树索引&#x…

用户界面的UML建模10

非正常的可视反馈可伴随着同步事件发生&#xff0c;而同步事件可由系统动作产生。但是&#xff0c;可以分别对它们进行建模。 在下节中将对这些特殊的事件依次进行论述。 6.1 异常处理建模 异常&#xff0c;由Meyer 定义[16],其作为运行时事件&#xff08;run-time events&a…

最新版Chrome浏览器加载ActiveX控件之CFCA安全输入控件

背景 CFCA安全输入控件用于保证用户在浏览器、桌面客户端、移动客户端中输入信息的安全性&#xff0c;防止运行在用户系统上的病毒、木马等恶意程序入侵窃取用户输入的敏感信息。确保用户输入、本地缓存、网络传输整个流程中&#xff0c;输入的敏感信息不被窃取。广泛应用于银行…

0基础跟德姆(dom)一起学AI 自然语言处理10-LSTM模型

1 LSTM介绍 LSTM&#xff08;Long Short-Term Memory&#xff09;也称长短时记忆结构, 它是传统RNN的变体, 与经典RNN相比能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时LSTM的结构更复杂, 它的核心结构可以分为四个部分去解析: 遗忘门输入门细胞状态输出门…

力扣283 移动零

void moveZeroes(int* nums, int numsSize) {int last_non_zero_found_at 0;for (int i 0; i < numsSize; i) {if (nums[i] ! 0) {// 交换 nums[last_non_zero_found_at] 和 nums[i]int temp nums[last_non_zero_found_at];nums[last_non_zero_found_at] nums[i];nums[i…

LookingGlass使用

文章目录 背景编译安装运行限制使用场景总结参考 背景 Looking Glass 是一款开源应用程序&#xff0c;可以直接使用显卡直通的windows虚拟机。 常见环境是Linux hostwindows guest&#xff0c;基本部署结构图&#xff1a; 编译 git clone --recursive https://github.com/g…

JVM学习:CMS和G1收集器浅析

总框架 一、Java自动内存管理基础 1、运行时数据区 运行时数据区可分为线程隔离和线程共享两个维度&#xff0c;垃圾回收主要是针对堆内存进行回收 &#xff08;1&#xff09;线程隔离 程序计数器 虚拟机多线程是通过线程轮流切换、分配处理器执行时间来实现的。为了线程切换…

关于 webservice 日志中 源IP是node IP的问题,是否能解决换成 真实的客户端IP呢

本篇目录 1. 问题背景2. 部署gitlab 17.52.1 添加repo源2.2 添加repo源 下载17.5.0的charts包2.3 修改values文件2.3.1 hosts修改如下2.3.2 appConfig修改如下2.3.3 gitlab下的sidekiq配置2.3.4 certmanager修改如下2.3.5 nginx-ingress修改如下2.3.6 <可选> prometheus修…

javaEE-网络编程-3 UDP

目录 Socaket套接字 UDP数据报套字节编程 1.DatagrameSocket类 DatagramSocaket构造方法: DatagramSocaket常用方法&#xff1a; 2.DatagramPacket类 DatagramPacket构造方法&#xff1a; UDP回显服务器实现 UDP服务端实现&#xff1a; 创建一个Socket类对象&#xf…