12.2 Linux_进程间通信_共享内存

news2024/10/10 0:50:19

概述

什么是共享内存:

共享内存又叫内存映射,可以通过mmap()映射普通文件。

实际上就是将磁盘中的一个文件映射到内存的一个缓冲区中去,这样进程就可以直接将这块空间当作普通内存来访问,不需要再使用I/O中的read/write去访问这个文件。

映射之后,内存中读写数据就是在文件中读写数据。

共享内存使用方法:

1、open打开一个文件

2、mmap创建共享内存映射,注意mmap权限要<=open时的权限

3、直接按照内存方式访问共享内存。

共享内存分配的原理:

内存是按页进行分配的,一页的大小为4K。假设文件大小为1K,但它实际所分配的空间是一页,即4K。但可操作的空间是1K。

当对1K的文件申请1K的共享内存时,分配的映射空间大小实际为4K,即:分配的映射空间大小为4K的整数倍。同理,申请2K的共享内存时,分配的映射空间大小也为4K;申请5K的共享内存时,分配的映射空间大小就变成了8K。

按照上述情况进行分配时,在代码中允许写入的映射区地址范围为0~4K,但是只有0~1K的空间可以对文件内容产生影响在1K~4K空间进行写入数据不会报错,但也不会对文件产生影响,但申请5K共享内存后,访问4K~5K的空间会产生总线错误报错,因为文件空间只有0~4K

下图是文件大小为5K,实际分配8K空间,在mmap申请5K的示意图:

相关函数

1、创建共享内存映射

mmap的参数在内核中的示意图:

mmap将指定文件(fd)的指定位置(off~off+len)的空间,映射到指定内存(add)中并返回这块内存的首地址(void*返回值)。 

函数声明如下: 

void *mmap(void *addr, 
           size_t length, 
           int prot, 
           int flags,
           int fd, 
           off_t offset);

返回值:成功返回映射区的首地址,失败返回MAP_FAILED

addr:指定的内存映射地址,NULL代表自动分配

length:映射空间大小,单位字节,从offset参数开始计算。

             注意:length会自动与4K进行补齐,如:length=1K,实际分配4K

prot:共享内存的访问权限,多个权限之间可用 "按位或 |" 连接

          注意:prot权限应该 <= open时的权限

权限含义
PROT_READ可读
PROT_WRITE可写
PROT_EXEC可执行
PROT_NONE不可访问

flags:共享内存的属性,进程间通信时写入MAP_SHARED,代表映射内存允许共享。

fd:要进行映射的文件的文件描述符

offset:要进行映射的文件的偏移量,写0代表从头部开始映射。

            注意:offset值为4K的整数倍,因为内存按页进行分配,页的大小为4K

2、共享内存读写数据

//写入数据
void *memcpy(void *dest, const void *src, size_t n);
//读取数据,读取数据就是直接访问内存
printf("%s\n",(char*)addr);

dest:内存首地址

src:写入数据首地址

n:写入数据的大小

3、释放内存映射 

int munmap(void *addr, size_t length);

返回值:成功返回0,失败返回-1

addr:mmap的返回值

length:mmap开辟的内存大小,与mmap写入相同的参数即可

实验代码

1、单工数据传输

实验现象:A进程不断的写入数据'A',B进程每隔1s读取一下共享内存中的内容。

