Linux——共享内存

news2025/1/24 8:53:21

Linux——共享内存

  • 什么是共享内存
  • 共享内存原理
  • Linux下共享内存的接口
    • 创建/获取共享内存:shmget
      • ftok函数
    • 映射共享内存到进程地址空间:shmat
  • 解除共享内存映射:shmdt
    • 删除共享内存段:shmctl
  • 利用共享内存进行通信

我们之前学习了匿名管道命名管道来进行进程之间的通信,其实除了管道之外,我们还有一种方式就是共享内存

什么是共享内存

共享内存(Shared Memory)是进程间通信(Inter-Process Communication, IPC)的一种方式,它允许两个或多个进程访问同一块物理内存区域,从而实现数据的快速、直接交换。在操作系统层面,共享内存指的是操作系统创建或映射到多个进程地址空间的同一块内存区域,使得这些进程可以直接读写这块内存,如同访问本进程的私有内存一样。

共享内存的工作机制通常涉及以下步骤:
创建共享内存:通过系统调用(如Unix/Linux下的shmget函数)创建一个共享内存段,并指定其大小和权限。
映射共享内存:每个需要访问共享内存的进程都需通过系统调用(如shmat函数)将共享内存段映射到自身的地址空间。
访问共享内存:映射成功后,进程就可以像访问普通内存一样读写这块共享内存区域,从而实现实时的数据交换。
同步与互斥:由于多个进程可以同时访问同一内存区域,为了避免数据竞争和不一致,通常需要借助其他同步机制(如信号量、互斥锁等)来保证对共享内存的有序和安全访问。

共享内存的优点在于速度快,因为它是内存级别的通信,没有额外的复制开销。缺点则是需要用户程序自行处理同步问题,否则容易引发竞态条件和死锁等问题。

共享内存原理

共享内存是操作系统支持的一种进程间通信(IPC,Inter-Process Communication)机制,它允许多个进程访问同一块物理内存区域,从而实现高效的数据共享和通信。以下是共享内存的基本原理:

  1. 内存区域创建
    在操作系统层面,通过系统调用(如Unix/Linux下的shmget())创建一块共享内存区域。创建时需要指定一个键值(通常通过ftok()函数生成)来标识这块内存,同时指定内存区域的大小。
  2. 内存映射
    一旦共享内存区域被创建,各个希望参与通信的进程可以调用shmat()函数,将这块共享内存映射到它们各自的地址空间。映射成功后,每个进程都可以通过本地内存地址访问这块共享内存,就像访问普通的内存一样。
  3. 数据同步
    由于多个进程可以直接读写同一块内存区域,因此必须有适当的同步机制来保证数据的一致性和完整性,如互斥锁(mutexes)、信号量(semaphores)或其他同步原语,以避免数据竞争(race conditions)。
  4. 内存解除映射和删除
    当进程不再需要访问共享内存时,可以调用shmdt()函数来解除映射关系,解除映射后,进程无法再通过本地地址访问共享内存。当所有进程都解除映射后,如果有必要,可以通过shmctl()函数并设置适当的命令来删除共享内存区域。
  5. 优点
  • 高效性:由于数据不需要在进程间复制,共享内存是最快捷的IPC方式之一。
  • 低开销:相比消息队列、管道等其他IPC机制,共享内存不需要额外的复制和包装开销。
  1. 挑战
  • 同步复杂性:确保多个进程对共享内存的并发访问是一致的是一项复杂任务,需要良好的同步策略和编程技巧。
  • 内存管理:操作系统需要跟踪哪些进程正在使用共享内存,何时应该回收内存资源。

简而言之,共享内存的核心原理是利用操作系统提供的功能,让多个进程可以直接读写同一块物理内存区域,从而实现进程间的数据交换。为了正确使用共享内存,程序员需要谨慎处理同步问题,并且在进程生命周期中妥善管理内存映射和解除映射。
在这里插入图片描述

Linux下共享内存的接口

在Linux系统中,使用共享内存进行进程间通信涉及以下几个关键的系统调用接口:

创建/获取共享内存:shmget

在这里插入图片描述

shmget 函数用于创建一个新的共享内存段或者获取已存在的共享内存标识符(shmid)。参数说明如下:
key:通常是通过 ftok() 函数生成的一个键值,用来唯一标识共享内存段。
size:要创建的共享内存段的大小(字节数)。
shmflg:标志位,可以指定创建模式(如 IPC_CREAT 表示若不存在则创建)、权限位(如 S_IRUSR | S_IWUSR 表示所有者具有读写权限)和其他选项。

ftok函数

ftok() 函数在 Unix 和 Linux 系统中用于生成一个用于进程间通信(IPC)的唯一键值(key),尤其是配合 System V IPC 机制中的消息队列(message queues)、信号量(semaphores)以及共享内存(shared memory)。这个键值是系统内核用来识别不同 IPC 资源的关键标识符。

