Linux : System V 共享内存

news2025/4/25 9:23:18

目录

一 前言

二 共享内存概念

 三 共享内存创建 

四 查看共享内存 

五 共享内存的删除

六 共享内存的关联 

七 共享内存去关联 

八 共享内存的使用(通信)

 九 共享内存的特点


一 前言

共享内存区是最快的IPC形式(进程间通信:IPC,InterProcess Communication) 一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用write()和read()来传递彼此的数据。


二 共享内存概念

 在上一篇进程间的管道通信中我们提到过,在进程间进行通信的时候,由于程序地址空间的存在,进程间的独立性使得他们之间的通信很麻烦,如果想要通信则需要两个进程看到同一份资源,上篇通过系统调用创建管道文件,使得进程之间看到共享资源(内存级文件),而本节进程间通信时进程之间看到的同一份资源是  共享内存。

🚀什么是共享内存呢? 

实际上,我们在学习程序地址空间的时候,如上图所示,我们已经看到了有一个区域的名字是共享区,在之前我们学习动静态库的时候,就说过动态库是在进程运行的时候加载到程序地址空间中的共享区的。当程序需要的时候,就会来到这部分读取数据。这一块内存就可以看作是一块只读共享区,共享内存进程通信实际上就是这个原理。

             共享内存进程通信就是在物理内存中开辟一块可以让所有进程都看到的内存空间,然后多个进程只需要向这块空间中读取或者写入数据,这样就达到了多个进程间一起通信的目的

也就是说,共享内存进程间的通信就是在物理内存中开辟一块空间当作共享内存,然后通信的进程们通过各自的页表将这块物理内存(共享内存)映射到各自的程序地址空间中 


 三 共享内存创建 

shmget()    (share memory  get)

这个接口的参数一共有三个 

  1. key_t key :  这是一个键值,key_t是一个整型,此参数其实是传入的是一个整数。通常这个键是通过  ftok() 函数从一个文件路径和一个项目ID生成的. 这个key值其实就是共享内存段在操作系统层面的唯一标识符。共享内存是Linux系统的一种进程通信的手段, 而操作系统中共享内存段一定是有许多的, 为了管理这些共享内存段, 操作系统一定会描述共享内存段的各种属性。类似其他管理方式,操作系统也会为共享内存维护一个结构体,在这个结构体内会维护一个key值,表示此共享内存在系统层面的唯一标识符,其一般由用户传入,为了区别每一块的共享内存,key的获取也是有一定的方法。

    ftok()函数的作用是将 一个文件 和 项目id 转换为一个System V IPC key值。用户就是使用这个函数来生成key值。

    他有两个参数,第一个参数显而易见是文件的路径,第二个参数则是随意的8bite位的数值。ftok()函数执行成功则会返回一个key值,这个key值是该函数通过传入文件的inode值和传入的proi_id值通过一定的算法计算出来的。由于每一个文件的inode值是唯一的,所以我们不用担心key值得重复。

  2. size_t size:  该参数传入的是想要开辟的共享内存的大小,单位是 byte字节。值得注意的是系统是按照4KB大小为单位开辟空间的,因为我们在磁盘一篇中学到系统I/O的单位大小就是4KB,也就是说无论这个参数传的是1、1024还是4096时,系统都会开辟4KB,但是虽然系统是按照4KB为单位开辟的空间,但是实际上用户能使用的空间的大小还是传入的size字节大小。

  3. int shmflg: 这里传入的是一组标识位,可以控制shemget的行为,它包括权限标志(类似0666)和命令标志,就像我们使用文件接口open时的O_WRONLY、O_RDONLY一样。共享内存接口标识符的两个最重要的宏是:IPC_CREAT、IPC_EXCL

    IPC_CREAT:传入该宏,表示创建一个新的共享内存段,若共享内存段已经存在,则获取此内存段;若不存在就创建一个新的内存段。

    IPC_EXCL:该宏需要和IPC_CREAT一起使用。表示如果创建的内存段不存在,则正常创建,若存在则返回错误。使用该宏保证的是此次使用shmget()接口创建成功时,创建出来的共享内存是全新的。

  4. shmget()函数的返回值,如果创建共享内存成功或者找到共享内存则返回共享内存的id,该id的作用是可以让通信的进程找到同一份块的资源。此id 是给上层用户使用,是为了标识共享内存。而key也是为了标识共享内存,但是是从系统层面来说。

🚍:接下来我们来学习和认识共享内存的创建

///comm.hpp/
#ifndef _COMM_HPP_
#define COMM_HPP_

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>