A.c代码如下:

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_PATH "./mmap"
int main(){

	int fd;
	void* mmap_addr = NULL;
	int i=0;
	char buf[100] = {0};
	//打开文件
	if((fd=open(FILE_PATH,O_RDWR)) < 0){
		perror("open");
		return -1;
	}
	//创建共享内存映射
	if((mmap_addr = mmap(NULL,4*1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
		perror("mmap");
		return -1;
	}
	memset(mmap_addr,0,lseek(fd,0,SEEK_END));//清空缓冲区
	close(fd);//创建共享内存映射后可以关闭文件描述符
	//进程间通信
	while(1){
		memcpy(mmap_addr+i,"A",strlen("A"));
		i++;
		sleep(1);
	}
	return 0;
}

B.c代码如下: 

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_PATH "./mmap"
int main(){

	int fd;
	void* mmap_addr = NULL;
	int i=0;
	char buf[100] = {0};
	//打开文件
	if((fd=open(FILE_PATH,O_RDWR)) < 0){
		perror("open");
		return -1;
	}
	//创建共享内存映射
	if((mmap_addr = mmap(NULL,4*1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
		perror("mmap");
		return -1;
	}
	close(fd);//创建共享内存映射后可以关闭文件描述符
	//进程间通信
	while(1){
		printf("read:%s\n",(char*)mmap_addr);
		sleep(1);
	}
	return 0;
}

代码运行结果如下:

2、AB进程互传数据(存在问题)

实验现象:A发送开始信号后,AB进程开始互传数据

存在数据丢失问题,未解决?????????

A.c代码如下:

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_PATH "./mmap"
int main(){

	int fd;
	void* mmap_addr = NULL;
	int i=0;
	char buf[100] = {0};
	//打开文件
	if((fd=open(FILE_PATH,O_RDWR)) < 0){
		perror("open");
		return -1;
	}
	//创建共享内存映射
	if((mmap_addr = mmap(NULL,4*1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
		perror("mmap");
		return -1;
	}
	memset(mmap_addr,lseek(fd,0,SEEK_END),strlen(mmap_addr));//清空缓冲区
	close(fd);//创建共享内存映射后可以关闭文件描述符
	//进程间通信
	memcpy(mmap_addr,"A Start SIG",strlen("A Start SIG"));
	while(1){
		if(*(char*)mmap_addr == 'B'){
			//读出B进程写入的内容
			printf("A read:%s\n",(char*)mmap_addr+strlen("B"));//读取数据,不读取数据来源标号
			memset(mmap_addr,0,strlen(mmap_addr));//清空缓冲区
			//写入新数据
			memcpy(mmap_addr,"A",strlen("A"));//数据来源标号
			sprintf(buf,"A_Data:%d",i++);     //新数据
			memcpy(mmap_addr+strlen("A"),buf,strlen(buf));
		}
	}
	return 0;
}

B.c代码如下: 

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_PATH "./mmap"
int main(){

	int fd;
	void* mmap_addr = NULL;
	int i=0;
	char buf[100] = {0};
	//打开文件
	if((fd=open(FILE_PATH,O_RDWR)) < 0){
		perror("open");
		return -1;
	}
	//创建共享内存映射
	if((mmap_addr = mmap(NULL,4*1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED){
		perror("mmap");
		return -1;
	}
	close(fd);//创建共享内存映射后可以关闭文件描述符
	//进程间通信
	while(1){
		if(*(char*)mmap_addr == 'A'){
			//读出B进程写入的内容
			printf("B read:%s\n",(char*)mmap_addr+strlen("A"));
			memset(mmap_addr,0,strlen(mmap_addr));//清空缓冲区
			//写入新数据
			memcpy(mmap_addr,"B",strlen("B"));//数据来源标号
			sprintf(buf,"B get A data,B data is %d",i++);     //新数据
			memcpy(mmap_addr+strlen("B"),buf,strlen(buf));
			sleep(1);
		}
	}
	return 0;
}

共享内存注意事项

1、共享内存创建隐含读操作:

当mmap创建共享内存后,会自动的将要进行映射的文件的内容读取到映射区。

2、总线错误报错原因:

原因1:

当用于映射的文件大小为0,且指定非0大小的映射区时,会报错总线错误。

解决方法:将空文件中加一个空格,使得文件不是空文件即可。

原因2:

当映射的文件的大小<映射区的大小时,这时不进行报错。但如果访问空间超出了页的范围,则会报错总线错误。

3、非法参数错误报错原因:

原因1:mmap传入的length值为0

原因2:mmap传入的offset值不为4K的整数倍

4、写入字符不全原因:

用于映射的文件大小为A,指定映射区大小为B,当A<B时,只会写入A大小的数据,即:最多写入的数据大小是文件的大小。

5、映射区建立后,便可关闭文件:

在mmap之后就可用关闭进行映射的文件,这时对共享内存进行写入依旧可以修改磁盘文件的内容。

匿名映射

匿名映射不需要文件,但只能用于血缘关系进程之间的通信。

mmap的flags参数写入MAP_SHARED|MAP_ANONYMOUS,fd参数写入-1

#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

int main(){
	pid_t pid;
	int i=0;
	void* mmap_addr = NULL;
	//创建共享内存映射
	if((mmap_addr = mmap(NULL,4*1024,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0)) == MAP_FAILED){
		perror("mmap");
		return -1;
	}
	if((pid = fork())<0){
		perror("fork");
		return -1;
	}else if(pid == 0){
		while(1){
			memcpy(mmap_addr+i,"A",strlen("A"));
			i++;
			sleep(1);
		}
	}else{
		while(1){
			printf("%s\n",(char*)mmap_addr);
			sleep(1);
		}
	}

	return 0;
}

systemV共享内存

使用步骤:

  • 生成key
  • 创建/打开共享内存
  • 映射共享内存
  • 读写共享内存
  • 撤销共享内存
  • 删除共享内存 

共享内存相关命令:

ipcs 查看共享内存、消息队列、信号灯

ipcrm -m <shmid>:删除指定的共享内存

1、生成key

key_t ftok(const char *pathname, int proj_id);

返回值:成功返回key,失败返回-1

pathname:文件路径

proj_id:用于生成key的数字,范围1~255

该函数可以将pathname的节点号与proj_id进行结合,生成一个整数key,能够确保key不重复。

2、创建/打开共享内存

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

返回值:成功返回共享内存的id,失败返回EOF

key:和共享内存关联的key值,由ftok生成或写入IPC_PRIVATE

size_t:共享内存大小,单位字节

shmflg:标志位,写入IPC_CREAT|0666,代表创建共享内存权限可读可写

3、映射共享内存

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

返回值:成功返回映射后的地址,失败返回(void*)-1

shmid:共享内存的id

shmaddr:映射内存空间,NULL代表由系统自动分配

shmflg:标志位,0代表可读可写,SHM_RDONLY代表只读

4、撤销共享内存

int shmdt(const void *shmaddr);

返回值:成功返回0,失败返回EOF

shmaddr:shmat返回的地址

进程结束时,会自动撤销共享内存。

注意:撤销之后只代表映射的内存空间不存在了,但共享内存还在。

5、删除共享内存

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

shmid:共享内存的id

cmd:要执行的操作,写入IPC_RMID

buf:保存或设置共享内存属性的地址,写入NULL即可

注意:共享内存在创建后,不再使用一定要删除,否则会导致内存泄漏。

示例代码 

写端代码如下:

#include <string.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main(){

	key_t key;
	int shmid;
	void* shmaddr = NULL;
	//1.生成key
	if((key=ftok(".",1))<0){
		perror("ftok");
		return -1;
	}
		perror("ftok");
	printf("key = %d\n",key);
	//2.创建/打开共享内存
	if((shmid=shmget(key,100,IPC_CREAT|0666))<0){
		perror("shmget");
		return -1;
	}
	printf("shmid = %d\n",shmid);
	//3.映射共享内存
	if((shmaddr=shmat(shmid,NULL,0)) == (void*)-1){
		perror("shmat");
		return -1;
	}	
	//4.读写共享内存
   	strcpy(shmaddr,"hello");
	//memcpy(shmaddr,"hello",strlen("hello")); 	
	//5.撤销共享内存
	shmdt(shmaddr);	
	//6.删除共享内存
	return 0;
}

读端代码如下:

#include <string.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main(){

	key_t key;
	int shmid;
	void* shmaddr = NULL;
	//1.生成key
	if((key=ftok(".",1))<0){
		perror("ftok");
		return -1;
	}
	printf("key = %d\n",key);
	//2.创建/打开共享内存
	if((shmid=shmget(key,100,0666))<0){//以可读可写方式打开共享内存
		perror("shmget");
		return -1;
	}
	printf("shmid = %d\n",shmid);
	//3.映射共享内存
	if((shmaddr=shmat(shmid,NULL,0)) == (void*)-1){
		perror("shmat");
		return -1;
	}	
	//4.读写共享内存
	printf("read:%s\n",(char*)shmaddr);
	//5.撤销共享内存
	shmdt(shmaddr);	
	//6.删除共享内存
	shmctl(shmid,IPC_RMID,NULL);
	return 0;
}

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

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

相关文章

霍普菲尔德(Hopfield)神经网络求解旅行商问题TSP,提供完整MATLAB代码,复制粘贴即可运行

Hopfield神经网络是以美国物理学家约翰霍普菲尔德&#xff08;John Hopfield&#xff09;的名字命名的。他在1982年提出了这种类型的神经网络模型&#xff0c;因此通常被称为Hopfield网络。旅行商问题&#xff08;Traveling Salesman Problem&#xff0c;TSP&#xff09;是一个…

IEDA创建文件模板

1、点击设置-编辑器-文件与代码模板 2、输入对应的名称、扩展名、文件名 3、复制模板代码-点击应用、确定即可 4、新建配置项目&#xff0c;右键点击新建选择SpringMVC即可&#xff08;刚刚模板中的名称&#xff09;

D32【python 接口自动化学习】- python基础之输入输出与文件操作

day32 文件编码 学习日期&#xff1a;20241009 学习目标&#xff1a;输入输出与文件操作&#xfe63;-44 文件编码&#xff1a; 如何解决不同操作系统的文件乱码问题&#xff1f; 学习笔记&#xff1a; 为什么产生乱码 常见操作系统的文件编码 以不同的编码打开文件 # 以gb…

Linux学习网络编程学习(TCP和UDP)

文章目录 网络编程主要函数介绍1、socket函数2、bind函数转换端口和IP形式的函数 3、listen函数4、accept函数网络模式&#xff08;TCP&UDP&#xff09;1、面向连接的TCP流模式2、UDP用户数据包模式 编写一个简单服务端编程5、connect函数编写一个简单客户端编程 超级客户端…

如何实现不同VLAN间互通?

问题描述 客户要求不同VLAN的PC机互通&#xff0c;如下图拓扑所示。 此外&#xff0c;仅允许在设备 LSW3 上进行配置修改。 分析 由于所有的PC都在同一个网段&#xff0c;当任何一个设备想要和另一个设备通信时&#xff0c;它会首先根据数据交互的流程广播一个ARP请求报文来获…

1. Keepalived概念和作用

1.keepalived概念 (1)解决单点故障(组件免费) (2)可以实现高可用HA机制 (3)基于VRR协议(虚拟路由沉余协议) 2.keepalived双机主备原理

枚举+二分,CF 325B - Stadium and Games

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 325B - Stadium and Games 二、解题报告 1、思路分析 考虑 一个可能的初…

QD1-P8 HTML格式化标签

本节学习&#xff1a;HTML 格式化标签。 本节视频 www.bilibili.com/video/BV1n64y1U7oj?p8 ‍ 一、font 标签 用途&#xff1a;定义文本的字体大小、颜色和 face&#xff08;字体类型&#xff09;。 示例 <!DOCTYPE html> <html><head><meta cha…

10.9QT对话框以及QT的事件机制处理

MouseMoveEvent(鼠标移动事件) widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 设置窗口为无边框&#xff0c;去掉标题栏等装饰this->setWi…

如何使用ArcGIS Pro设置一个图层不同标注

在有些时候&#xff0c;需要对某个要素进行突出显示&#xff08;比如省会城市&#xff09;&#xff0c;那就需要标注不同的样式&#xff0c;这里为大家介绍一下一个图层不同标注的方法。 分类标注 现在有一张广东省的行政区划图&#xff0c;想要突出标注广州市&#xff0c;虽…

超详解C++类与对象(中)

目录 1. 构造函数 1.1. 定义 1.2. 注意 2.析构函数 2.1定义 2.2注意 3.拷贝构造函数 3..1. 定义 3.2. 注意 4.运算符重载 4.1. 定义 5. 赋值运算符重载 5.1. 定义 5.2. 注意 ​​​​​​​ &#x1f493; 博客主页&#xff1a;C-SDN花园GGbond ⏩ 文章专…

大模型学习----什么是RAG

大模型快速定制的 RAG&#xff08;Retrieval-Augmented Generation&#xff09;方法 一、什么是 RAG RAG&#xff08;Retrieval-Augmented Generation&#xff09;即检索增强生成&#xff0c;它是一种结合了检索和语言生成的技术&#xff0c;旨在利用外部知识源来增强大型语言…

YOLO11改进|注意力机制篇|引入全局上下文注意力机制GCA

目录 一、【】注意力机制1.1【GCA】注意力介绍1.2【GCA】核心代码 二、添加【GCA】注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【】注意力机制 1.1【GCA】注意力介绍 下图是【GCA】的结构图&#xff0c;让我们简单分析一下…

SQL优化 where谓词条件is null优化

1.创建测试表及谓词条件中包含is null模拟语句 create table t641 as select * from dba_objects; set autot trace select SUBOBJECT_NAME,OBJECT_NAME from t641 where OBJECT_NAMEWRI$_OPTSTAT_SYNOPSIS$ and SUBOBJECT_NAME is null; 2.全表扫描逻辑读1237 3.创建等值谓词条…

PE结构之导出表

导出表结构中各种值的意义 ​​​​​​ 根据函数地址表遍历函数名称RVA表,和上面的图是逆过程 //函数地址表 和当前内存中的位置DWORD AddressOfFunctionsFOA RVAToFOA(LPdosHeader, LPexprotDir->AddressOfFunctions);PDWORD LPFunctionsAddressInMemary (PDWORD)((cha…

flask发送邮件

开通邮件IMAP/SMTP服务 以网易邮箱为例 点击开启发送验证后会收到一个密钥&#xff0c;记得保存好 编写代码 安装flask-mail pip install flask-mail在config.py文件中配置邮件信息 MAIL_SERVER&#xff1a;邮件服务器 MAIL_USE_SSL&#xff1a;使用SSL MAIL_PORT&#…

【计算机网络】网络相关技术介绍

文章目录 NAT概述NAT的基本概念NAT的工作原理1. **基本NAT&#xff08;静态NAT&#xff09;**2. **动态NAT**3. **NAPT&#xff08;网络地址端口转换&#xff0c;也称为PAT&#xff09;** 底层实现原理1. **数据包处理**2. **转换表**3. **超时机制** NAT的优点NAT的缺点总结 P…

Linux:多线程中的生产消费模型

多线程 生产消费模型三种关系两个角色一个交易场所交易场所的实现&#xff08;阻塞队列&#xff09;pthread_cond_wait 接口判断阻塞队列的空或满时&#xff0c;需要使用while测试一&#xff1a;单消费单生产案例测试二&#xff1a;多生产多消费案例 生产消费模型 消费者与生产…

鸿蒙网络管理模块05——数据流量统计

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、概述 HarmonyOS供了基于物理网络的数据流量统计能力&#xff0c;支持基于网卡/U…

贪心,CF 865B - Ordering Pizza

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 865B - Ordering Pizza 二、解题报告 1、思路分析 如果我们不考虑披萨数…