函数原型如下:

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

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

参数说明:

  • pathname:是一个字符串,代表系统中一个已存在的文件的路径名。通常会选择应用程序能够访问并知道其稳定的路径,例如可执行文件或配置文件。
  • id:一个整数值,作为项目的子序列号。它可以被用来区分同一文件的不同 IPC 资源,不过通常设置为非零的常数值即可。
    函数返回:
  • 如果成功,返回一个类型为 key_t 的 IPC 键值,该键值是基于给定的文件路径和项目 ID 计算得出的,理论上在同一系统上应当是唯一的。
  • 如果失败,返回 (key_t) -1,并且会设置 errno 以指示出错原因。

我们结合这两个来创建一块共享内存

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<iostream>


const char* path_name = "../include";
#define MY_PROJECT_ID 1
#define MYSIZE 4096

//获取key值
key_t GetKey()
{
    key_t key = ftok(path_name,MY_PROJECT_ID);
    if(key < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }

    return key;
}

//获取shmid值
int Getshmid(const key_t& key)
{
    int shmid = shmget(key,MYSIZE,IPC_CREAT | 0666);
    if( shmid == -1)
    {
        perror("shmget fail");
        exit(EXIT_FAILURE);
    }

    return shmid;
}

这个时候,我们再来看:

#include"shmnt.hpp"

int main()
{
    key_t key = GetKey();
    int shmid = Getshmid(key);

    return 0;
}

我们创建好了共享内存,我们可以用ipcs -m来查看:
在这里插入图片描述

我们创建好了共享内存,下一步就是将共享内存连接到进程的地址空间:

映射共享内存到进程地址空间:shmat

在这里插入图片描述

shmat 函数将共享内存段连接到调用进程的地址空间中。
shmid:由 shmget 返回的共享内存标识符。
shmaddr:通常设为 NULL,表示让系统选择合适的地址来映射;也可以指定特定地址,但这样做有风险且需要额外注意。
shmflg:标志位,比如 SHM_RDONLY 表示以只读方式映射共享内存。

 //将该shmid挂到虚拟地址空间
//将该shmid挂到虚拟地址空间
 void* Attachshmid(int shmid)
 {
    return shmat(shmid,nullptr,0);
 }

这个时候,我们再来看:

int main()
{
    key_t key = GetKey();
    int shmid = Getshmid(key);
    void *share_adderss = Attachshmid(shmid);

    if(share_adderss == (void*)-1)
    {
        perror("shmat fail");
        return 1;
    }
    else
    {
        std::cout<<"has be attached"<<std::endl;
        sleep(10);
    }
    return 0;
}

在这里插入图片描述
我们看到,我们的连接数从0变成了1,意味着程序运行期间,进程已经将该共享内存段映射到了它们自己的地址空间

解除共享内存映射:shmdt

对共享内存段进行取消映射。

int shmdt(const void * __shmaddr);

shmaddr:这是通过 shmat() 函数成功映射共享内存时返回的地址指针。成功返回0,错误返回1

我们写这样一段函数:

 //解除
 void Disattachshmid(const void* share_address)
 {
    if(shmdt(share_address) == -1)
    {
        perror("shmdt fail");
        return;
    }

    std::cout<<"has be disattached process"<<std::endl;
    sleep(10);
 }
#include"shmnt.hpp"

int main()
{
    key_t key = GetKey();
    int shmid = Getshmid(key);

    void *share_adderss = Attachshmid(shmid);

    if(share_adderss == (void*)-1)
    {
        perror("shmat fail");
        return 1;
    }
    else
    {
        std::cout<<"has be attached"<<std::endl;
        sleep(10);
    }
    

    Disattachshmid(share_adderss);
    return 0;
}

在这里插入图片描述
执行这段脚本,我们可以监视共享内存的使用情况:
在这里插入图片描述
我们看到,已经成功将映射关系消除了。

删除共享内存段:shmctl

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

对共享内存段进行控制操作,其中 cmd 参数可以是 IPC_RMID 来删除共享内存段。
shmid:共享内存标识符。
cmd:控制命令,如 IPC_RMID 表示删除。
buf:如果是其他命令可能需要指向 shmid_ds 结构体的指针,但在删除操作中通常设置为NULL。

//删除共享内存
 void Deletshare(int shmid)
 {
    if(shmctl(shmid,IPC_RMID,nullptr) == -1)
    {
        perror("shcmtl fail");
        exit(EXIT_FAILURE);
    }
    else
    {
        std::cout<<"has successfully deleted"<<std::endl;
    }

 }
#include"shmnt.hpp"

