进程间通信(下)

1. system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
那么这到底是为什么呢?

1.1 共享内存示意图

我们来看看吧!下图就是shm的原理图。

所谓共享区,就是在内存中开辟的一块空间,然后将该内存空间挂接在进程的共享区中,这里的挂接就是将内存空间的实际地址由页表进行映射转为虚拟地址,将此虚拟地址存放入共享区(存放的还有该共享内存空间的其他相关信息,比如大小)。  

这里要注意,我们要如何确保需要通信的两个进程能够指向同一块共享内存区呢?

这就需要该空间具有唯一标识的标志。 

匿名管道是父子继承的方式,命名管道由路径进行唯一标识,那么共享内存区呢?

OS会依据唯一的key来对空间进行标识,后面函数篇详细讲解。 

在后面我们使用共享内存区的缩写shm。 

1.2 共享内存区的数据结构

OS当然会对shm进行管理,那就肯定有管理shm的结构体。

1.3 共享内存函数 

1.3.1 shmget函数

功能:用来创建共享内存
原型    int shmget(key_t key, size_t size, int shmflg);
参数
key: OS标识该内存空间唯一的标识符,由ftok函数得来。
size: 共享内存大小 < 这里有一个细节,shm的基本单位是4KB,如果你传入4097Bytes,那         么该shm的实际空间大小是8KB,但你只能使用4097字节,因此建议传入4KB的整数倍>
shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

             这里重点介绍IPC_CREAT与IPC_EXCL

             只传入前者表示有则返回shmid,传入前者|后者表示有则出错返回。
返回值成功返回一个非负整数,即该共享内存段的标识码;失败返回-1 

 1.3.2 ftok函数

功能:用来生成shmget的第一个参数key,使得操作系统可以开辟具有唯一标识的空间。

           实质是一个类似哈希函数的算法,在函数内部对传入的参数进行一系列运算,最后生             成一个具有唯一标识能力的码,该函数确保传入参数相同时,每次返回的key相同。
原型    key_t ftok(const char *pathname, int proj_id);
参数
pathname: 一个稳定的存在的路径,OS会使用该文件的相关信息生成唯一标识key
id: 通常是一个ASCLL码字符
返回值成功返回key值,失败返回-1

1.3.3 shmat函数 

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1

        //通过该指针可以直接对内存空间进行操作

说明:

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

 1.3.4 shmdt函数

功能:将共享内存段与当前进程脱离
原型    int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存 

1.3.5 shmctl 

功能:用于控制共享内存
原型    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

1.3.6 指令小集

ipcs 

这一指令可以查看当前所有进程间通信设施的状态信息 

 ipcs -m

仅查看进程间通信中以共享内存为设施的状态信息 

ipcrm -m  shmid 

删除该内存空间

小细节

这里我们在写下面代码验证的时候遇到了下面这种情况:这里的shm_sever里会创建shm。

上一个进程已经结束了,重新启动我们创建shm却失败了,查看以后发现上一个进程创建的shm还在。 

 当我们删除shmid为1的shm后,再次启动进程就成功创建了shmid为2的shm。

这说明,shm的生命周期并不向管道一样随进程,而是随内核的,如果我们不主动释放它,他会一直存在直到系统关闭。 

1.4 代码验证

common.hpp 

该文件中包含创建、销毁shm,挂接进程与解除关联关系的一系列方法。 

#pragma once
#include <iostream>
#include <cstring>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
using namespace std;

//在这里将获取shmid的参数使用宏定义,免去后面在外部接口处传入
#define defaultsize 4096
#define PATH "./"
#define Projid 'A'

//获取key值
int GetKey()
{
    return ftok(PATH, Projid);
}

//将key值转化为十六进制,与OS一致,方便查看
string ToHex(int key)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0X%x", key);
    return buffer;
}

