进程间的通信 -- 共享内存

一 共享内存的概念

1. 1 共享内存的原理

     之前我们学过管道通信,分为匿名管道和命名管道,匿名管道通过父子进程的属性继承原理来完成父子进程看到同一份资源的目的,而命名管道则是通过路径与文件名来唯一标识管道文件,来让不同的进程之间进行通信。

   而共享内存也是同理, 就是允许两个或多个不相关的进程访问同一段物理内存空间。

1.2 基本概念 

   不同进程之间共享的内存通常为同一段物理内存。进程可以 将同一段物理内存连接到他们自己的地址空间中 ,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

 概念图如下:

 

 二 共享内存的使用

2.1 使用流程

一般而言,共享内存的使用可以分为以下几步:

  1. 创建共享内存段
  2. 关联共享内存(建立通信)
  3. 传输数据
  4. 关闭共享内存区域

2.1.1 创建共享内存段 shmget

创建共享内存接口:shmget

创建一个新共享内存段或者取得一个既有共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的  共享内存标识符。

接口如下:
 

int shmget(key_t key, size_t size, int shmflg);- 功能: 创建一个新的共享内存段,或者获取一个既有的共享内存段的标识新创建的内存段中的数据都会被初始化为0- 参数:- key: key_t 类型是一个整型,通过这个找到或者创建一个共享内存一般使用16进制标识,非0值- size: 共享内存的大小,以几页的大小创建(大于1的数值,一般为4的整数)- shmflg: 属性- 访问权限- 附加属性: 创建/判断共享内存是不是存在- 创建: IPC_CREAT- 判断共享内存是否存在: IPC_EXCL,需要和 IPC_CREAT一起使用IPC_CREAT | IPC_EXCL | 0664(权限)- IPC_CREAT	如果内核中对应key值的共享内存不存在,则新建一个共享内存并返回该共享内存的句柄;如果已存在,则直接返回该共享内存的句柄– IPC_CREAT IPC_EXCL	如果不存在对应key值的共享内存则新建一个共享内存并返回该共享内存的句柄;如果已存在,则出错返回- 返回值:失败: -1 并设置错误号成功: >0 返回共享内存的引用的ID (int类型),后面操作共享内存都是通过这个值

2.1.2 key值的获取 ftok 

 key值的作用:

问:当我们调用系统接口申请了一块共享内存,我们要保证对应的进程能够访问到同一块共享内存,那么如何做到这一点呢?

答案:每一个共享内存被申请的时候都会有一个key值,这个key值用于标识系统中共享内存的唯一性。

ftok函数的作用:通过数学运算将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中。需要注意的是,pathname所指定的文件必须存在且可存取。

ftok运算出来的key值可能会产生冲突,不过概率很小。如果产生冲突,就对ftok的参数进行修改即可。

当我们想让两个进程使用共享内存的时候,我们就利用key值寻找到这一块共享内存

key_t ftok(const char* pathname, int proj_id);- 功能: 根据指定的路径名, 和int值, 生成一个共享内存的key- 参数: - pathname: 指定一个存在的路径/home/ubuntu/Linux/a.txt/root- proj_id: int类型的值,但是这个系统调用只会使用其中的1个字节(8bit)范围: 0-255   一般指定一个字符 'a'

2.1.3  共享内存的关联 shmat

我们不是单单把共享内存创建出来就行了,我们还要将需要获取这块共享内存的进程与对应的共享内存关联起来,通信结束后,解除关联,最后进行共享内存的释放。

void* shmat(int shmid, const void* shmaddr, int shmflg);- 功能: 和当前的进程进行关联- 参数: - shmid: 共享内存的标识(ID),由shmget返回值获取- shmaddr: 申请的共享内存的起始地址(虚拟内存的地址,一般不会手动指定,由内核指定,设置为NULL)- shmflg: 对共享内存的操作- 读权限: SHM_RDONLY,必须要有读权限- 读写: 0- 返回值:成功: 返回共享内存的首(起始)地址失败: (void*) -1 设置错误号

 2.1.4 解除共享内存的关联 shmdt

   接触进程和共享内存的关联。

int shmdt(const void* shmaddr);- 功能: 解除当前进程和共享内存的关联- 参数:- shmaddr: 共享内存的首地址- 返回值:成功:  0失败: -1

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 2.1.5 删除共享内存函数  shmctl

int shmctl(int shmid, int cmd, struct shmid_ds* buf);- 功能: 对共享内存进行操作。 删除共享内存,共享内存要删除才会消失, 创建共享内存的进程被销毁了对共享内存是没有任何影响的。- 参数:- shmid: 共享内存的ID- cmd: 要做的操作- IPC_STAT: 获取共享内存的当前状态- IPC_SET: 设置共享内存的状态- IPC_RMID: 标记共享内存被销毁- buf: 需要设置或者获取的共享内存的属性信息- IPC_STAT: buf存储数据- IPC_SET: buf中需要初始化数据,设置到内核中- IPC_RMID: 没有用,NULL

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

