【Linux—进程间通信】共享内存的原理、创建及使用

news2024/10/6 6:51:23

什么是共享内存

      共享内存是一种计算机编程中的技术,它允许多个进程访问同一块内存区域,以此作为进程间通信(IPC, Inter-Process Communication)的一种方式。这种方式相对于管道、套接字等通信手段,具有更高的效率,因为数据不需要在用户空间和内核空间之间进行复制,也不需要经过序列化和反序列化的复杂过程。

特点:

  • 高速度:由于省去了数据复制和上下文切换的开销,共享内存提供了非常高的数据交换速度。
  • 低延迟:适用于需要快速响应和大数据量传输的场景。
  • 同步需求:虽然高效,但多个进程同时访问同一块内存可能会导致数据不一致。因此,需要使用如互斥锁、信号量等同步工具来确保数据的正确性和完整性。
  • 生命周期管理:共享内存段需要显式创建、映射到进程地址空间、使用后断开连接,并在不再需要时销毁,以避免资源泄露。
  • 共享内存在系统中可以存在多个,供不同进程之间进行通信

共享内存的原理

每一个进程都有属于自己的进程地址空间,假设操作系统在物理内存开辟了一段空间,该进程可以创建一段虚拟内存,将这段虚拟内存的起始与结束地址通过页表与物理内存的空间构建联系

如果另一个进程,也通过上述方式,通过页表映射到同一段物理内存,那就实现了让多个进程看到同一段空间,这样当一个进程向这段物理空间写入数据,另一个进程就可以马上从这段空间读取数据了,就可以实现进程间的通信了

共享内存的使用

(一)创建共享内存

#原型
 int shmget(key_t key, size_t size, int shmflg);
#参数
 key:用户自定义共享内存的标识
 size:共享内存大小
 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
#返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

【解释】:

# key:由于进程间具有独立性,所以共享内存一定不是某一个进程自己创建的,而是进程通过函数调用让操作系统创建的,而为了使另一个进程可以找到该共享内存,每一个共享内存一定有一个唯一性的标识,但是这个标识一定不能是操作系统自己独立生成的,因为这样只有要创建共享内存的那个进程能找到该共享内存。所以用户可以通过key自己设定唯一的标识,key一般使用函数调用生成

#include <sys/types.h>
#include <sys/ipc.h>

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

pathname为文件路径,proj_id为项目id,这两个都是由用户自己设定的,该函数会通过特定的算法将参数形成唯一的key值

#size:共享内存的大小,建议设置为4096个字节的倍数,例如:当我们将共享内存设置为4097个字节,OS会申请4096*2大小的空间,由于我们申请的是4097个字节,剩下的4095个字节我们不能使用,就会浪费掉

#shmflg: 标志位参数有两种:IPC_CREAT、IPC_EXCL,常用的反方式有两种

  • IPC_CREAT: 如果要创建的共享内存不存在那就创建,如果存在就返回该共享内存
  • IPC_CREAT | IPC_EXCL:如果创建的共享内存不存在那就创建,如果存在就报错
  • 在使用时后面一般还要加上权限,防止进程无法与共享内存联系(注意)

ps:第二种使用方法可以保证每次创建的共享内存都是新创建的,所以在使用上,IPC_CREAT | IPC_EXCL一般用于创建共享内存,IPC_CREAT一般用于获取共享内存

#返回值:共享内存创建成功就返回该共享内存的shmid,失败就返回-1

key和shmid都是标识共享内存的唯一性字段,不过key是用户自定义的,用于让内核区分shm唯一性的,用户不能通过key进行对shm管理,而shmid是有内核返回的一个值,是让用户对共享内存进行管理的id值

(二)删除共享内存

  • 查看所有的共享内存:
ipcs -m

  • 利用指令删除共享内存:
ipcrm -m shmid

  • 代码删除共享内存:

shmctl函数

#功能:用于控制共享内存
#原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
#参数:
 shmid:由shmget返回的共享内存标识码
 cmd:将要采取的动作(有三个可取值)
 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