//创建或获取shm的接口,后续会进行封装,sever创建,而client要获取
int CreatShmOrDie(int size, int shmflg)
{
    int key = GetKey();
    int shmid = shmget(key, size, shmflg);
    if (shmid < 0)
    {
        cerr << "shmget fail... errno" << errno << ",fail message: " << strerror(errno) << endl;
    }
    cout << "creat shm success..." << endl;
    return shmid;
}

//secver创建shm
int CreatShm(int size)
{
    return CreatShmOrDie(size, IPC_CREAT | IPC_EXCL | 0666);
}

//client获取shmid
int GetShm(int size)
{
    return CreatShmOrDie(size, IPC_CREAT | 0666);
}

//销毁shm
void DelShm(int shmid)
{
    int ret = shmctl(shmid, IPC_RMID, 0);
    if (ret == -1)
    {
        cerr << "Delete shm fail... errno" << errno << ",fail message: " << strerror(errno) << endl;
    }
    cout << "Delete shm success..." << endl;
}

//进程与shm建立关联关系
void *SetRelationship(int shmid)
{
    void *ptr = shmat(shmid, nullptr, 0);
    if ((long long int)ptr == -1)
    {
        cerr << "process and shm set relationship fail... errno" << errno << ",fail message: " << strerror(errno) << endl;
        return nullptr;
    }
    cout << "process and shm set relationship success..." << endl;
    return ptr;
}

//解除进程与shm的关联关系
void RemoveRelationship(void *addr)
{
    int ret = shmdt(addr);
    if (ret == -1)
    {
        cerr << "remove relationship fail... errno" << errno << ",fail message: " << strerror(errno) << endl;
    }
    else
    {
        cout << "remove relationship success..." << endl;
    }
}
fifo.hpp 

这一文件是命名管道的一部分,由于shm通信并不具备同步互斥机制,shm的通信是借助信号量来保护的。这里简单起见,我们借用管道通信的保护机制来保护shm,具体是如何实现的详见代码注释。 

#pragma once
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;

#define PAtH "./fifo.txt"
#define MODE 0666
class FIFO // 管理fifo的创建与销毁
{
public:
    FIFO(string path)
        : _path(path)
    {
        int n = mkfifo(_path.c_str(), MODE); // 创建,成功返回0
        if (n == 0)
        {
            cout << "name_pipe creat success..." << endl;
        }
        else
        {
            cerr << "name_pipe creat fail... errno: " << errno << ",errstring: " << strerror(errno) << endl;
        }
    }
    ~FIFO()
    {
        int n = unlink(_path.c_str()); // 销毁,成功返回0
        if (n == 0)
        {
            cout << "name_pipe unlink success..." << endl;
        }
        else
        {
            cerr << "name_pipe unlink fail... errno: " << errno << ",errstring: " << strerror(errno) << endl;
            // 标准错误输出
        }
    }

private:
    string _path;
};

//控制对管道的操作(读和写),借助管道的同步机制保护shm
class Sync
{
public:
    Sync()
        : rfd(-1), wfd(-1)
    {
    }
    void OpenRead()//打开管道的读端
    {
        rfd = open(PAtH, O_RDONLY);
        if (rfd == -1)
        {
            cerr << "rfd open O_RDONLY fail... errno: " << errno << ",errstring: " << strerror(errno) << endl;
        }
        else
        {
            cout << "rfd open success" << "rfd=" << rfd << endl;
        }
    }
    void OpenWrite()//打开管道的写端
    {
        wfd = open(PAtH, O_WRONLY);
        if (wfd == -1)
        {
            cerr << "wfd  open O_WRONLY fail... errno: " << errno << ",errstring: " << strerror(errno) << endl;
        }
        else
        {
            cout << "wfd open success" << "wfd=" << wfd << endl;
        }
    }
    bool wake()//读端读,当读取结束或失败返回false
    {
        int c = 0;

        int n = read(rfd, &c, sizeof(c));
        if (n == sizeof(c))
            return true;
        if (n == 0)
            return false;
        return false;
    }
    bool wakeup()//写端写,当写入结束或失败返回false
    {
        int c = 0;

        int n = write(wfd, &c, sizeof(c));

        if (n == sizeof(c))
            return true;
        return false;
    }

private:
    int rfd;
    int wfd;
};
shm_sever.cc 

