• 进程间通信之共享内存


    进程间通信之共享内存

    1.1共享内存的介绍

    共享内存是最快的进程间通信形式,是通过调用系统接口(shmget)由操作系统开辟一块物理内存,然后通过页表映射到进程地址空间中,进而使得用户可以使用这块内存,两个进程间共用一块共享内存通过数据交互就可以建立通信。

    1.2建立共享内存前后虚拟内存和物理内存之间的示意图

    未创建共享内存之前的虚拟内存和物理内存之间的联系

    在这里插入图片描述

    • 虚拟地址和物理内存之间是通过页表建立联系的,我们看到的都是物理内存映射到页表上的虚拟内存而不是真实的物理内存,
    • 其中task_struct 是进程的数据结构(通过该数据结构来描述 管理该数据结构就是对进程的管理) mm_struct 是进程地址空间的数据结构

    创建共享内存之后的虚拟内存和物理内存之间的联系

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5X339Qj-1656584415666)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20220629224323893.png)]

    • 创建了共享内存后,将两个进程的物理地址都指向该块物理内存后就可以使得两个进程看到同一份资源,从而实现信息的交互,实现通信

    2.1通过共享内存进行通信的具体步骤介绍

    • 1、申请共享内存 通过函数shmget()创建(申请)出共享内存

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdtuovKM-1656584415668)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20220629225840008.png)]

    • 2、将共享内存挂接到进程地址空间 通过函数shmat() 将进程的和该共享内存进行挂接;实质上就是进程开辟新的虚拟地址空间,通过修改页表,取消原来的虚拟内存和物理内存的映射关系,将共享内存(这块物理内存)与进程的虚拟内存建立映射关系,使得进程可以使用共享内存

    在这里插入图片描述

    • 3、去关联共享内存 通过函数 shmdt() 去关联共享内存;修改页表,取消共享内存与虚拟内存之间的映射关系,恢复进程原来的物理内存和虚拟内存之间的映射关系

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CiDzFixX-1656584415671)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20220629234723251.png)]

    • 4、释放共享内存 通过函数int shmctl(int shmid, int cmd, struct shmid_ds *buf);释放共享内存 ;共享内存的生命周期是随着内核的而不是随着进程的结束而自动释放的(不同于文件),如果使用完不释放就会使得内存越来越少,造成内存泄漏,故申请的共享内存使用完后要记得释放

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGPCMcJS-1656584415672)(C:\Users\华哥\AppData\Roaming\Typora\typora-user-images\image-20220629234820210.png)]

    2.2共享内存查看和删除的指令

    2.2.1查看共享内存

    //输入指令
    ipcs -m 
    
    • 1
    • 2

    2.2.2删除共享内存

    //输入指令
    ipcrm -m +[shmid]//shmid是共享内存id(用户)
    
    • 1
    • 2

    3.1两个进程通过同一块共享内存进行通信简单演示

    • 两个进程公用一块共享内存,然后直接对该内存操作,就像mallocc出来的空间一样使用,不用调用系统接口read write之类的来访问这块内存。
      在这里插入图片描述

    具体的代码:

    //头文件 包含用ftok()创建的key 需要的参数Pathname和proj_id
    #include<stdio.h>
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/shm.h>
    #include<unistd.h>
    #define pathname "/home/zh/nodeput/lesson23"
    #define proj_id 0x36
    #define SIZE 4096
    -------------------------------------------
    //client.c
    #include"serve.h"
    int main()
    {
      key_t key=ftok(pathname,proj_id);
      if(key<0)
      {
        perror("ftok");
      }
    
      int shmid=shmget(key,SIZE,IPC_CREAT);//创建
      char* mem=(char*)shmat(shmid,NULL,0);//挂接
    
      //开始通信
      
      int i=0;
      while(1)
      {
        mem[i] = 'A'+ i;//这里是直接对开辟出来的共享内存进行使用 对其进行赋值操作  然后可以让另一个进程打印出来
        i++;
        mem[i]='\0';
        sleep(1);
      }
      shmdt(mem);//去关联 
      shmctl(shmid,IPC_RMID,NULL);//释放
      return 0;
    }
    -------------------------
    //serve.c
    
    #include"serve.h"
    int main()
    {
      key_t key=ftok(pathname,proj_id);//生成key
      if(key<0)
      {
        perror("ftok");
        return 1;
      }
      int shmid= shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0644);//通过key创建出共享内存
      if(shmid<0)
      {
        perror("shmget");
        return 2;
      }
      char* mem=(char*)shmat(shmid,NULL,0);//挂接共享内存
    
      //开始通信
      while(1)
      {
        printf("client sent: %s\n",mem);
        sleep(1);
      }
      shmdt(mem);//去关联共享内存
      shmctl(shmid,IPC_RMID,NULL);//释放共享内存
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    3.1.1需要注意的几个点:

    • 1、*key_t ftok(const char pathname,int proj_id)**函数的第一个参数是一个必须存在的路径,可以是文件也可以是目录
    • 2、该路径是必须存在的,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的权限无关
    • 3、proj_id是可以根据自己的约定,随意设置。这个数字,有的称之为project ID 在UNIX系统上,它的取值是1到255,8个比特位,2个字节

    3.1.2几个函数的参数返回值的介绍

    1.创建共享内存函数

    int shmget(key_t key,size_t size,int shmflg)

    • 第一个参数:key key 是通过ftok()函数根据pathname和proj_id创建出来的具有唯一映射关系的一个值,帮助操作系统用来标识一块共享内存

    • 第二个参数:size size是我们要创建的共享内存的大小,我们看到的内存实际上是虚拟内存,是物理内存通过页表映射出来的,总是提到映射,可计算机到底是怎样将虚拟地址空间映射到实实在在的物理内存上的呢?这就要提到内存分段和分页模式了 这个转换过程有操作系统和CPU共同完成. 操作系统为CPU设置好页表 CPU通过MMU单元进行地址转换 简单的说,就是人为的把线性地址空间划分为一个一个4k的(几乎所有的PC上的操作都使用4KB大小的页)逻辑页,把内存页以同样的方法等分为固定大小的物理页 4kb,4096字节,为了更好的使用内存size最好是对齐4096 也就是说最好设置成4096的整数倍

    • 第三个参数 :shmflg 主要是和一些标志有关,包括IPC_CEREAT和IPC_EXCL,这两个与open()的O_CREAT和O_EXCL类似。

      • 使用说明:
      • 1、如果是IPC_CREAT单独使用就是如果该函数对应key的共享内存不存在就创建并且返回该该块共享内存的shmid 如果该块共享内存已经存在了就直接返回其对应的shmid IPC_CREAT单独使用shmget()函数调用一定会成功,要么返回的是已经存在的共享内存的shmid要么是返回新创建的共享内存的shmid
      • 2、如果是IPC_CREAT和IPC_EXCL 配合使用就是当且仅当该块共享内存不存在时才会创建并且返回其对应的神shmid,否则就会返回-1
      • 3、IPC_EXCL标志单独使用并没有太大的意义 只有和IPC_CREAT配合使用才能发挥其作用,可以保证成功创建的共享内存必定是新创建的,而不是已存在的。
      • 4、对于用户的读取和写入许可指定SHM_R和SHM_W(SHM_R>3)和(SHM_W>3)是一组读取和写入许可,(SHM_R>6)和(SHM_W>6)是全局读取和写入许可

    2.挂接共享内存函数

    **void shmat(int shmid, const void shmaddr, int shmflg);

    • 第一个参数:shmid 就是shmget创建好的共享内存的shmid(一个整数标识符),可以通过这个标识符释放掉对应的共享内存
    • 第二个参数:shmaddr 是指定挂接到的地址,如果为NULL则由操作系统自动选择一个合适的地址;如果不为空且没有指定SHM_RND,则此段链接到shmaddr所指定的地址上,如果shmaddr非空并且指定了SHM_RND则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上.SHM_RND命令的意思是取整,SHMLAB的意思是低边界地址的倍数,它总是2的乘方。该算式是将地址向下取最近一个 SHMLAB的倍数。除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为0,以便由内核选择地址
    • 第三个参数:shmflg如果是0 就是读写模式,SHM_RD0NLY就是只读模式

    3.去关联共享内存函数

    **void shmat(int shmid, const void shmaddr, int shmflg)

    • 第一个参数:shmid 就是shmget创建好的共享内存的shmid(一个整数标识符),可以通过这个标识符释放掉对应的共享内存
    • 第二个参数和第三个参数和上面的shmat()相同

    4.释放共享内存函数

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

    • 第一个参数:共享内存的标识符
    • 第二个参数:操作命令 有IPC_STAT、IPC_SET、IPC_RMID
      • IPC_STAT是查看共享内存的状态,把共享内存的shmid_ds结构复制到buf中
      • IPC_SET是改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid结构内
      • IPC_RMID是释放这块共享内存
    • 第三个参数:buf 共享内存的管理结构,结构体类型
    • 返回值:成功0 出错-1

    4.1共享内存通信和管道通信的比较

    1.共享内存通信拷贝数据次数少,速度快

    • 共享内存是创建好后就可以直接使用的,就像malloc 开辟的空间一样,不需要再另外调用函数来访问这块内存

    • 而管道通信的时候创建好管道后需要调用系统接口read()和write()来访问管道文件的,这样就有数据在缓冲区之间的拷贝操作,进而使得效率变低

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bdMBg9KY-1656584415675)(1.1共享内存的介绍/image-20220630181015039.png)]

    2.管道自带同步与互斥机制,而共享内存没有

    学习过管道我们知道管道是自带同步与互斥机制的,读写是不可以同时进行的。但是共享内存就不带这种机制,这就使得其数据的读写可以是同时进行的,要想实现类似同步与互斥的机制就需要用到锁或则信号量来解决

  • 相关阅读:
    图解 SQL 执行顺序,通俗易懂!
    机器学习笔记之隐马尔可夫模型(二)背景介绍
    告别BeanUtils,Mapstruct从入门到精通
    Redis的内存淘汰策略(八)
    WIFI6 TWT机制介绍
    LeetCode算法题解(回溯)|LeetCode93. 复原 IP 地址、LeetCode78. 子集、LeetCode90. 子集 II
    ES6--》一文搞懂JS中的Promise
    LSM Tree
    性能测试-基础理论知识
    ELK日志分析平台(二)----logstash数据采集
  • 原文地址:https://blog.csdn.net/xbhinsterest11/article/details/125546023