#返回值:成功返回0;失败返回-1
命令说明
IPC_STAT把shmid_ds数据结构中的数据设置为共享内存当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

ps.删除共享内存关心第三个参数,可以直接把他设置为nullptr

(三)将共享内存连接到进程地址空间

shmat函数

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

(四)将共享内存从进程地址空间脱离

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

注意:将共享内存段与当前进程脱离不等于删除共享内存段


补充:共享内存不具有进程之间的同步机制,假设一个进程负责读,一个进程负责写,就算共享内存中没有写入数据,读进程还是会一直读,这就可能会发生写进程才写了一半的数据就被读走了,造成数据不一致问题。我们可以利用管道解决这个问题,因为管道具有同步机制,我们让写端写完以后,通过管道传输信号,只有读端通过管道接受到信号以后,才会进行对共享内存的读取

(向管道中写的信号是什么不重要,只要向共享内存中写后,向管道中发送信息,在接收到管道信号后,才读取共享内存的内容,这样就可以让共享内存也存在向管道一样的同步机制,写端写一条,读端读一条)

代码完整使用

shm.hpp

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define Creater 1
#define User 2
const char *pathname = "/home/zyq/mydir/dir/Shm";
int proj_id = 0x666;

class Shm
{
private:
    key_t GetComkey()
    {
        key_t key = ftok(_pathname, proj_id);
        if (key < 0)
        {
            perror("ftok");
            return -1;
        }
        return key;
    }
    int GetShmid(key_t key, int size, int flag)
    {
        int shmid = shmget(_key, size, flag);
        if (shmid < 0)
            perror("shmget");
        return shmid;
    }

    void AttachShm()
    {
        _addrshm = shmat(_shmid, nullptr, 0);
        if (_addrshm == nullptr)
        {
            perror("shmat");
        }
    }

    void DetachShm()
    {
        if (_addrshm != nullptr)
        {
            int n = shmdt(_addrshm);
            if (n < 0)
                perror("shmdt");
        }
    }

public:
    Shm(const char *pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = GetComkey();
        if (who == Creater)
        {
            GetCreatershmid();
        }
        else
        {
            GetUsershmid();
        }
        //将共享内存连接到进程地址空间
        AttachShm();
        std::cout << "_key:" << _key << std::endl;
        std::cout << "_shmid:" << _shmid << std::endl;
    }
    ~Shm()
    {
        //删除进程地址空间
        shmctl(_shmid,IPC_RMID,nullptr);
        //将共享内存脱离进程地址空间
        DetachShm();
    }

    bool GetCreatershmid()
    {
        _shmid = GetShmid(_key, 4096, IPC_CREAT | IPC_EXCL|0666 );
        if (_shmid < 0)
            return false;
        else
        {
            std::cout << "Create shm done!" << std::endl;
            return true;
        }
    }

    bool GetUsershmid()
    {
        _shmid = GetShmid(_key, 4096, IPC_CREAT|0666);
        if (_shmid < 0)
            return false;
        else
        {
            std::cout << "Get shm done!" << std::endl;
            return true;
        }
    }

    void* Addr()
    {
        return _addrshm;
    }
private:
    key_t _key;
    int _shmid;

    const char *_pathname;
    int _proj_id;
    int _who;
    void *_addrshm;
};

server.cc

#include "shm.hpp"
#include "namedpipe.hpp"
int main()
{
    // 创建共享内存并连接
    Shm shm(pathname, proj_id, Creater);
    char *shmaddr = (char *)shm.Addr();
    // 创建管道
    NamedPipe fifo(path, Creater);
    fifo.OpenforRead();

    while (true)
    {
        //读共享内存前先获取唤醒信号
        std::string str;
        fifo.ReadNamedPipe(&str);

        std::cout << "shm content:" << shmaddr << std::endl;
        sleep(1);
    }

    sleep(10);
    return 0;
}

client.cc