sever端对shm的内容进行读取,借用管道的同步机制,只要管道在读取,sever就对shm读取,一旦管道读取结束,sever对shm的读取也结束,进入下一阶段。

#include "common.hpp"
#include "fifo.hpp"
int main()
{
    // 检查获取key值是否出错
    int key = GetKey();
    cout << ToHex(key) << endl;

    // 创建shm,如果有报错的那种
    int shmid = CreatShm(defaultsize);
    cout << shmid << endl;

    // 挂接shm
    char *buffer = (char *)SetRelationship(shmid);

    // 进程间通信  这里借用管道的同步互斥机制,当写端终止写入或关闭,读端读到0,
    FIFO fifo(PAtH);
    Sync syn;
    syn.OpenRead();
    while (1)
    {
        if (!syn.wake()) // 读端读到0,退出循环
            break;
        cout << "from client message: " << buffer << endl;
        sleep(1);
    }

    // 解除挂接
    RemoveRelationship(buffer);
    // 销毁shm
    DelShm(shmid);
    return 0;
}
 shm_client.cc

client端对shm进行写入,借用管道的同步机制,只要管道在写入,client就对shm写入,一旦管道写入结束,client对shm的写入也结束,进入下一阶段。

#include "common.hpp"
#include "fifo.hpp"
int main()
{
    // 检查获取key值是否出错
    int key = GetKey();
    cout << ToHex(key) << endl;

    // 获取shmid
    int shmid = GetShm(defaultsize);
    cout << shmid << endl;

    // 进程与shm挂接
    char *buffer = (char *)SetRelationship(shmid);

    // 进程间通信  这里借用管道的同步互斥机制,当写端终止写入或关闭,读端读到0,
    Sync syn;
    syn.OpenWrite();
    sleep(10);

    for (char c = 'A'; c < 'H'; c++)
    {
        buffer[c - 'A'] = c;
        sleep(1);
        !syn.wakeup();//当读端不再读,OS会强制关闭该管道并释放信号杀死该进程
    }
    // 写端不再写,读端会返回0

    // 解除挂接
    RemoveRelationship(buffer);
    return 0;
}

当我们终止sever与client的任意一端,管道机制都会收到,进而影响shm。 

  我们由sever端负责创建与销毁shm和管道,接收信息的任务,client端只需要发送信息即可。

下图nattch没有0-1的过程是因为该信息是每秒进行打印,而我们在两端并未进行等待,0-1,1-0的过程瞬间就已经完成。 

1.5 小结

注意:共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除

1.5.1 shm的优点 (部分)

这一进程通信方式速度是最快的,因为这一通信方式无需向管道那样使用系统调用,read与write系统调用的本质是拷贝函数,将数据在用户空间与内核空间之间传输。而shm相对而言非常快,两个进程访问同一内存空间,一个进程对该空间进行更改,另一个进程马上就能看到。 

1.5.2 shm的缺点 (部分)

这一进程通信方式没有同步与互斥机制的保护,也就是说,存在写端还没写完,读端就已经把已写的部分数据进行读取了, 这会造成通信间双方数据不一致的问题。

共享内存的操作是非进程安全的,多个进程同时对共享内存读写是有可能会造成数据的交叉写入或读取,造成数据混乱

 

2. system V消息队列

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

blender复制uv贴图

1、新建两个猴头 2、点击其中一个进入uv编辑模式 3、在uv编辑中打开一个图像 4、新建一个材质球&#xff0c;将图像渲染到模型上 打开图像纹理 选择刚才打开的图像 切换到材质预览模式后&#xff0c;就可以看到贴图了 5、选择一个孤岛 6、然后选择拼排孤岛 可以看到该模型展开…