注意:一般有几个进程关联 这一块共享内存,就需要解除关联多少次,但是,删除共享内存只需要删除一次就好了(其实可以删除多次,看补充)。

2.1.6 示例 

 接下来我们进行一段示例,操作内容如下:

要求:使用代码创建一个共享内存, 支持 A.B 两个进程进行通信。

 进程A 向共享内存当中写 “i am process A”。

 进程B 从共享内存当中读出内容,并且打印到标准输出。

  因为两个进程中有大量重复代码,因此,我们封装一个头文件,以此来复用。

shared.h内容如下:
 

#include<iostream>
#include<cassert>
#include<cstring>
#include<cerrno>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cstdlib>
#include<unistd.h>
using namespace std;#define PATHNAME "./shared2"//ftok的第一个参数,是一个合法路径
#define PROJ_IO 'a'//ftok的第二个参数
#define MAXSIZE 4096//创建的共享内存的大小
//1.获取key值代码
key_t GetKey()
{key_t k = ftok(PATHNAME, PROJ_IO);//如果返回值小于0 则创建失败if(k < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(1);}return k;
}//2.创建共享内存段代码
int GetShmget(key_t k,int flags){int shmid = shmget(k, MAXSIZE, flags);if(shmid < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(2);}return shmid;
}//获取共享内存
int Getshm(key_t k){return GetShmget(k, IPC_CREAT);//没有就创建,有就获取
}//创建共享内存
int Createshm(key_t k){return GetShmget(k, IPC_CREAT | IPC_EXCL | 0600);//没有创建,有就报错,这里创建内存需要给对应的权限
}//3. 关联共享内存,返回共享内存的空间起始位置
void* attachshm(int shmid){void* p = shmat(shmid, nullptr, 0);//因为linux系统是64位,一个地址是8个字节,所以要变成8个字节大小的数据类型做对比if((long long)p == -1L){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(4);}return p;
}//4.解除共享内存的关联
void detachshm(void* p){//如果失败报错if(shmdt(p) == -1){std::cerr << errno << ":" << strerror(errno) << std::endl;}
}//5 shmctl删除共享内存
void delshm(int shmid){if(shmctl(shmid, IPC_RMID, nullptr) == -1){std::cerr << errno << ":" << strerror(shmid) << std::endl;exit(3);}
}

A.cpp:

#include"shared.hpp"int main(){//先生成一个key值key_t k=GetKey();printf("A 获取key值成功: %d\n",k);//创建共享内存,有就获取标识码shmidint shmid=Getshm(k);printf("A 获取共享内存块成功\n");//关联共享内存块char* p = (char*)attachshm(shmid);if (p == (void*)-1) {perror("shmat failed");delshm(shmid);printf("A 删除共享内存成功\n");exit(EXIT_FAILURE);}printf("A 关联共享内存块成功\n");//开始写入:const char* str = "i am process A";snprintf(p, MAXSIZE, "%s", str);//去关联detachshm(p);printf("A 去关联成功\n");return 0;
}

B.cpp

 #include"shared.hpp"int main(){//先生成一个key值key_t k=GetKey();printf("B 获取key值成功: %d\n",k);//创建共享内存,有就获取标识码shmidint shmid=Getshm(k);printf("B 获取共享内存块成功\n");//关联共享内存块char* p = (char*)attachshm(shmid);if (p == (void*)-1) {perror("shmat failed");delshm(shmid);printf("B 删除共享内存成功\n");exit(EXIT_FAILURE);}printf("B 关联共享内存块成功\n");//开始读出:sleep(5);printf("attach sucess, address p: %s\n",p);//去关联detachshm(p);printf("B 去关联成功\n");//删除共享内存delshm(shmid);printf("B 删除共享内存成功\n");return 0;
}

 代码结果:

2.2 共享内存的命令行操作

2.2.1 共享内存的查看 – ipcs指令

 ​  报告进程间通信设施的状态,包括共享内存、消息队列以及信号量等等

ipcs用法:

ipcs -a //打印当前系统中所有进程间通信方式的信息

ipcs -m //打印出使用共享内存进行进程间通信的信息(**常用**)

ipcs -q //打印出使用消息队列进行进程间通信的信息

ipcd -s //打印出使用信号进行进程间通信的信息

2.2.2  共享内存的删除- ipcrm 指令

ipcrm用法(rm:remove)

ipcrm -M shmkey //移除用shmkey创建的共享内存段

ipcrm -m shmid //移除用shmid标识的共享内存段