#define PATHNAME "."
#define PROJ_ID 0x66           //目标标识符 int proj_id 就是一个整型
#define MAX_SIZE 4096
key_t getKey()
{
    key_t k = ftok(PATHNAME,PROJ_ID);//可以获取同一个key
    if(k < 0)
    {
        //cin ,cout,cerr -------->stdin stdout stderr--->0 1 2
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;//strerror(errno) 打印错误码对应的错误信息
        exit(1);
    }
    return k;
}
/shm_client.cpp///
#include "comm.hpp"
int main()
{
  key_t k=getKey();
  printf("key: 0x%x\n",k); 
  return 0;
}
///shm_server.cpp
#include "comm.hpp"

int main()
{
    key_t k=getKey();
    printf("key: 0x%x\n",k);
    return 0;
}

 运行结果:

 🚋:key的值是什么并不重要,重要的是能进行唯一性标识。

有了key之后,我们就可以用唯一的key进行共享内存的创建

//comm.hpp
//将一些函数进行封装到comm.hpp,然后client和server进行调用
int getShmHelper(key_t k,int flags)
{
    int shmid=shmget(k,MAX_SIZE,flags);//创建共享内存函数shmget
    if(shmid < 0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}

//获取共享内存
int getShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT); //如果存在就获取,所以在客户端client,我们可以通过这个函数,来获取共享内存
}
//


//创建共享内存
int createShm(key_t k)//创建共享内存的工作,有服务端server来做
{
    return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0600);//如果存在就创建失败,保证了我们创建的一定是新的共享内存,0600 代表创建的共享内存可读可写
}
//server.cpp
#include "comm.hpp"

int main()
{
    key_t k=getKey();
    printf("key: 0x%x\n",k);

    int shmid=createShm(k);//服务端进行创建共享内存
    printf("shmid: %d\n",shmid);
    
    
    return 0;
}
///client.cpp
#include "comm.hpp"
int main()
{
  key_t k=getKey();
  printf("key: 0x%x\n",k);

  int shmid=getShm(k);//客户端进行获取共享内存
  printf("shmid: %d\n",shmid);

  
  return 0;
}

 运行结果:

 🍉这里的 key shmid 有什么区别呢?

key:是系统层面的,系统通过key来创建共享内存。

shmid:是上层用户层面,用户通过shmid来找到共享内存。


四 查看共享内存 

可是当我们再次运行服务端的时候,会发现出现如下问题:文件已存在

 这是什么原因呢?事实上,共享内存并不会随着进程的退出而退出,在创建共享内存的进程退出之后,共享内存是依旧存在于操作系统中的。而我们的服务端用key创建共享内存的时候,必须要求创建一个新的,如果当前的key对应的共享内存已经存在,则报错。

我们可以通过命令查看共享内存资源: ipcs -m  

这表明共享内存的生命周期是随着OS的,并不会因为进程的退出,而把共享内存删除。


五 共享内存的删除

我们可以使用 ipcrm -m (InterProcess Communication Remove Memory) 命令来删除。

那我们是通过 key 删除共享内存还是 shmid呢? 前面我们也说了key是内核层面,操作系统使用key,而删除共享内存,是指令操作,属于用户层面,所以我们通过shmid删除共享内存。

 我们还可以通过调用系统函数  shmctl 来对共享内存进行删除

我们在comm.hpp中对 shmctl进行封装成删除共享内存的函数。

/comm.hpp
void delShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)//返回-1代表调用失败
    {
        std::cerr<<"shmctl"<<errno<<":"<<strerror(errno)<<std::endl;
    }
}
/sever.cpp
#include "comm.hpp"

int main()
{
    key_t k=getKey();
    printf("key: 0x%x\n",k);

    int shmid=createShm(k);//服务端进行创建共享内存
    printf("shmid: %d\n",shmid);

    sleep(5);
    delShm(shmid);//调用删除共享内存函数
    
   
}

 再次测试:


六 共享内存的关联 

🌏:前面我们讲述了服务端对共享内存的创建以及删除,但是要想使得两个进程进行通信,我们还需要进行共享内存对两个进程关联起来。

 系统调用函数 shmat (attach)

 我们在comm.hpp中对 shmat进行封装成关联共享内存的函数。

/comm.hpp//
//关联共享内存
void* attachShm(int shmid)
{
    void* mem =shmat(shmid,nullptr,0);
    if((long long)mem==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(3);
    }
    return mem;
}
///server.cpp
#include "comm.hpp"

