文章目录

  • systemV 标准
  • 共享内存通信原理
  • 共享内存的理解
  • 共享内存的创建--shmget函数
  • 查看共享内存和释放共享内存方式
  • 挂在共享内存--shmat函数
  • 去挂在共享内存--shmdt函数
  • 共享内存使用的基本框架逻辑代码
  • 共享内存完成进程之间通信
  • 共享内存通信的特点
  • 浅谈systemV标准的IPC数据结构

systemV 标准

systemV标准是大佬定制的一套标准,该标准可以在操作系统层面上,完成进程之间的通信;
systemV标准给用户提供了系统调用接口,只要我们使用它所提供的系统调用就可以完成进程间的通信;
对于systemV标准提供了三种方式:

  1. 消息队列
  2. 共享内存
  3. 信号量;
    其中消息队列和共享内存都是来完成进程通信的,而信号量主要是保证同步和互斥的;

共享内存通信原理

共享内存的通信原理:
在物理内存开辟一份共享内存的空间,然后把这份空间映射到需要进行相互通信的虚拟地址空间中;
这样使得不同的进程看到了同一份资源,这样就可以通过共享内存进行通信了;

至于是如何在内存开辟一共享内存空间?并且如何建立共享内存空间映射到进程的虚拟地址空间中的?
这都是systemV标准提供给我们共享内存操作IPC完成的;


共享内存的理解

  1. 请问:操作系统会不会同时存在多个进程,使用不同的共享内存进行通信呢?

那肯定会的,共享内存在操作系统中是存在多份的,只要你有办法创建出共享内存就可以(当然,我们可以通过操作系统提供的系统调用创建,这个是肯定搞得);

那既然在操作系统可以存在多份共享内存,操作系统毫无疑问需要进行对共享内存的管理;那对于操作系统来说,毫无疑问管理的方式只有一个逻辑,先描述,再组织;先用一个结构体描述这个共享内存的信息,再用一些数据结构,如链表进行对该共享内存结构体进行管理;

  1. 既然共享内存那么多,那你怎么保证两个或多个进程之间通信,能够看到同一份共享内存呢?

毫无疑问,在我们匿名管道中,为了让通信之间的进程看到同一份管道资源,使用策略是子进程继承父进程的信息;在我们的命名管道里,为了让通信之间的进程看到同一份命名管道资源,使用的策略是路径+文件名;
那么在我们共享内存中,巍峨让通信之间的进程看到同一份共享内存资源,使用的策略是该共享内存的唯一id号即可;
我们发现,无论那种通信方式,都是为了让通信之间的进程,看到同一份资源,并且这份资源是唯一的;

那么共享内存的id号,到底在哪儿呢?毫无疑问肯定在描述共享内存的结构体中,这就和标识一个进程id一样,该进程的id也是在描述进程的结构体中;


共享内存的创建–shmget函数

  1. 创建共享内存函数 shmget

第二个参数为什么是最好是4KB的整数倍?
原因就是共享内存申请内存的基本单位是4KB,也就是一个页;

这个函数的返回值是:
创建成功,OS返回给用户一个id值,用户通过该id来管理共享内存(去关联,关联,删除等),出错久返回-1;

这个函数的第一个参数 key:设置key为共享内存的唯一ID号
为了达到不同之间的进程通信,就需要不同进程看到同一份共享内存,看到同一份共享内存的前提是:需要知道该共享内存的唯一ID,这个key值,理论上是可以自己随意设置的,只要保证该id号是唯一的;
但是自己设置,总有些不好,所以操作系统给我们提供了一个系统调用ftok,来帮我们设置共享内存key值;
这个key值是什么不重要,它只要宝子该共享内存是唯一的,和其他共享内存id号不一样就可以;



所以在我们调用shmget函数之前,必须调用ftok来帮我们生成一个key,该key就是给shmget函数第一个参数使用了,表示表示该共享内存的唯一标识,这个key在内核中,会在共享内存的结构体,设置给共享内存的ID;