ipcrm -Q msgkey //移除用msqkey创建的消息队列

ipcrm -q msqid //移除用msqid标识的消息队列

ipcrm -S semkey //移除用semkey创建的信号

ipcrm -s semid //移除用semid标识的信号

三 补充说明

 问题1:操作系统如何知道共享内存被读诵进程关联- 共享内存委会一个结构体  struct shmid_ds 这个结构体中有一个成员 shm_nattach- shm_nattach 记录了关联的进程个数
 问题2:可不可以对共享内存进行多次删除 shmctl- 可以的- 因为 shmctl 仅是标记删除共享内存,不是直接删除- 什么时候真正删除呢?当和共享内存关联的进程数为0的时候,就真正被删除- 当共享内存的key为0的时候, 共享内存被标记删除了- 因此,需要合理衡量共享内存的删除,不要在其它进程还在使用时删除

 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【系统分析师】-需求工程

一、需求工程 需求工程分为需求开发和需求管理。 需求开发&#xff1a;需求获取&#xff0c;需求分析&#xff0c;需求定义、需求验证。 需求管理&#xff1a;变更控制、版本控制、需求跟踪&#xff0c;需求状态跟踪。&#xff08;对需求基线的管理&#xff09; 1.1需求获取…

02-设计概述

上一篇&#xff1a;01-导言 本章重点讨论 JNI 中的主要设计问题。本节中的大多数设计问题都与本地方法有关。调用 API 的设计将在第 5 章&#xff1a;调用 API 中介绍。 2.1 JNI 接口函数和指针 本地代码通过调用 JNI 函数来访问 Java 虚拟机功能。JNI 函数可通过接口指针使用…

如何实现桌面美化

一.隐藏桌面图标 1. 在商店里下载TranslucentTB 二.设置底层图标 1.下载Nexus 打开官网&#xff1a; Winstep Nexus Dock and Nexus Ultimate - The Advanced Docking System for Windows 三.设置插件 1.打开致美化官网 致美化 - 最专业的视觉美化交流平台 (zhutix.com) 2.注…

MySQL进阶:全局锁、表级锁、行级锁总结

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;MySQL进阶&#xff1a;MySQL事务、并发事务问题及隔离级别 &#x1f4da;订阅专栏&#xff1a;MySQL进阶 希望文章对你们有所帮助…

swagger在java中的基本使用

自动生成接口文档&#xff0c;和在线接口测试的框架。 导入依赖 <!-- knife4j对swagger进行一个封装--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><versi…

LeetCode 刷题 [C++] 第121题.买卖股票的最佳时机

题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的…

Linux笔记--系统相关指令

一、系统资源观察 1. df 指令&#xff08;显示文件系统磁盘使用情况统计&#xff09; disk free&#xff0c;显示文件系统磁盘使用情况统计 #以高可读性的形式显示磁盘使用情况统计 df -h (挂载:利用一个目录当成进入点&#xff0c;将磁盘分区槽的数据放置到该目录下&…

Sqli-labs靶场第14关详解[Sqli-labs-less-14]

Sqli-labs-Less-14 #手工注入 post传参了 根据题目看&#xff0c;像一个登录页面&#xff0c;尝试使用布尔型盲注测试能否登录网站 1. Username输入a" 测试是否会有报错&#xff0c;burp抓包 报错&#xff1a;syntax to use near "a"" and password&q…

排序——手撕快排

本节复习快速排序&#xff0c; 快排我们要讲三个版本&#xff1a;一种是霍尔大佬的原版版本&#xff0c; 也就是快速排序的原版。 一种挖坑法。还有一种前后指针法。 首先我们应该知道&#xff0c;三个版本针对的是单趟进行排序的方法不同。 而多趟使用的是递归或者非递归模拟…

Linux 任务进程命令练习

1、通过ps命令的两种选项形式查看进程信息 2、通过top命令查看进程 3、通过pgrep命令查看sshd服务的进程号 4、查看系统进程树 5、使dd if/dev/zero of/root/file bs1M count8190 命令操作在前台运行 6、将第5题命令操作调入到后台并暂停 7、使dd if/dev/zero of/root/file2 bs…

v71.字符串计算

1.字符串 输入和输出 其中scanf("%s",string);读入数据的时候是很微妙的 输入的是Hello world!,输出结果是Hello#。 scanf函数只会读取一段单词&#xff08;字母紧靠一起&#xff09;&#xff0c;遇到回车、空格或者tab就会停止。但是scanf函数的读入是不安全的&am…

艾尔登法环备份存档方法

1.PC端使用WinR输入%AppData%\EldenRing 2.如图创建文件夹“我这取名叫备份存档”&#xff0c;将其中的三个文件复制到新建的文件夹中 3.理论上只需要备份替换ER0000.sl2文件即可