int main()
{
    key_t k=getKey();
    printf("key: 0x%x\n",k);

    int shmid=createShm(k);//服务端进行创建共享内存
    printf("shmid: %d\n",shmid);

    sleep(5);
    //关联共享内存
    char* start=(char*)attachShm(shmid);//返回值是共享内存地址
    printf("attach success,address start:%p\n",start);
   
    //删除
    sleep(5);
    delShm(shmid);

 
    return 0;
}

测试结果


七 共享内存去关联 

既然共享内存可以关联,自然也可以去关联,去关联并不是删除共享内存,而是去除进程与共享内存的联系。

系统调用函数 shmdt()    (detach)

我们在comm.hpp中对 shmdt进行封装成去关联共享内存的函数。

//comm.cpp//

//去关联共享内存
void detachShm(void* start)
{
    if(shmdt(start)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}

八 共享内存的使用(通信)

🌿,在前面我们做了以下工作

共享内存的创建------------->关联共享内存---------->(这里我们将进行共享内存的通信)---------------->关联共享内存------------------------------------>删除共享内存 

//client.cpp
// 4.使用即通信

  const char* message="hello server, 我是另外一个进程正在和你通信";
  pid_t id=getpid();
  int count =0;
  while(true)
  {
    sleep(1);
    //向共享内存输入消息
    snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,count++);
    //pid count message
  }
/server.cpp///
//4.使用
    while(true)
    {
        printf("client say: %s\n",start);//打印由客户端发来的消息,直接打印共享内存地址即可
        sleep(1);
    }

测试结果:

 

 完整测试如下

/comm.hpp
#ifndef _COMM_HPP_
#define COMM_HPP_

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>


#define PATHNAME "."
#define PROJ_ID 0x66           //目标标识符 int proj_id 就是一个整型
#define MAX_SIZE 4096
key_t getKey()
{
    key_t k = ftok(PATHNAME,PROJ_ID);//可以获取同一个key
    if(k < 0)
    {
        //cin ,cout,cerr -------->stdin stdout stderr--->0 1 2
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;//strerror(errno) 打印错误码对应的错误信息
        exit(1);
    }
    return k;
}


int getShmHelper(key_t k,int flags)
{
    int shmid=shmget(k,MAX_SIZE,flags);//创建共享内存函数shmget
    if(shmid < 0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}

//获取共享内存
int getShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT); //如果存在就获取,所以在客户端client,我们可以通过这个函数,来获取共享内存
}
//


//创建共享内存
int createShm(key_t k)//创建共享内存的工作,有服务端server来做
{
    return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0666);//如果存在就创建失败,保证了我们创建的一定是新的共享内存
}

//关联共享内存
void* attachShm(int shmid)
{
    void* mem =shmat(shmid,nullptr,0);
    if((long long)mem==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(3);
    }
    
    return mem;
}

//去关联共享内存
void detachShm(void* start)
{
    if(shmdt(start)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}
//删除共享内存
void delShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)//返回-1代表调用失败
    {
        std::cerr<<"shmctl"<<errno<<":"<<strerror(errno)<<std::endl;
    }
}

#endif
client.cpp///
#include "comm.hpp"
int main()
{
  //1.获取key
  key_t k=getKey();
  printf("key: 0x%x\n",k);

  //2.获取共享内存
  int shmid=getShm(k);//客户端进行获取共享内存
  printf("shmid: %d\n",shmid);

   sleep(5);

   //3.进行关联
   char* start=(char*)attachShm(shmid);
   printf("attach success,address start:%p\n",start);

   sleep(5);
  // 4.使用即通信

  const char* message="hello server, 我是另外一个进程正在和你通信";
  pid_t id=getpid();
  int count =0;
  while(true)
  {
    sleep(1);
    //向共享内存输入消息
    snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,count++);
    //pid count message
  }

  // sleep(5);

  //5.去关联
   detachShm(start);
  
  return 0;
}
/server.cpp
#include "comm.hpp"

int main()
{
    //1.创建key
    key_t k=getKey();
    printf("key: 0x%x\n",k);

    //2.创建共享内存
    int shmid=createShm(k);//服务端进行创建共享内存
    printf("shmid: %d\n",shmid);

    sleep(5);

    //3. 关联共享内存
    char* start=(char*)attachShm(shmid);//返回值是共享内存地址
    printf("attach success,address start:%p\n",start);
    
    //4.使用
    while(true)
    {
        printf("client say: %s\n",start);//打印由客户端发来的消息,直接打印共享内存地址即可
        sleep(1);
    }
    
    // // 5.去关联
    detachShm(start);
     
     sleep(5);
    
    //删除
     sleep(10);
     delShm(shmid);

   

    return 0;
}

 九 共享内存的特点

