【关于Linux中----进程间通信方式之system V共享内存】

news2024/11/28 12:43:17

文章目录

  • 一、共享内存示意图
  • 二、学习共享内存前的准备工作
  • 三、共享内存函数
    • 3.1创建共享内存:
    • 3.2控制共享内存:
    • 3.3挂接和去挂接:


一、共享内存示意图

上一篇文章中讲述的是管道的通信方式,而这里要讲的是操作系统层面专门为进程间通信设计的一个方案,而在同一主机内进行的进程间通信就是system V方案。

那么这种通信方式是谁设计的?该以怎么样的形式给用户使用?

答:这是很久以前的计算机科学家和程序员设计的。
由于操作系统不相信任何人,所以使用该通信方式时,必须通过系统提供的一系列函数接口调用。

在管道的讲解中,已经说明了一个概念:要想实现进程间通信,就必须要让不同的进程看到同一资源,而system V用来实现这个目的的方式有三种----共享内存、消息队列和信号量,本篇博客就对第一种方式进行讲解。

共享内存原理:

在这里插入图片描述
如图所示为两个进程在内存中的管理,具体的详细介绍在我之前的文章【关于Linux中----进程优先级、环境变量和进程地址空间】中已经解释过了,不太了解的读者可以先跳转过去看一下。

而共享内存就是先在物理内存中开辟出一段空间,然后通过某种调用将多个进程“挂接”到同一块物理内存上(经过页表映射实现),这样也就使得不同的进程看到了同一份资源。
而当进程不再使用这份资源时,就会进行去挂接,然后再释放这块内存。


二、学习共享内存前的准备工作

来理清几个问题是学习共享内存前的重中之重!

在操作系统中可能同时存在多个进程使用不同的共享内存来进行进程间通信,也就意味着物理内存中可能存在多个共享内存,那么操作系统如何管理它们呢?

答:先描述,再组织----将每一个共享内存的属性都存入一个描述共享内存的结构体中,再将所有的这些结构体以某一种数据结构(可能是顺序表、链表等等)的方式串联起来。这时,对共享内存的管理也就变成了对某一种数据结构进行增删查改等的操作了。

如何保证多个需要进行通信的进程看到的是同一份资源呢?

答:每一个共享内存一定都有一个标识自己唯一性的ID,进程通过每一个共享内存的ID就可以确定这到底是不是 要使用的资源。而这个ID就存在于上面问题中所说的结构体中,因为它是共享内存的属性之一。


三、共享内存函数

前面说过的使用共享内存的四个过程对应四个接口

3.1创建共享内存:

在这里插入图片描述

下面对该接口的三个参数进行解释:
key_t key表示的就是上文中说过的用来确定内存空间唯一性的ID,它需要用户自己设置。
而这个值的设置需要使用下面这个函数:
在这里插入图片描述
该函数的两个参数都需要用户自己根据需要设置,分别是自定义路径名和自定义项目标识符,而返回值就是内存空间的ID(创建失败就返回-1)。而这个返回值也就是key。

size_t size表示的是创建的共享内存的大小(一般建议是4KB的整数倍)。

shmflg的表示如下:
在这里插入图片描述

它有两部分组成。

如果单独使用IPC_CREAT或shmflg设置为0,则表示如果该共享内存已经存在就返回这个共享内存的ID,而如果不存在则创建一个共享内存。
而IPC_EXCL不单独使用,单独使用没有意义,它必须和IPC_CREAT配合使用,以IPC_EXCL|IPC_CREAT的方式出现。表示如果不存在共享内存则进行创建,而如果已经存在就返回出错。这样做的目的是使每次返回的共享内存都是新的、未使用过的

关于上面IPC_CREAT|IPC_EXCL的使用方式在我之前的文章【关于Linux中----文件接口、描述符、重定向、系统调用和缓冲区】中已经介绍过,不太了解的读者可以先跳转过去看一下。

下面通过代码样例体会一下:
创建一个.h文件和两个.c文件。.h文件内容如下:
在这里插入图片描述
(这里的第二个参数的宏是随便写的,也可以是其他值)

