当前位置: 首页 > news >正文

Linux : System V 共享内存

目录

一 前言

二 共享内存概念

 三 共享内存创建 

四 查看共享内存 

五 共享内存的删除

六 共享内存的关联 

七 共享内存去关联 

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

 九 共享内存的特点


一 前言

共享内存区是最快的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;
}

 九 共享内存的特点

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

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

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

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

相关文章:

  • 基于srpingboot智慧校园管理服务平台的设计与实现(源码+文档+部署讲解)
  • 基于SpringBoot的“小说阅读平台”的设计与实现(源码+数据库+文档+PPT)
  • 基于springboot+vue的校园数字化图书馆系统
  • 【RAGFlow】ubuntu22部署ragflow(v0.17.2)
  • Cursor 使用 APIkey 配置 Anthropic Claude BaseURL , gpt-4o,deepseek等大模型代理指南
  • 基于单片机的并联均流电源设计(论文+源码)
  • 《C语言:从诞生到成为编程基石的历史之旅》
  • c# 获取redis中所有的value
  • 快速入手-基于DRF跨域配置django-cors-headers(十三)
  • nodejs 文件相关
  • GRU原理
  • 1.1 轴承故障数据预处理与时频图提取
  • 踏过强化学习的每一步推导
  • C++基础算法(插入排序)
  • 学习如何设计大规模系统,为系统设计面试做准备!
  • Uniapp自定义TabBar组件全封装实践与疑难问题解决方案
  • sscanf() 用法详解
  • docker 安装多次 Jenkins后,插件全部报错
  • G8 的 Morph(形态/变形)转移到 Genesis 9 (G9)
  • 《Java八股文の文艺复兴》第八篇:时空裂缝中的锁竞争——当Synchronized与量子纠缠重构线程安全
  • 网站建设协议书 印花税/优化是什么梗
  • 西安免费做网站公司/网上全网推广
  • 惠州网站网站建设/搜索关键词排行榜
  • 网站建设要求 牛商网/线下推广方案
  • 建设网站合同/电商网站订烟平台
  • 263企业邮箱登录登录/自己怎么优化我网站关键词