共享内存的优点:所以进程间通信,速度是最快的,能大大减少拷贝次数。

同样的代码,考虑到键盘输入和显示器输出 ,如果用管道来实现,会对数据进行几次拷贝?

共享内存的缺点:不给我们进行同步和互斥的操作,没有对数据做任何保护。

即客户端不进行写,服务端也一直进行读取,服务端不进行读取,客户端依然进行写入。 

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

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

相关文章

端到端语音识别案例

《DeepSeek大模型高性能核心技术与多模态融合开发&#xff08;人工智能技术丛书&#xff09;》(王晓华)【摘要 书评 试读】- 京东图书 语音识别这一技术正如其名&#xff0c;是通过精密地解析说话人的语音来识别并准确转写出其所说的内容。它不仅仅是一个简单的转录过程&#…

【软件系统架构】微服务架构

一、引言 随着互联网技术的快速发展&#xff0c;传统的单体应用架构在面对复杂业务需求时逐渐暴露出诸多问题&#xff0c;如开发效率低、部署困难、扩展性差等。为了解决这些问题&#xff0c;微服务架构应运而生。本文将详细介绍微服务架构的定义、发展历史、特点、细分类型、优…

Linux内核设计——(一)进程管理

目录 一、进程及线程简介 二、进程描述符 2.1 进程描述符简介 2.2 分配进程描述符 2.3 进程标识值 2.4 进程状态 2.5 进程上下文 三、进程创建 3.1 写时拷贝 3.2 fork()和vfork() 四、线程 4.1 Linux线程实现 4.2 内核线程 五、进程终结 5.1 删除进程描述符 5.…

22 安装第三方包

一、什么是第三方包 在 Python 的世界里&#xff0c;包就像是一个个功能强大的工具箱&#xff0c;它将多个 Python 模块收纳其中&#xff0c;而每个模块又蕴含着丰富多样的具体功能。可以说&#xff0c;一个包就是一系列同类功能的集合体&#xff0c;它们就像紧密协作的团队&a…

oracle 常用函数的应用

在使用开发中会经常遇到数据类型转换、显示系统时间等情况&#xff0c;需要使用函数来实现。通过函数来实现业务需求会非常的省事便捷&#xff0c;函数可以用在适当的dml语句和查询语句中。 Oracle 数据库中主要使用两种类型的函数&#xff1a; (1)单行函数&#xff1a;对每一个…

“上云入端” 浪潮云剑指组织智能化落地“最后一公里”

进入2025年&#xff0c;行业智能体正在成为数实融合的核心路径。2025年初DeepSeek开源大模型的横空出世&#xff0c;通过算法优化与架构创新&#xff0c;显著降低算力需求与部署成本&#xff0c;推动大模型向端侧和边缘侧延伸。其开源策略打破技术垄断&#xff0c;结合边缘计算…

CentOS 7 如何挂载ntfs的移动硬盘

CentOS 7 如何挂载ntfs的移动硬盘 前言一、查看硬盘并尝试挂载(提示无法挂载)二、yum安装epel-release提示yum被锁定三、强行终止yum的进程四、yum安装epel-release完成五、yum安装ntfs-3g六、此时可正常挂载NTFS硬盘 前言 CentOS 7默认情况下是不支持NTFS的文件系统&#xff…

pytorch+maskRcnn框架训练自己的模型以及模型导出ONXX格式供C++部署推理

背景 maskrcnn用作实例分割时&#xff0c;可以较为精准的定位目标物体&#xff0c;相较于yolo只能定位物体的矩形框而言&#xff0c;优势更大。虽然yolo的计算速度更快。 直接开始从0到1使用maskrCNN训练自己的模型并并导出给C部署&#xff08;亲测可用&#xff09; 数据标注…

①EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

型号 协议转换通信网关 EtherCAT 转 Modbus TCP MS-GW15 概述 MS-GW15 是 EtherCAT 和 Modbus TCP 协议转换网关&#xff0c;为用户提供一种 PLC 扩展的集成解决方案&#xff0c;可以轻松容易将 Modbus TCP 网络接入 EtherCAT 网络 中&#xff0c;方便扩展&#xff0c;不受限…

《Oracle服务进程精准管控指南:23c/11g双版本内存优化实战》 ——附自动化脚本开发全攻略