信息安全从业者书单推荐

作为一名网安人&#xff0c;身上肩负的责任是很大的&#xff0c;能力越大&#xff0c;责任也越大&#xff0c;反过来责任越大&#xff0c;能力也必须跟得上。不管是想进这行&#xff0c;还是已经在这行&#xff0c;持续学习肯定是不能缺少的&#xff0c;除了在工作中积累&#…

【Python】用于发送电子邮件的标准库smtplib和构建邮件主体、添加附件、设置收件人的email

欢迎来到《小5讲堂》 这是《Python》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 插件介绍邮件代码扩展知识点文章推荐 插件介绍 smtplib 是 Pytho…

uni-app App端实现文字语音播报(Ba-TTS)

前言 最近在遇到消息提示语音播放出来&#xff0c;查了一圈文档发现并没有自带api 后面想起支付宝收钱播报&#xff0c;不受限与系统环境和版本环境&#xff08;后面查阅他是音频实现的&#xff09; 如果是由安卓端需要语音播放功能-直接使用Ba-TTs救急&#xff08;需要付费2…

kettle从入门到精通 第六十三课 ETL之kettle kettle调用python脚本的两种方法

想真正学习或者提升自己的ETL领域知识的朋友欢迎进群&#xff0c;一起学习&#xff0c;共同进步。若二维码失效&#xff0c;公众号后台加我微信入群&#xff0c;备注kettle。 kettle中不能直接调用python脚本&#xff0c;可以通过shell脚本和http进行调用pyton服务。 一、shel…

vue3的节点靶向更新知识分享

靶向更新的流程 先来看看我画的整个靶向更新的流程&#xff0c;如下图&#xff1a; 整个流程主要分为两个大阶段&#xff1a;编译时和运行时。 编译时阶段找出动态节点&#xff0c;使用patchFlag属性将其标记为动态节点。 运行时阶段分为两块&#xff1a;执行render函数阶段…

C语言实现Hash Map(2):Map代码实现详解

在上一节C语言实现Hash Map(1)&#xff1a;Map基础知识入门中&#xff0c;我们介绍了Map的基础概念和在C中的用法。但我写这两篇文章的目的是&#xff0c;能够在C语言中实现这样的一个数据结构&#xff0c;毕竟有时我们的项目中可能会用到Map&#xff0c;但是C语言库中并没有提…

springboot vue 开源 会员收银系统 (2) 搭建基础框架

前言 完整版演示 前面我们对会员系统https://blog.csdn.net/qq_35238367/article/details/126174288进行了分析 确定了技术选型 和基本的模块 下面我们将从 springboot脚手架开发一套收银系统 使用脚手架的好处 不用编写基础的rabc权限系统将工作量回归业务本身生成代码 便于…

【通义千问—Qwen-Agent系列2】案例分析(图像理解图文生成Agent||多模态助手|| 基于ReAct范式的数据分析Agent)

目录 前言一、快速开始1-1、介绍1-2、安装1-3、开发你自己的Agent 二、基于Qwen-Agent的案例分析2-0、环境安装2-1、图像理解&文本生成Agent2-2、 基于ReAct范式的数据分析Agent2-3、 多模态助手 附录1、agent源码2、router源码 总结 前言 Qwen-Agent是一个开发框架。开发…

【LeetCode】【209】长度最小的子数组(1488字)

文章目录 [toc]题目描述样例输入输出与解释样例1样例2样例3 提示进阶Python实现前缀和二分查找滑动窗口 个人主页&#xff1a;丷从心 系列专栏&#xff1a;LeetCode 刷题指南&#xff1a;LeetCode刷题指南 题目描述 给定一个含有n个正整数的数组和一个正整数target找出该数组…