#include"shm.hpp"
#include"namedpipe.hpp"
int main()
{
    //获取共享内并连接
    Shm shm(pathname,proj_id,User);
    char* shmaddr=(char*)shm.Addr();
    //获取管道
    NamedPipe fifo(path,User);
    fifo.OpenforWrite();

    char ch='A';
    while(ch<'Z')
    {
        shmaddr[ch-'A']=ch;
        //写完以后,向管道发送唤醒信息
        //向管道中写的内容不重要,主要是利用管道的同步机制
        std::cout<<"add "<<ch<<" into shm"<<std::endl;
        std::string str="WakeupRead";
        fifo.WriteNamedPipe(str);

        ch++;
        sleep(2);
    }
    sleep(10);
    return 0;
}

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

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

相关文章

【skill】onedrive的烦人问题

Onedrive的迷惑行为 安装Onedrive&#xff0c;如果勾选了同步&#xff0c;会默认把当前用户的数个文件夹&#xff08;桌面、文档、图片、下载 等等&#xff09;移动到安装时提示的那个文件夹 查看其中的一个文件的路径&#xff1a; 这样一整&#xff0c;原来的文件收到严重影…

孪生网络、匹配网络和原型网络:详解与区分

孪生网络、匹配网络和原型网络 孪生网络、匹配网络和原型网络&#xff1a;详解与区分孪生网络&#xff08;Siamese Networks&#xff09;核心概念工作原理 匹配网络&#xff08;Matching Networks&#xff09;核心概念工作原理 原型网络&#xff08;Prototypical Networks&…

环形链表知识点

目录 判断链表中是否有环快慢指针步数问题 判断链表中是否有环 题目&#xff1a;给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 解决方法&#xff1a;使用快慢指针 如果两个快慢指针相遇&#xff0c;则有环。 如果没有相遇&#xff0c;则没有环。 但是这个原理…

Linux——守护进程化(独立于用户会话的进程)

目录 前言 一、进程组ID与会话ID 二、setsid() 创建新会话 三、daemon 守护进程 前言 在之前&#xff0c;我们学习过socket编程中的udp通信与tcp通信&#xff0c;但是当时我们服务器启动的时候&#xff0c;都是以前台进程的方式启动的&#xff0c;这样很不优雅&#xff0c…

【LinuxC语言】setitimer与getitimer函数

文章目录 前言一、setitimer() 函数二、getitimer() 函数三、示例代码总结 前言 在Linux系统下&#xff0c;编写程序时经常需要使用定时器来实现一些定时任务、超时处理等功能。setitimer() 和 getitimer() 函数是两个用于操作定时器的重要函数。它们可以帮助我们设置定时器的…

第19章 基于质量特性的测试技术

一、功能性测试 &#xff08;一&#xff09;测试方法 等价类边界值法因果图法判定表法场景法 &#xff08;二&#xff09;用例 1、正常用例 2、异常用例 &#xff08;三&#xff09;完备性 1、功能覆盖率 2、X1-A/B 功能覆盖率X&#xff1a;软件实际功能覆盖文档中所有…

【Linux 网络】网络基础(一)(局域网、广域网、网络协议、TCP/IP结构模型、网络传输、封装和分用)-- 详解

一、计算机网络的发展背景 1、网络的定义 网络是指将多个计算机或设备通过通信线路、传输协议和网络设备连接起来&#xff0c;形成一个相互通信和共享资源的系统。 &#xff08;1&#xff09; 独立模式 独立模式 &#xff1a; 计算机之间相互独立。 &#xff08;2&#xff09;…

VMvare如何更改虚拟机内共享文件夹的挂载点

更改虚拟机内共享文件夹的路径 进入目录 /etc/init.d ,并找到vmware-tools文件 里面有配置项 vmhgfs_mnt"/mnt/hgfs" 将引号内的内容更改为你需要挂载的路径,重启即可 注意挂载的路径不能是 “/”&#xff0c;必须根目录下的某个文件夹&#xff0c;或者其子文件夹 …

在线OJ——链表经典例题详解