sever.c内容如下:
在这里插入图片描述

Makefile内容如下:
在这里插入图片描述
执行结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f sever slient fifo
gcc -o sever sever.c
[sny@VM-8-12-centos practice]$ ./sever
1711344665
[sny@VM-8-12-centos practice]$ ./sever
1711344665

而要想让另一个进程也看到同一个共享内存,就必须设置相同的key值,这样才能进行通信,如下:

[sny@VM-8-12-centos practice]$ cat sever.c > client.c
[sny@VM-8-12-centos practice]$ make clean;make
rm -f sever slient fifo
gcc -o sever sever.c
gcc -o client client.c
[sny@VM-8-12-centos practice]$ ./sever
1711344665
[sny@VM-8-12-centos practice]$ ./client
1711344665

接下来进行创建共享内存,对sever.c稍作改动:
在这里插入图片描述
执行结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f sever slient fifo
gcc -o sever sever.c
[sny@VM-8-12-centos practice]$ ./sever
key:1711344665 shmid:0
[sny@VM-8-12-centos practice]$ ./sever
shmget: File exists

可以看到,第一次执行创建共享内存成功,第二次创建失败,因为共享内存已经存在,返回错误。

那么该进程结束之后,创建的共享内存释放了吗?

答案是没有,可以用ipcs -m指令查看共享内存,如下:

[sny@VM-8-12-centos practice]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x66010c19 0          sny        0          4666       0                       

所以也就得出了一个很重要的结论:共享内存是由内核控制的,不随某一个进程的结束而释放。必须有程序员显示地调用命令或接口以及操作系统重启才能释放。

如下:

[sny@VM-8-12-centos practice]$ ipcrm -m 0
[sny@VM-8-12-centos practice]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status     

可以看到上面的释放共享内存是通过指令搭配shmid实现的。
那么key和shmid有什么区别?

答:key只是一个用来标识共享内存唯一性的东西,并不能控制shm;而shmid是操作系统给用户返回的ID,用来进行用户层面上对共享内存的管理。


3.2控制共享内存:

在这里插入图片描述
这里的第一个参数就是上一个接口的返回值,第二个参数是具体的选项(选择以哪种方式管理共享内存),这里着重讲解其中一种方式:
在这里插入图片描述
顾名思义就知道这个选项是释放共享内存的,而一旦选择这个选项,就可以直接将第三个参数设置为空。

第三个参数很明显就是一个结构体指针,这个结构体就是上文中所说的管理共享内存相关属性的结构体,其为空时表示该共享内存已被释放。

举个例子,在sever.c后加上这几行代码:
在这里插入图片描述
接下来用这样一个指令检测系统中的共享内存:

[sny@VM-8-12-centos practice]$ while :; do ipcs -m; sleep 1; echo"#########################################"; done

结果如下:
在这里插入图片描述
可以看到,共享内存成功地进行了创建和释放。


3.3挂接和去挂接:

在这里插入图片描述
挂接:

同样的第一个参数还是创建共享内存的返回值,第二个参数是一个指针,指针指向所开辟的共享内存的起始地址(虚拟地址),第三个参数和上一个接口中的一样也是选项。
至于去挂接就很简单了,参数只有一个(注意去挂接并不是释放内存!)

直接上代码看一下:

#include "comm.h"                                                                        
  2 #include <unistd.h>                                                                      
  3 int main()                                                                               
  4 {                                                                                        
  5   key_t key=ftok(PATH_NAME,PROJ_ID);                                                     
  6   if(key<0)                                                                              
  7   {//创建key值失败                                                                       
  8     perror("ftok");                                                                      
  9     return 1;                                                                            
 10   }                                                                                      
 11   int shm_id=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);//创建一个全新的内存                    
 12   if(shm_id<0)                                                                           
 13   {                                                                                      
 14     perror("shmget");                                                                    
 15     return 2;                                                                            
 16   }                                                                                      
 17   printf("key:%u shmid:%d\n",key,shm_id);                                                
 18   sleep(10);                                                                             
 19   char* mem=(char*)shmat(shm_id,NULL,0);                                                 
 20   printf("attach successfully!\n");                                                      
 21   sleep(5);
 22   //在这里完成通信逻辑                                                                                                                               
 23   shmdt(mem);                        
 24   printf("deattach successfully!\n");
 25   sleep(5);                                                                                                                            
 26   shmctl(shm_id,IPC_RMID,NULL);                                                                                                        
 27   printf("key:0x%x shmin:%d -> delete successfully!\n",key,shm_id);                                                                    
 28   sleep(10);                                                                                                                           
 29   return 0;             
 30 }      

同样的,也可以用上一个接口中的检测共享内存的方式验证一下整个过程,由于内容类似,这里就不进行粘贴了。

下面编写一下client.c:

#include "comm.h"
  2 #include <unistd.h>   
  3 int main()            
  4 {                     
  5   key_t key=ftok(PATH_NAME,PROJ_ID);
  6   if(key<0)           
  7   {//创建key值失败    
  8     perror("ftok");   
  9     return 1;         
 10   }                   
 11   printf("%u\n",key); 
 12   //client只需要直接获取sever中的共享内存即可
 13   int shm_id=shmget(key,SIZE,IPC_CREAT);
 14   if(shm_id<0)        
 15   {                   
 16     perror("shmget"); 
 17     return 2;         
 18   }                   
 19   char* mem=(char*)shmat(shm_id,NULL,0);
 20   sleep(5);           
 21   printf("client attach successfully!\n");
 22   shmdt(mem);         
 23   sleep(5);                                                                                                                                          
 24   return 0;
 25 }              

读者同样可以自己测试一下整个过程。

下面编写通信逻辑:
完整的sever.c如下:

#include "comm.h"
  2 #include <unistd.h>
  3 int main()
  4 {
  5   key_t key=ftok(PATH_NAME,PROJ_ID);
  6   if(key<0)
  7   {//创建key值失败
  8     perror("ftok");
  9     return 1;    
 10   }                
 11   int shm_id=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);//创建一个全新的内存
 12   if(shm_id<0)
 13   {                                 
 14     perror("shmget");
 15     return 2;     
 16   }                
 17   printf("key:%u shmid:%d\n",key,shm_id);                                                                                                            
 18   char* mem=(char*)shmat(shm_id,NULL,0);
 19   printf("attach successfully!\n");                
 20   //在这里完成通信逻辑                             
 21   while(1)            
 22   {                                                
 23     sleep(1);                                      
 24     printf("%s\n",mem);
 25   }                    
 26   shmdt(mem);          
 27   printf("deattach successfully!\n");
 28   shmctl(shm_id,IPC_RMID,NULL);      
 29   printf("key:0x%x shmin:%d -> delete successfully!\n",key,shm_id);
 30   return 0;                                                        
 31 }          

完整的client.c如下:

#include "comm.h"
  2 #include <unistd.h>
  3 int main()
  4 {      
  5   key_t key=ftok(PATH_NAME,PROJ_ID);
  6   if(key<0)
  7   {//创建key值失败
  8     perror("ftok");
  9     return 1;
 10   }    
 11   printf("%u\n",key);
 12   //client只需要直接获取sever中的共享内存即可
 13   int shm_id=shmget(key,SIZE,IPC_CREAT);
 14   if(shm_id<0)
 15   {    
 16     perror("shmget");
 17     return 2;
 18   }    
 19   char* mem=(char*)shmat(shm_id,NULL,0);
 20   printf("client attach successfully!\n");
 21   char c='A';
 22   while(c<='Z')
 23   {    
 24     mem[c-'A']=c;
 25     c++;
 26     mem[c-'A']=0;
 27     sleep(2);
 28   }    
 29   shmdt(mem);
 30   printf("client dettach successfully!\n");
 31   return 0;
 32 }             

在上面的通信代码中,并没有使用系统接口,为什么?