Java进阶学习笔记3——static修饰成员方法

成员方法的分类&#xff1a; 类方法&#xff1a;有static修饰的成员方法&#xff0c;属于类&#xff1a; 成员方法&#xff1a;无static修饰的成员方法&#xff0c;属于对象。 Student类&#xff1a; package cn.ensource.d2_staticmethod;public class Student {double scor…

SpringMVC流程

1、SpringMVC常用组件&#xff1a; DispatcherServlet&#xff08;请求分发器&#xff09;&#xff1a;Spring MVC的核心组件之一&#xff0c;负责处理全局配置和将用户请求分发给其他组件进行处理。Controller&#xff08;处理器&#xff09;&#xff1a; 实际处理业务逻辑的…

springmvc中HandlerMapping是干什么用的

HandlerMapping处理器映射器 作用是根据request找到相应的处理器Handler和Interceptors&#xff0c;然后封装成HandlerExecutionChain对象返回 HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 实现类 HandlerMapping帮助DispatcherServlet进…

Oblivion Desktop:一款强大的网络工具介绍

一款优秀的开源网络工具。 文章目录 Oblivion Desktop: 安全与隐私的网络工具软件背景开发背景 使用方法安装日常使用高级功能 总结 Oblivion Desktop: 安全与隐私的网络工具 软件背景 Oblivion Desktop 是一个由 BePass 团队开发的开源桌面应用&#xff0c;旨在为用户提供更…

喜报 | 江苏刺掌信息科技有限公司获选市企业发展服务中心优质合作伙伴

喜报 江苏刺章信息成功入选 镇江市企业发展服务中心 “优质合作伙伴” 为进一步完善镇江市公共服务体系建设&#xff0c;提升服务范围和能力&#xff0c;更好地为企业提供专业、高效、安全的服务&#xff0c;镇江市企业发展服务中心启动了优质合作伙伴的征选工作&#xff0c;通…

win10右键没有默认打开方式的选项的处理方法

问题描述 搞了几个PDF书籍学习一下&#xff0c;不过我不想用默认的WPS打开&#xff0c;因为WPS太恶心人了&#xff0c;占用资源又高。我下载了个Sumatra PDF&#xff0c;这时候我像更改pdf文件默认的打开程序&#xff0c;发现右击没有这个选项。 问题解决 右击文件–属性–…

Linux——进程与线程

进程与线程 前言一、Linux线程概念线程的优点线程的缺点线程异常线程用途 二、Linux进程VS线程进程和线程 三、Linux线程控制创建线程线程ID及进程地址空间布局线程终止线程等待分离线程 四、习题巩固请简述什么是LWP请简述LWP与pthread_create创建的线程之间的关系简述轻量级进…

JAVA云HIS医院系统源码 HIS源码:云HIS系统与SaaS的关系

云HIS系统与SaaS的关系 云HIS系统是一种基于云计算技术的医院信息系统&#xff0c;它采用B/S架构&#xff0c;通过云端SaaS服务的方式提供。用户可以通过浏览器访问云HIS系统&#xff0c;无需关注系统的部署、维护、升级等问题。云HIS系统通常具有模板化、配置化、智能化等特点…

Android 共享内存

Parcelable 和 Serializable 区别 Serializable IO完成&#xff08;通过磁盘文件读写&#xff09; Parcelable C 对象指针 来实现共享内存 import android.os.Parcel; import androidx.annotation.NonNull;public class ApiResponseBean extends Throwable implements Parce…

吴恩达2022机器学习专项课程C2W2实验:Relu激活函数

目录 代码修改1.Activation2.Dense3.代码顺序 新的内容1.总结上节课内容2.展示ReLU激活函数的好处3.结论 代码案例一代码案例二1.构建数据集2.构建模型 2D1.构建数据集2.模型预测3.扩展 代码修改 1.Activation &#xff08;1&#xff09;需要添加代码from tensorflow.keras i…