正在学习或者是使用 Oracle 数据库的小伙伴&#xff0c;是不是对于那个一直启动且及其占用内存的后台进程感到烦躁呢&#xff1f;而且即使是手动去开关也显得即为麻烦&#xff0c;所以基于我之前所学习到的方法&#xff0c;我在此重新整理&#xff0c;让大家动动手指就能完成开…

Java单列集合[Collection]

目录 1.Collection单列集合 1.1单列集合各集合特点 1.2、Collection集合 1.2.1、Collection方法 1.2.2、Collection遍历方式 1.2.2.1、迭代器遍历集合 1.2.2.2、增强for遍历集合 1.2.2.3、forEach遍历集合&#xff08;JDK8之后&#xff09; 1.2.2.4、遍历案例 1.3、Li…

如何在ONLYOFFICE插件中添加自定义AI提供商:以通义千问和Kimi为例

随着 ONLYOFFICE AI 插件的发布&#xff0c;我们极大地提升了编辑器的默认功能。在ONLYOFFICE&#xff0c;我们致力于提供强大且灵活的解决方案&#xff0c;以满足您的特定需求。其中一项便是能够在 AI 插件中添加自定义提供商。在这篇文章中&#xff0c;我们将展示如何将通义千…

Spark,配置hadoop集群1

配置运行任务的历史服务器 1.配置mapred-site.xml 在hadoop的安装目录下&#xff0c;打开mapred-site.xml&#xff0c;并在该文件里面增加如下两条配置。 eg我的是在hadoop199上 <!-- 历史服务器端地址 --> <property><name>mapreduce.jobhistory.address…

FPGA实现4K MIPI视频解码H265压缩网络推流输出,基于IMX317+VCU架构,支持4K60帧,提供工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 MIPI 编解码方案我这里已有的视频图像编解码方案 3、详细设计方案设计框图FPGA开发板IMX317摄像头MIPI D-PHYMIPI CSI-2 RX Subsystem图像预处理Sensor …

【Linux】网络概念

目录 网络模型 OSI七层模型 TCP/IP五层(或四层)模型 网络传输 网络传输基本流程 封装与分用 以太网通信&#xff08;局域网传输&#xff09; 跨网络传输 网络模型 OSI七层模型 TCP/IP五层(或四层)模型 网络层和传输层就是操作系统的一部分 网络传输 网络传输基本流程…

【模拟CMOS集成电路设计】电荷泵(Charge bump)设计与仿真(示例:栅极开关CP+轨到轨输入运放+基于运放CP)

【模拟CMOS集成电路设计】电荷泵&#xff08;Charge bump&#xff09;设计与仿真 0前言1电荷泵1.1 PFD/CP/电容器级联1.2 PFD/CP/电容传递函数 2基本电荷泵(CP)结构2.1“漏极开关”结构2.2“源极开关”结构2.3“栅极开关”结构 3 CP的设计与仿真13.1 P/N电流源失配仿真3.2 电荷…

Kafka消息丢失全解析!原因、预防与解决方案

作为一名高并发系统开发工程师&#xff0c;在使用消息中间件的过程中&#xff0c;无法避免遇到系统中消息丢失的问题&#xff0c;而Kafka作为主流的消息队列系统&#xff0c;消息丢失问题尤为常见。 在这篇文章中&#xff0c;将深入浅出地分析Kafka消息丢失的各种情况&#xf…

VS Code 云服务器远程开发完整指南

VS Code Ubuntu 云服务器远程开发完整指南 远程开发是现代开发者的标配之一&#xff0c;特别是在使用云服务器&#xff08;如 Ubuntu&#xff09;进行部署、测试或大项目开发时&#xff0c;利用 VS Code 的 Remote-SSH 插件&#xff0c;可以像本地一样顺滑操作远程服务器。本…

【Rtklib入门指南】4. 使用RTKLIB进行载波相位差分定位(RTK)

RTK RTK&#xff08;Real-Time Kinematic&#xff0c;实时动态&#xff09;定位技术是一种高精度的卫星导航技术。相比传统的GPS定位技术&#xff0c;RTK能够在厘米级别的精度范围内提供定位结果。这使得RTK技术在无人机、自动驾驶、工程测绘、农业机械自动化等领域具有广泛应用…

【SECS】初识SECS协议

【SECS】初识SECS协议 基本知识流和功能函数数量官方文件中缩写标注正常是不是都是主机向设备端?对数据信息中第1字节第1-2位官网介绍 S1F1双向指令说明测试H发起端E发起端 参考资料 基本知识 SECS&#xff08;SEMI Equipment Communications Standard&#xff09;即半导体设…