进程间通信(下)

news2024/11/21 1:37:20

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消息队列

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

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

相关文章

用户账户的权限管理

用户账户的权限管理 用户账号&#xff1a; 1、超级用户 &#xff1a;管理员账号 root 默认对本机拥有最高权限的账户&#xff0c;在系统中唯一。 2、普通用户&#xff1a;一般由管理员创建&#xff0c;拥有的权限是受限制的&#xff0c;一般只在自己的家目录中拥有完整的权限…

【无标题】亚马逊5月24日宣布推出2024出口跨境物流加速器计划

亚马逊中国5月24日郑重宣布启动“2024亚马逊出口跨境物流加速器计划”&#xff0c;旨在依托其世界领先的物流网络和前沿技术&#xff0c;结合本土资源&#xff0c;不断优化跨境物流服务&#xff0c;以强化中国卖家在跨境物流供应链管理方面的能力&#xff0c;进而提升整体效率&…

DI-engine强化学习入门(四)DI-ZOO强化学习——MUJOCO环境搭建

上一章我们搭建了Atari的强化学习环境&#xff0c;这一期我们来搭建MUJOCO强化学习环境 那么我们为什么要“多此一举”呢&#xff1f; 一、概述 MuJoCo和Atari对比 Atari&#xff1a; 类型&#xff1a;Atari游戏是一系列的经典视频游戏。简单性&#xff1a;Atari游戏通常有相…

Leetcode—2769. 找出最大的可达成数字【简单】

2024每日刷题&#xff08;139&#xff09; Leetcode—2769. 找出最大的可达成数字 实现代码 class Solution { public:int theMaximumAchievableX(int num, int t) {return num t * 2;} };运行结果 之后我会持续更新&#xff0c;如果喜欢我的文章&#xff0c;请记得一键三连…

深入理解python列表遍历:两种方法详解与实例

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、使用索引遍历列表 三、直接使用元素遍历列表 四、总结 一、引言 在编程过程…

【设计模式】JAVA Design Patterns——Callback(回调模式)

&#x1f50d;目的 回调是一部分被当为参数来传递给其他代码的可执行代码&#xff0c;接收方的代码可以在一些方便的时候来调用它。 &#x1f50d;解释 真实世界例子 我们需要被通知当执行的任务结束时。我们为调用者传递一个回调方法然后等它调用通知我们。 通俗描述 回调是一…

人生苦短,我学python之数据类型(上)

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏&#xff1a;Python 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 目录 一.元组 &#xff08;tuple&#xff09; 二.集合&#xff08;set&#xff09; 三.字典(dict) 一.元组 &#…

科技前沿:IDEA插件Translation v3.6 带来革命性更新,翻译和发音更智能!

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【论文速读】Transformer:Attention Is All You Need

Transformer&#xff1a;Attention Is All You Need 摘要模型架构注意力模型Scaled Dot-ProductMulti-Head Attention Position-wise Feed-Forward NetworksEmbeddings and SoftmaxPositional Encoding 摘要 我们提出了一种新的简单的网络架构&#xff0c;Transformer&#xf…

OSPF多区域组网实验(思科)

华为设备参考&#xff1a;OSPF多区域组网实验&#xff08;华为&#xff09; 技术简介 OSPF多区域功能通过划分网络为多个逻辑区域来提高网络的可扩展性和管理性能。每个区域内部运行独立的SPF计算&#xff0c;而区域之间通过区域边界路由器进行路由信息交换。这种划分策略适用…

如何构建基因单倍型网络

一. 单倍型网络 单倍型网络图通常是指在遗传学和进化生物学中使用的一种图形表示法&#xff0c;用于描述不同个体或群体之间的遗传关系。这种图形通常用于研究基因或DNA序列在不同个体之间的变化和传递。在单倍型网络图中&#xff0c;节点表示不同的单倍型&#xff0c;而边表示…

自己搭建ntp服务器

1、背景 终端在使用过程中&#xff0c;发现联网后进行NTP(network time protocol&#xff0c;网络时间协议)校时过程中&#xff0c;经常出现校时失败的问题&#xff0c;特别是本地环境&#xff0c;因此需要在自己这边搭建测试环境。 2、搭建步骤 2.1 安葬服务器 sudo apt-get…

Python项目:数据可视化_下载数据【笔记】

源自《Python编程&#xff1a;从入门到实践》 作者&#xff1a; Eric Matthes 02 下载数据 2.1 sitka_weather_07-2021_simple.csv from pathlib import Path import matplotlib.pyplot as plt import csv from datetime import datetimepath Path(D:\CH16\sitka_weather_0…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 5月25日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年5月25日 星期六 农历四月十八 1、 气象台&#xff1a;未来三天&#xff0c;北方雷雨侵扰&#xff0c;南方暴雨大暴雨将成片出现&#xff0c;今年首个台风生成在即。 2、 人社部&#xff1a;拟增加网络主播等19个新职业&am…

“高考钉子户”唐尚珺决定再战2024年高考

“高考钉子户”唐尚珺决定在2024年再次参加高考&#xff0c;这个选择确实很特别也很有趣。十几年连续参加高考&#xff0c;他已经积累了大量的备考经验和应试技巧。这样的经验对于高考辅导机构来说无疑是非常宝贵的资源&#xff0c;他如果选择去辅导机构当老师&#xff0c;应该…

运算符重载(上)

目录 运算符重载日期类的比较判断日期是否相等判断日期大小 赋值运算符重载赋值运算符重载格式赋值运算符只能重载成类的成员函数不能重载成全局函数用户没有显式实现时&#xff0c;编译器会生成一个默认赋值运算符重载&#xff0c;以值的方式逐字节拷贝 感谢各位大佬对我的支持…

使用llama.cpp实现LLM大模型的格式转换、量化、推理、部署

使用llama.cpp实现LLM大模型的量化、推理、部署 大模型的格式转换、量化、推理、部署概述克隆和编译环境准备模型格式转换GGUF格式bin格式 模型量化模型加载与推理模型API服务模型API服务(第三方)GPU推理 大模型的格式转换、量化、推理、部署 概述 llama.cpp的主要目标是能够在…

微信小程序反编译/解包

微信小程序反编译/解包 环境与工具 操作系统&#xff1a;Windows 11 23H2 微信版本&#xff1a;3.9.10.19 Q&#xff1a;如何找到小程序文件位置&#xff1f; A&#xff1a;在微信的设置找到文件路径&#xff0c;小程序文件位于 \WeChat Files\Applet\。 Q&#xff1a;小程…

centos下yum -y install npm报没有可用软件包 npm

yum -y install npm安装报错 失败原因是因为缺少epel&#xff08;epel是社区打造的免费开源发行软件包版本库&#xff0c;系统包含大概1万多个软件包&#xff09;&#xff0c;需要先安装epel-release 解决方法&#xff1a; 1、先安装epel-release yum -y install epel-releas…

1106 2019数列

solution 维护长度为4的数组&#xff0c;对于第四位之后的数字&#xff0c;为所维护数组的所有元素之和 的个位数 #include<iostream> using namespace std; int main(){int n, a[4] {2, 0, 1, 9}, cnt 0, d;scanf("%d", &n);for(int i 0; i < n; …