int main()
{
    key_t key = GetKey();
    int shmid = Getshmid(key);

    void *share_adderss = Attachshmid(shmid);

    if(share_adderss == (void*)-1)
    {
        perror("shmat fail");
        return 1;
    }
    else
    {
        std::cout<<"has be attached"<<std::endl;
        sleep(10);
    }
    

    Disattachshmid(share_adderss);

    Deletshare(shmid);
    return 0;
}

在这里插入图片描述

利用共享内存进行通信

我们之前的大部分工作都只是把准备工作做好了,我们还没有进行通信,我们可以利用共享内存进行通信:
我们准备一个client.cc:

#include"shmnt.hpp"

int main()
{
    key_t key = GetKey();
    int shmid = Getshmid(key);

    //挂载
    int* share_adderss = (int*)Attachshmid(shmid); //强转为int*类型

    //进行通信
    for(int i = 0; i < 10; i++)
    {
        share_adderss[i] = i; //写入数据,以便读取
        std::cout<<"client say "<< share_adderss[i] <<std::endl;
        sleep(1);
    }

    //取消挂载
    Disattachshmid(share_adderss);
    return 0;
}

再准备一个server.cc,读取client.cc写入共享内存中的内容:

#include"shmnt.hpp"

int main()
{
    key_t key = GetKey();
    int shmid = Getshmid(key);

    int* share_adderss = (int*)Attachshmid(shmid); //强转为int*类型

    if(share_adderss == (void*)-1)
    {
        perror("shmat fail");
        return 1;
    }
    else
    {
        std::cout<<"has be attached"<<std::endl;
        //sleep(10);
    }

    //进行通信
    int i = 0;
    while(true)
    {
        if(i < 10)
        {
            std::cout<<"server say: "<< share_adderss[i] << std::endl;
            i++;
            sleep(1);
        }
        else
        {
            break;
        }
    }

    Disattachshmid(share_adderss);

    Deletshare(shmid);
    return 0;
}

在这里插入图片描述
这里注意,共享内存实现通信并不保证同步机制,如果我这里写入的速度变慢一点:
在这里插入图片描述就会出现乱读,这时候我们要保证手动保证同步机制。

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

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

相关文章

香港服务器怎么看是CN2 GT线路还是CN2 GIA线路?

不知道有没有小伙伴们注意过&#xff0c;很多人在租用香港服务器的时候都习惯性选择 CN2 线路&#xff1f;仿佛香港服务器是否采用 CN2 线路成为个人企业选择香港服务器的一个标准。其实&#xff0c;香港服务器有CN2、优化直连(163)、BGP多线(包含了国际和国内线路)&#xff0c…

Unity 刮刮乐(优化极简)

废话不多说上代码&#xff0c;上图片&#xff0c;欢迎对Unity有兴趣的伙伴和我一起探讨学习 using UnityEngine; using UnityEngine.UI;public class ScratchCardWithSpriteRenderer : MonoBehaviour {// 公开背景和遮罩的Sprite Renderer组件public SpriteRenderer background…

java复原IP 地址(力扣Leetcode93)

复原IP 地址 力扣原题链接 问题描述 有效 IP 地址正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 ‘.’ 分隔。 例如&#xff1a;“0.1.2.201” 和 “192.168.1.1” 是有效 IP 地址&#xff0c…

爬取b站音频和视频数据,未合成一个视频

一、首先找到含有音频和视频的url地址 打开一个视频&#xff0c;刷新后&#xff0c;找到这个包&#xff0c;里面有我们所需要的数据 访问这个数据包后&#xff0c;获取字符串数据&#xff0c;用正则提取&#xff0c;再转为json字符串方便提取。 二、获得标题和音频数据后&…

基于LSB(最低有效位)的图像水印算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

【蓝桥杯第十四届省赛B】(部分详解)

【01串的熵】 https://www.lanqiao.cn/problems/3498/learning/?subject_code1&group_code4&match_num14&match_flow1&origincup #include <iostream> #include<cmath> using namespace std; int main() {double n23333333;double sum0;for(int…

【跟着CHATGPT学习硬件外设 | 04】ADC

本文根据博主设计的Prompt由CHATGPT生成&#xff0c;形成极简外设概念。 &#x1f680; 1. 概念揭秘 1.1 快速入门 模数转换器&#xff08;ADC&#xff0c;Analog-to-Digital Converter&#xff09;是一种将模拟信号转换为数字信号的电子设备。模拟信号通常表示物理测量的连…

Learning To Count Everything

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;学习数一切东西1、研究背景2、提出方法3、模块详细3.1、多尺度特征提取模块3.2、密度预测模块 4、损失函数5、性能对比6、贡献 二…

mybatis标签解析教程