引言&#xff1a;本篇博客详细讲解了关于链表的三个经典例题&#xff0c;分别是&#xff1a;环形链表&#xff08;简单&#xff09;&#xff0c;环形链表Ⅱ&#xff08;中等&#xff09;&#xff0c;随机链表的复制&#xff08;中等&#xff09;。当你能毫无压力地听懂和成功地…

面试中算法(使用栈实现队列)

使用栈来模拟一个队列&#xff0c;要求实现队列的两个基本操作:入队、出队。 栈的特点&#xff1a;先入后出&#xff0c;出入元素都是在同一端&#xff08;栈顶&#xff09;。 队列的特点&#xff1a;先入先出&#xff0c;出入元素是在两端&#xff08;队头和队尾)。 分析&…

深度学习:基于Keras,使用长短期记忆神经网络模型LSTM和RMSProp优化算法进行销售预测分析

前言 系列专栏&#xff1a;【机器学习&#xff1a;项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学习模型、处理非…

springboot+vue课程作业成绩可视化大屏分析系统

教师的登录功能。 教师需要可以拥有每学期新增课程的功能。 新增的课程有作业成绩&#xff0c;考勤成绩&#xff0c;考试成绩&#xff0c;实验成绩&#xff0c;其中作业成绩是平时作业1到作业8的平均成绩&#xff0c;最后根据占比得出学期的总评成绩。&#xff08;参考我发的表…

Shell编程debug

debug调试 debug方法 sh -x显示脚本执行过程set命令设置开始debug和结束debug的位置显示脚本某一部分执行过程&#xff0c;解决复杂脚本故障 示例&#xff1a; sh -x 显示脚本执行过程 set显示脚本的部分执行过程 set -x 开始调试&#xff0c;从这里开始显示脚本的详细执行过…

【C++】模板初阶:泛型编程的起点

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

day02-分布式事务

1.分布式事务问题 1.1.本地事务 本地事务&#xff0c;也就是传统的单机事务。在传统数据库事务中&#xff0c;必须要满足四个原则&#xff1a; 1.2.分布式事务 分布式事务&#xff0c;就是指不是在单个服务或单个数据库架构下&#xff0c;产生的事务&#xff0c;例如&#xf…

鸿蒙组件样式复用简介

鸿蒙组件样式复用简介 使用Style进行复用在Component内部复用在Component外部复用使用Extend复用指定类型组件Extend支持参数传递 使用Style进行复用 在页面开发过程中&#xff0c;会遇到多个组件都在使用相同的样式&#xff0c;这时候就要考虑是不是可以将相同的样式的进行复…

Python中的`return`语句详解

Python中的return语句详解 对于初学Python或任何编程语言的人来说&#xff0c;理解函数如何返回值是非常重要的。在Python中&#xff0c;return语句用于从函数中返回结果。本篇博客将详细介绍return语句的基本用法&#xff0c;以及如何在不同情境中有效使用它。 什么是return…

mac安装虚拟机linux系统

需要下载的有&#xff1a;centos8镜像 , 虚拟器 VMware 软件包 , Termius 或者xshell 1. CentOS系统下载 linux系统一般有&#xff1a; CentOS、ubuntu、redhat&#xff0c;选择一种进行安装就可以 CentOS 2024 年开始停止维护和发布 CentOS8的下载与安装(windows下安装) 镜…

cloudreve离线下载报错Insufficient capacity

报错内容&#xff1a; [Warning] 2024-05-03 22:57:40 Failed to update status of download task "c0xxxxxxxxx749": Insufficient capacity 使用motrix作为离线程序&#xff0c;报错后&#xff0c;会自动暂停下载 报错原因&#xff1a; 初始容量只有1G&#xff0c…

算法提高之潜水员

算法提高之潜水员 核心思想&#xff1a;二维01背包 两个容量v1v2注意状态计算时j和p可以<各自的v #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 1010,M 80,K 22;int f[K][M];int k,V1,V2;int main(){ci…