共享内存一旦建立好映射金自己进程的地址空间,该进程就可以直接看到该共享内存,就像malloc开辟空间一样,所以不需要系统接口。

由于结果是一个动态的过程,这里就不粘贴了,读者可以自己运行一下试试。


本篇完!来日方长,继续努力!

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

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

相关文章

编译原理-链接实例分析

gcc-arm-none-eabi 工具链功能1.arm-none-eabi-gcc &#xff1a;c语言编译器&#xff0c;可以将.c文件编译为.o的执行文件2.arm-none-eabi-g &#xff1a;c编译器&#xff0c;可以将.cpp文件编译成.o的执行文件3.arm-none-eabi-ld : 链接器&#xff0c;链接所有的.o文件生成可执…

CDH6.3生产环境中禁用Kerberos

在集群启用Kerberos后&#xff0c;会对现有环境的部分代码做改造&#xff0c;有些人觉得使用起来不方便&#xff0c;想取消Kerberos。本篇文章主要介绍如何禁用CDH集群的Kerberos及禁用后对各组件服务的测试。修改了网上相关文档的一些缺陷&#xff0c;在生产环境中实际使用过通…

GIT ---- GitHub配置SSH Key的完整步骤

1. 配置 SSH Key 由于提交代码每次输入用户名和密码&#xff0c;很繁琐&#xff0c;所以直接配置 SSH Key&#xff0c;直接自动验证&#xff0c;减少提交代码的操作步骤。 2. 查看配置命令 git config --list 查看当前Git环境所有配置&#xff0c;还可以配置一些命令别名之类…

这一年,熬过许多夜,也有些许收获 | 2022年度总结

弹指一挥间&#xff0c;时间如白驹过隙。光阴似箭&#xff0c;日月如梭&#xff0c;时间如闪电&#xff0c;转瞬即逝。回望来时路&#xff0c;不觉潸然泪下… 一说到年终总结&#xff0c;好像都离不开这样煽情的开场白。但不可否认的是&#xff0c;时间确实过得很快&#xff0…

学习记录661@项目管理之项目立项管理

什么是项目立项管理 项目立项管理关注的重点在于是否要启动一个项目&#xff0c;并为其提供相应的预算支持具体来说&#xff0c;项目立项管理包括以下 5 个典型环节&#xff0c;分别是 项目建议项目可行性分析项目审批项目招投标项目合同谈判与签订 需要说明的是&#xff0c…

两大技巧教你轻松应对IB数学

同学想要在IB数学科取得好成绩&#xff0c;可以从两个方面来着手。 1.复习技巧第一个是复习技巧。这方面&#xff0c;同学要清楚知道自己读的课程&#xff0c;它的教学大纲&#xff08;Syllabus&#xff09;要求是什么&#xff0c;还有它背后想要同学达到什么样的目标。 IB数学…

浅谈DNS解析

DNS介绍IP是计算机里的地址簿&#xff0c;但是IP是由一串数字组成&#xff0c;我们的大脑很难记住&#xff0c;所以就需要定义一个符合人类记忆规则的地址&#xff0c;而这就是我们现在常用的网站域名&#xff0c;域名就是我们和计算机作为地址沟通的桥梁&#xff0c; 虽然我们…

物流企业如何确保网络安全?

随着网上购物的发展&#xff0c;人们的日常生活越来越离不开物流企业的服务了。而且在一些企业的供应链中&#xff0c;物流运输也是非常重要的一环节。与此同时&#xff0c;伴随着供应链数字化&#xff0c;透明度、速度和成本优势增加了公司对技术的兴趣。物流企业也更喜欢使用…

开放式基金净值实时数据 API 数据接口

开放式基金净值实时数据 API 数据接口 实时净值&#xff0c;全量实时数据&#xff0c;包含净值与增长率。 1. 产品功能 支持所有开放式基金净值数据查询&#xff1b;实时数据&#xff0c;包含实时净值与增长率信息&#xff0c;16:00 ~ 23:00 更新当日数据&#xff1b;包含前一…