mybatis标签解析 标签结构 我们在mapper的xml文件中&#xff0c;使用动态SQL&#xff0c;那么这些标签<where>、<if>、<set>、<ForEach>、<Choose>、<Trim> 等是怎么解析的呢&#xff1f;我们先看包的结构 包结构中&#xff0c;script…

算法沉淀——拓扑排序

前言&#xff1a; 首先我们需要知道什么是拓扑排序&#xff1f; 在正式讲解拓扑排序这个算法之前&#xff0c;我们需要了解一些前置知识&#xff08;和离散数学相关&#xff09; 1、有向无环图&#xff1a; 指的是一个无回路的有向图。 入度&#xff1a;有向图中某点作为图…

微服务(基础篇-007-RabbitMQ部署指南)

目录 05-RabbitMQ快速入门--介绍和安装_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1LQ4y127n4?p65&vd_source60a35a11f813c6dff0b76089e5e138cc 1.单机部署 1.1.下载镜像 1.2.安装MQ 2.集群部署 2.1.集群分类 2.2.设置网络 视频地址&#xff1a; 05-Rab…

Topaz Gigapixel AI for Mac 图像放大软件

Topaz Gigapixel AI for Mac是一款专为Mac用户设计的智能图像放大软件。它采用了人工智能技术&#xff0c;特别是深度学习算法&#xff0c;以提高图像的分辨率和质量&#xff0c;使得图像在放大后仍能保持清晰的细节。这款软件的特点在于其能够将低分辨率的图片放大至高分辨率&…

银河麒麟V10:sudo: /usr/bin/sudo 必须属于用户 ID 0(的用户)并且设置 setuid 位

一、引起原因&#xff1a; sudo chmod -R 777 bin 修改了/usr/bin/sudo的权限&#xff0c;引发后续问题。 二、现象&#xff1a; sudo执行命令报错&#xff1a; sudo: /usr/bin/sudo 必须属于用户 ID 0(的用户)并且设置 setuid 位 三、解决方法&#xff08;知道root密码&…

Java复习第十四天学习笔记(CSS),附有道云笔记链接

【有道云笔记】十四 3.30 CSS https://note.youdao.com/s/3VormGXs 一、CSS定义和基本选择器 CSS定义&#xff1a;cascading style sheet 层叠样式表。 语法&#xff1a; 选择器 { 属性名1:属性值1; 属性名2:属性值2; 属性名3:属性值3; 属性名4:属性值4; } CSS使用&a…

经典永不过时 Wordpress模板主题

经得住时间考验的模板&#xff0c;才是经典模板&#xff0c;带得来客户的网站&#xff0c;才叫NB网站。 https://www.jianzhanpress.com/?p2484

基于大数据的学习资源推荐系统的设计与实现(论文+源码)_kaic

摘 要 本文首先实现了学习资源推送管理技术的发展&#xff0c;随后依照传统的软件开发流程&#xff0c;最先为系统挑选适用的语言和软件开发平台&#xff0c;依据需求分析开展控制模块制作和数据库查询构造设计&#xff0c;依据系统整体功能模块的设计&#xff0c;制作系统的功…

武汉星起航引领亚马逊跨境电商新浪潮,助推卖家向全球拓展

在全球化的浪潮中&#xff0c;跨境电商以其独特的魅力和无限潜力&#xff0c;正成为推动国际贸易发展的重要引擎。亚马逊&#xff0c;作为全球电商平台的佼佼者&#xff0c;以其卓越的技术、高效的服务和广阔的市场覆盖&#xff0c;引领着跨境电商的新风潮。而在这股风潮中&…

JUC并发编程——对于synchronized关键字的理解

现象&#x1f50d;&#xff1a; 两个线程对初始值为 0 的静态变量一个做自增&#xff0c;一个做自减&#xff0c;各做 5000 次&#xff0c;最后输出的 counter一定为0 吗&#xff1f; Slf4j(topic "c.Test17") public class Test17 {static int counter 0;public…

Js逆向简单分析-某网站登录案例

文章目录 概要整体流程1.打开网站&#xff0c;输入数据进行登录2.对getlogin.php进行查看分析3.对于登录请求连接进行断点调试4.堆栈跟踪5.在网络上找在线md5加密 友情推荐 概要 某网站登录流量包逆向分析。 整体流程 1.打开网站&#xff0c;输入数据进行登录 用户名&#…

【C语言基础】:自定义类型(一)--> 结构体

文章目录 一、内置类型与自定义类型1.1 内置类型&#xff08;基本数据类型&#xff09;1.2 自定义类型 二、结构体2.1 结构体的声明2.2 结构体变量的创建和初始化2.3 结构体的特殊声明2.4 结构体的自引用 三、结构体内存对齐3.1 对齐规则3.2 为什么存在内存对齐3.3 修改默认对齐…