那我们如何保证不同进程看到同一份共享内存呢?我们就只需要保证不同进程看到的是同一个key 值的共享内存即可;那么又如何保证不同进程是看到同一个key值的共享内存呢?只需要在不同的进程都是用ftok函数和相同的路径名,和项目ID,就可以生成相同的key,这样就可以保证不同进程看到是同一个key值的共享内存了;


我们火速来创建一个共享内存来看看:

来挖掘共享内存得特性:

#include <stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/shm.h>
int main(){key_t key = ftok("./",0x1002); //这里两个参数都是随意给的//创建共享内存int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL); if(shmid < 0){perror("shmget:");return 2;}//创建成功,我们打印看看key和shmid值printf("key = %u,shmid = %d",key,shmid);return 0;}

当我第一次运行 注意:第一次运行我在强调这个词的时候,我们发现:key 是一个很大的数,但是这个数是什么不重要,它就是表示共享内存的ID而已;
我们看到 shmid = 0; 这个shmid就是shmget函数调用成功返回的值;


当我们第二次,第三次之后再次执行该进程:我们发现一个现象啊;就是创建共享内存失败了,原因就是我们的
shmget的第三个参数是IPC_CREAT | IPC_EXCL,它表示内存存在共享内存就会创建失败,不存在就会创建一个;

所以这里失败了,就反向推出共享内存还是存在内存中的;

但是我们思考了一个问题:明明我执行完了该进程,为什么该共享内存还在呢?这里我们就知道,共享内存的声明周期肯定不是跟随进程的


查看共享内存和释放共享内存方式

那既然共享内存还是纯在的话?我们可以通过一个命令查看共享内存ipcs -m 它表示可以查看内存中存在的IPC资源, -m选项表示查看共享内存的资源;



我们发现即使进程退出,共享内存还是存在;那共享内存的声明周期是随谁的呢?

systemV的IPC资源都是随内核的。并不是随进程的。那么共享内存也就是随内核的。

那么我们如何释放该共享内存呢?

  1. 程序员手动释放,通过系统调用接口shmctl
  2. 命令方式释放 ipcrm -m shmid ,该shmid是共享内存的shmget返回的id号;
  3. 重启操作系统

比如我们通过命令释放共享内存:


我们还可以使用通过一个系统调用接口:shmctl;
这个是一个共享内存的控制函数,但是我们不用来控制共享内存其他属性,我们只用来释放共享内存


具体操作,如何释放共享内存呢?
起始只要在程序中:调用shmctl(shmid,IPC_RMID,NULL);

#include <stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/shm.h>
int main(){key_t key = ftok("./",0x1002); //这里两个参数都是随意给得//创建共享内存int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL); if(shmid < 0){perror("shmget:");return 2;}printf("key = %u\n,shmid = %d\n",key,shmid);sleep(2); //没释放之前shmctl(shmid,IPC_RMID,NULL); //释放共享内存sleep(2);//释放之后return 0;}

我们再开多一个终端,监控上面代码的可执行程序:观察共享内存的变化:
监控脚本命令:while:;do ipcs -m;sleep 1;echo"##########";done;
我们可以看到一个变化:共享内存从有变无的过程,表示该系统调用确实释放了共享内存;


挂在共享内存–shmat函数

当我们通过shmget函数创建在物理空间创建好了共享内存时候,接下来我们就需要把该共享内存挂在到需要进行通信的进程虚拟地址中了;
在OS中给我们提供了一个系统调用接口shmat 函数:


该函数:
第一个参数就是shmget函数返回给用户层的id号;

第二个参数就是要把该共享内存挂在到虚拟地址哪个地方,通常我们都设置为NULL,不需要手动设置为具体数值地址,因为我们也不知道该虚拟地址的共享内存地址在哪个地方呀;

第三个参数我们设置为0就可以;


这个函数我们关注的是返回值:


成功返回共享内存挂在到进程的虚拟地址的起始位置;
失败返回(void*)-1

该函数的返回值类似malloc的返回值一样,只不过malloc返回的地址是在进程虚拟地址的堆空间段,而shmat返回的地址在进程虚拟地址的共享内存段;


去挂在共享内存–shmdt函数

当我们不想再让共享内存和进程进行关联时候,我们就可以去掉共享内存和进程的关联;
在OS中提供一个系统调用接口:shmdt;
该函数的一个参数是 shmat函数返回共享内存关联进程的虚拟地址;
返回值,成功就返回0,失败就-1;


而且我们必须理解:这个shmdt是去掉共享内存和进程虚拟地址的关联,并不是释放共享内存;


共享内存使用的基本框架逻辑代码

#include <stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/shm.h>
#include<unistd.h>
int main(){key_t key = ftok("./",0x1002); //这里两个参数都是随意给得//创建共享内存int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL); if(shmid < 0){perror("shmget:");return 2;}//创建完共享内存后,我们就需要挂在共享内存char* mem = (char*) shmat(shmid,NULL,0);printf("attaches shared_memory success!\n");//进行通信的逻辑代码区域//.....//.....//当我们该进程不使用该共享内存时候,我们需要去掉该共享内存的关联shmdt(mem);printf("deatach shared_memory success!\n");shmctl(shmid,IPC_RMID,NULL); //释放共享内存return 0;}

上面的代码就是共享内存使用的框架逻辑,我们完成了五个步骤:
1. 创建共享内存;
2. 挂接共享内存与进程之间的关联;
3. 完成通信逻辑代码;
4. 去挂接共享内存与进程之间的关联;
5. 释放共享内存;


共享内存完成进程之间通信

有了上面的基本逻辑框架,我们来通过共享内存完成一份代码:
一个进程server读取共享内存的数据;
一个进程client 向共享内存写入数据;


对于server.c的代码:

#include <stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/shm.h>
#include<unistd.h>
int main(){key_t key = ftok("./",0x1002); //这里两个参数都是随意给得if(key <0){perror("ftok:");return 1;}//创建共享内存int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL|0666); if(shmid < 0){perror("shmget:");return 2;}//创建完共享内存后,我们就需要挂在共享内存char* mem = (char*) shmat(shmid,NULL,0);printf("attaches shared_memory success!\n");//进行通信的逻辑代码区域//.....//.....while(1){sleep(1);printf("client sent to server:%s\n",mem);} //当我们该进程不使用该共享内存时候,我们需要去掉该共享内存的关联shmdt(mem);printf("deatach shared_memory success!\n");shmctl(shmid,IPC_RMID,NULL); //释放共享内存return 0;}

对于client.c代码:

#include <stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<unistd.h>
#include<sys/shm.h>
int main(){//client生成的共享内存key值,是和server生成的key是一样的//因为我们使用 ftok传入的参数也是一样的key_t key = ftok("./",0x1002); if(key <0){perror("ftok");return 1;} //这里的共享内存不需要在创建了,只需要使用server端创建的共享内存即可//我们的key是和server相同的,所以就可以保证client也是看到server的共享内存//由于不需要client创建共享内存,所以我们第三个参数传入的是IPC_CREAT//表示,有和key相关的共享内存,那么就会返回该共享内存给用户,不会再创建int shmid = shmget(key,4096,IPC_CREAT);if(shmid <0){perror("shmget:");return 1;}//有了共享内存我们就需要挂在该共享内存到client进程的虚拟地址char* mem = (char*)shmat(shmid,NULL,0);//挂在共享内存到client进程成功后,这就是client的业务逻辑代码区域//我们给共享内存每隔一秒发送字母char c = 'A';while(c <='Z'){mem[c-'A'] = c; //往共享内存中写入字母c++;mem[c-'A']='\0';sleep(2);//睡两秒是为了观察server端口是否接收到client发送的数据}//当业务逻辑处理完,不再使用共享内存,我们可以去掉该共享内存的关联shmdt(mem);return 0;
}

当我们启动了服务端server进程,再启动client进程;此时就可以通信了;


共享内存通信的特点

  1. 共享内存通信是所有进程间通信最快的方式;原因就是:一旦共享内存建立和进程地址的关联后,该进程就直接可以使用该共享内存了;并不像管道通信那样,还需要调用read wirte函数;
  2. 我们的共享内存是没有提同步互斥机制的,这个需要我们程序员自己来管理这部分的资源;再我们上面的测试代码进程通信也可以看出,当server进程先运行后,并不会等待client发数据过来再读取内容,server一旦启动就会一直读取共享内存;
  3. 共享内存的生命周期随内核;

浅谈systemV标准的IPC数据结构

我们不是说操作系统为了管理共享内存,一定会管理共享内存的数据结构吗?那肯定是的呀;
于此同时操作系统为了管理消息队列,信号量,也会管理他们的数据结构!


我们可以通过
man shmctl
man msctl
man semclt
查看共享内存,消息队列,信号量的用户层数据结构(内核层的数据结构和他类似);


共享内存的用户层的结构体


消息队列的结构体:


信号量的结构体


我们惊奇的发现:systemV 标准的数据结构尽管他们的数据结构不一样,但是很类似;

并且第一个成员的数据类型都是struct _ipc_perm,该类型的第一个成员就是_key,也就是用来标识该信号量。共享内存,消息队列的唯一性的一个关键字,所以说,我们上面的共享内存哪个ftok生成的key值就是设置到给共享内存的数据结构 struct shmid_ds的第一个成员 struct ipc_perm shm_perm 变量的第一个成员 key_t __key 这里的;


最关键的是:明明三个IPC数据结构都是不一样的,第一个成员都是一样的?操作系统这么设计的目的是什么?
就是为了管理systemV IPC的资源?如何管理呢?


你可以理解,在OS中,有一个数组,struct ipc_perm* arrary[64],它存放的就是各个systemV标准的IPC数据结构;
有同学说,明明systemV标准的IPC数据结构类型为 struct semid_ds`` struct shmid_ds ``struct msqid_ds,都是不一样的,为什么能够存储在 struct ipc_perm的数组中?
你们别忘记了struct semid_ds struct shmid_ds struct msqid_ds的第一个成员都是 struct ipc_perm类型啊;
只要我们systemV标准的地址强制类型转换为struct ipc_perm* 就可以存入它数组了

那假如我要访问systemV标准的IPCstruct semid_ds struct shmid_ds struct msqid_ds其他数据成员呢?那不就是不行了?
错了错了,肯定可以的,我们只要取出struct ipc_perm* arrary[64]的元素,它的元素就是systemV标准的IPC的数据结构地址,我们强制类型转化,就可以访问到各自的数据结构了;


这就是C语言的切片特性啊,通过一个公共的数据,存放不一样的数据结构;
这个和C++中父类的指针可以指向子类的对象是否很相似,这也是C++中的切片行为;


不知道同学们记不记得,当我们在申请共享内存时候,为什么共享内存shmid,是从零一直往上增长的?
原因就是struct ipc_perm* arrary[64]的数组下标咯,就那么简单;


【Linux】进程间通信--systemV标准--共享内存相关推荐

  1. Linux进程间通信六 Posix 共享内存简介与示例

    1. 共享内存简介 共享内存主要用于不同进程之间相互通信,因为操作的是同一块地址,不需要内核和用户层之间数据拷贝,属于最快的进程间通信方式,不过,为了防止读写冲突,一般需要额外的同步手段.之前介绍了S ...

  2. linux进程间通信:POSIX 共享内存

    文章目录 思维导图 通信原理 优势 POSIX 共享内存 编程接口 编程案例 思维导图 之前学习过sysemV 的共享内存的实现及使用原理,参考linux进程间通信:system V 共享内存 POS ...

  3. linux进程间通信之Posix共享内存用法详解及代码举例

    Posix共享内存有两种非亲缘进程间的共享内存方法: 1).  使用内存映射文件,由open函数打开,再由mmap函数把返回的文件描述符映射到当前进程空间中的一个文件. 2). 使用共享内存区对象,由 ...

  4. 【Linux 应用编程】进程管理 - 进程间通信IPC之共享内存 mmap

    IPC(InterProcess Communication,进程间通信)是进程中的重要概念.Linux 进程之间常用的通信方式有: 文件:简单,低效,需要代码控制同步 管道:使用简单,默认阻塞 匿名 ...

  5. linux下的进程间通信-管道及共享内存

    进程间通信(IPC):操作系统为用户提供的集中进程间通信方式: 为什么要进程间通信? 进程之间具有独立性(每个进程有自己的虚拟地址空间),访问自己的虚拟地址,无法访问同一块区域,因此无法实现数据通信. ...

  6. linux c之使用共享内存实现进程间通信

    这篇博客有别人的也有自己改的,作为读书笔记,勿喷. 1.共享内存的介绍 共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进 ...

  7. Linux IPC实践(8) --共享内存/内存映射

    概述 共享内存区是最快的IPC形式.一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据(如图). 共享内存 VS ...

  8. 阅读 Linux 内核源码——共享内存

    介绍 我看的是linux-4.2.3的源码.参考了<边干边学--Linux内核指导>(鬼畜的书名)第16章内容,他们用的是2.6.15的内核源码. 现在linux中可以使用共享内存的方式有 ...

  9. 进程间通信IPC之--共享内存

    每个进程各自有不同的用户地址空间,任何一个进 程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲 区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲 ...

最新文章

  1. HG522-C 刷Openwrt记录
  2. PMP-【第14章 五大过程组的工作要点】2021-2-17(292页-303页)
  3. C#通过Redis实现分布式锁
  4. php mysql ppt,7PHP访问数据库分析.ppt
  5. hdu 5783——Divide the Sequence
  6. Docker启动MySql后连接报1251处理方法
  7. android手机 不显示本地视频,各位大神们 android怎么获取手机本地视频啊?
  8. 飞机上终于能开着手机连 Wi-Fi 了,它背后的技术原理是什么?
  9. .net mvc html5,带有.NET MVC 3 Razor Editor的HTML5占位符
  10. HTTP/2特性及其在实际应用中的表现
  11. Servlet技术详解
  12. 一种去水印的营业执照识别方法
  13. python 携程订单接口_携程api开发
  14. android 自动发短信的代码,Android点击按钮时自动发送短信
  15. Typora+PicGo+阿里云OSS搭建博客图床(超详细)
  16. 读【选修计算机专业的伤与痛】
  17. COOKIE与SESSION比较
  18. Java接口练习(组装电脑)
  19. Java 多文件夹合并
  20. LInux上返回到切换目录前的目录

热门文章

  1. 搞计算机的离开高校的几个理由
  2. Vmware设置静态ip连网 ( 使用自定义Vmnet8 net )
  3. 分析Androidqq协议之收到qq群消息
  4. 墨天轮【2022年新春发布会暨国产数据库年度颁奖典礼】圆满收官
  5. Flume快速入门(五):File Channel之重播(replay)
  6. 是微服务架构不香还是云不香?(转载自陈皓前辈的博客:酷壳coolshell)
  7. 非常贴心的Anaconda3 + Pycharm2020+ OpenCV 下载安装及环境搭建教程
  8. 从软盘硬盘驱动器中提取扇区_硬盘驱动器诊断工具如何知道某个扇区是否损坏?...
  9. Robot Framework For循环详解
  10. linux切换用户执行脚本