C语言模拟实现库函数strstr

传入两个地址&#xff0c;第一个是母串首地址&#xff0c;第二个是子串首地址&#xff0c;判断是否是子串&#xff0c;如果不是&#xff0c;返回NULL&#xff0c;如果是&#xff0c;返回母串中第一次出现子串的首地址。 代码如下&#xff1a; #define _CRT_SECURE_NO_WARNING…

Android | Activity

Android Activity Activity 概念 Activity 是一种包含用户界面、主要用于与用户进行交互的Android应用组件。一个应用程序可以包含零个或多个 Activity 。 Activity 生命周期 Activity 类中定义了7个回调方法&#xff0c;覆盖了 Activity 生命周期的每一个环节。 onCreate(…

力扣sql基础篇(九)

力扣sql基础篇(九) 1 每位经理的下属员工数量 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 如果是得出来每组都是一个值,就可以在SELECT子句中写非分组字段 # e1.reports_to IS NOT NULL是为了确保是员工,通过员工去找经理 SELECT e2.employee_…

第二章.线性回归以及非线性回归—标准方程法

第二章.线性回归以及非线性回归 2.8 标准方程法 1.公式 1).代价函数&#xff1a; 2).累加平方和用矩阵表示&#xff1a; 2.对(&#x1d466; − &#x1d44b;&#x1d464;)&#x1d447;(&#x1d466; − &#x1d44b;&#x1d464;)求导的两种布局方式&#xff1a; 1).分子…

【初阶数据结构】——写了将近 5 万字,终于把 二叉树 初阶的内容讲清楚了

文章目录前言1. 树的概念及结构1.1 树的概念1.2 树与非树1.3 树的相关概念1.4 树的表示1.5 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09;2. 二叉树概念及结构2.1 概念2.2 现实中的二叉树2.3 特殊的二叉树2.3.1 满二叉树2.3.1 完全二叉树2.4 二叉树的性质…

2022年Tesla技术分享

Autopilot&#xff1a;允许车辆保持车道&#xff0c;跟随前车&#xff0c;弯道减速&#xff0c;等等&#xff0c;处理从停车场到城市街道&#xff0c;再到高速公路的所有驾驶过程。 一、硬件&#xff1a; 8个120W像素的摄像头&#xff0c;每秒36帧&#xff0c;360度空间&#…

牛客竞赛每日俩题 - 动态规划4

目录 经典dp1&#xff08;最长公共序列&#xff09; 经典dp2&#xff08;最长上升子序列 &#xff09; 经典dp&#xff08;最长公共序列&#xff09; 最长公共子序列__牛客网 解析&#xff1a; 有两个字符串T和S&#xff0c;S的长度为n T的长度为m 状态&#xff1a;f[i][j…

C# LINQ查询

一、什么是LINQ LINQ是Language-Integrated Query的缩写&#xff0c;它可以视为一组语言和框架特性的集合。LINQ可以对本地对象集合或远程数据源进行结构化的类型安全的查询操作。LINQ支持查询任何实现了IEnumerable<T>接口的集合类型&#xff0c;无论是数组、列表还是X…

oracle12c数据库安装(静默安装

写在前面 本教程是在Linux下安装oracle12c数据库&#xff0c;由于在有些情况下并没有图形化安装界面&#xff0c;所以这里介绍在linux下通用的安装方式&#xff1a;静默安装&#xff0c;通俗的说就是在linux的命令行窗口安装。 关闭防火墙 systemctl disable firewalld sy…

LeetCode 300. 最长递增子序列

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 300. 最长递增子序列&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCo…

Spring之AOP简单讲解

目录 一&#xff1a;基本概念 二&#xff1a;案例&#xff1a;模拟AOP的基础代码 三&#xff1a;AOP相关概念 四&#xff1a;AOP入门案例思路分析 五&#xff1a;AOP工作流程 六&#xff1a;AOP核心概念 七&#xff1a;AOP切入点表达式 八&#xff1a;xml方式AOP快速入门…