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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录


一、共享内存示意图

上一篇文章中讲述的是管道的通信方式而这里要讲的是操作系统层面专门为进程间通信设计的一个方案而在同一主机内进行的进程间通信就是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开辟空间一样所以不需要系统接口。

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


本篇完来日方长继续努力

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“【关于Linux中----进程间通信方式之system V共享内存】” 的相关文章