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

概述

什么是共享内存:

共享内存又叫内存映射,可以通过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>
#include <semaphore.h>#define FILE_PATH "./mmap"
int main(){int fd;void* mmap_addr = NULL;int i=0;char buf[100] = {0};sem_t* sem_mmap;//打开文件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);//创建共享内存映射后可以关闭文件描述符//创建信号量if((sem_mmap = sem_open("sem_mmap",O_CREAT,0666,1)) == SEM_FAILED){perror("sem_open");return -1;}//进程间通信memcpy(mmap_addr,"A Start SIG",strlen("A Start SIG"));while(1){sem_wait(sem_mmap);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));}sem_post(sem_mmap);}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>
#include <semaphore.h>#define FILE_PATH "./mmap"
int main(){int fd;void* mmap_addr = NULL;int i=0;char buf[100] = {0};sem_t* sem_mmap;//打开文件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;}//创建信号量if((sem_mmap = sem_open("sem_mmap",O_CREAT,0666,1)) == SEM_FAILED){perror("sem_open");return -1;}close(fd);//创建共享内存映射后可以关闭文件描述符//进程间通信while(1){sem_wait(sem_mmap);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);}sem_post(sem_mmap);}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.生成keyif((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.生成keyif((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.rhkb.cn/news/447850.html

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

相关文章

CV实战01 YOLOv5实现图像分割

网上翻了一天&#xff0c;没找到称心的教程&#xff0c;最后发现还是Ultralytics官方的教程文档好用&#xff01;这里贴上官方教程一起学习&#xff01; 【1&#xff1a;找到官方教程文档】 yolov5官方下载地址&#xff1a;GitHub - ultralytics/yolov5: YOLOv5 &#x1f680…

数字后端零基础入门系列 | Innovus零基础LAB学习Day1

一 Floorplan 数字IC后端设计如何从零基础快速入门&#xff1f;(内附数字IC后端学习视频&#xff09; Lab5-1这个lab学习目标很明确——启动Innovus工具并完成设计的导入。 在进入lab之前&#xff0c;我们需要进入我们的FPR工作目录。 其中ic062为个人服务器账户。比如你端…

多线程代码案例

案例一.单例模式 单例模式是一种设计模式;类似于棋谱,有固定套路,针对一些特定场景可以给出一些比较好的解决方案; 只要按照设计模式来写代码,就可以保证代码不会太差,保证了代码的下限; --------------------------------------------------------------------------------…

【优选算法】(第三十六篇)

目录 ⼆叉树的锯⻮形层序遍历&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 ⼆叉树的最⼤宽度&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 ⼆叉树的锯⻮形层序遍历&#xff08;medium&#xff09; 题目解析 1.题目链接&#xf…

植物大战僵尸杂交版

最新版植物大战僵尸杂交版 最近本款游戏火爆 下载资源如下&#xff1a; win版本&#xff1a;2.3.7 链接&#xff1a;下载地址 提取码&#xff1a;9N3P Mac&#xff08;苹果版本&#xff09;&#xff1a;2.0.0 链接&#xff1a;下载地址 提取码&#xff1a;Bjaa 介绍&#xff…

mysql/doris 计算两个时间相差n天n时n分示范

mysql/doris 计算两个时间相差n天n时n分示范 两个时间&#xff1a;so.create_time&#xff0c;so.update_time CONCAT(FLOOR(DATEDIFF(HOUR ,so.create_time,so.update_time)/24),天,DATEDIFF(HOUR ,so.create_time,so.update_time)%24,时,DATEDIFF(MINUTE ,so.create_time,so…

【重学 MySQL】六十六、外键约束的使用

【重学 MySQL】六十六、外键约束的使用 外键约束的概念关键字主表和从表/父表和子表外键约束的创建条件外键约束的特点外键约束的创建方式外键约束的删除外键约束的约束等级外键约束的级联操作外键约束的示例外键约束的作用开发场景阿里开发规范 在MySQL中&#xff0c;外键约束…

(已解决)vscode使用launch.json进行debug调试报错:Couldn‘t spawn debuggee:embedded null byte

Launch.json 进行debug时报错&#xff1a; 主要原因是vscode全局配置被整乱了&#xff0c;下面是个人解决的方法&#xff0c;以供参考. 在网上也寻找过解决方法&#xff0c;有的说是&#xff0c;在launch.json中&#xff0c;添加一行"python":"/root/miniconda3…

git版本控制软件,操作方法

git版本库操作 1. 注册用户信息 git config --global (邮箱和用户名) 2. 创建工作区 git init 3. 编写文件 vim readme.txt 4. 把文件放到暂存区 git add readme.txt 5. 查看工作区状态 git status 6. 把文件放到本地版本库里 git commit -m "" filename 7. 查看日志…

总结拓展十四:批次管理(2)

1、批次管理后台配置 1.1 批次管理级别配置(T-code:OMTC) ——路径&#xff1a;IMG->后勤-常规->批次管理->指定级别并激活状态管理 1.2 批次状态管理配置(T-code:OMTC) ——路径&#xff1a;IMG->后勤-常规->批次管理->指定级别并激活状态管理 批状态管…

2.1.ReactOS系统NtReadFile函数的实现。

ReactOS系统NtReadFile函数的实现。 ReactOS系统NtReadFile函数的实现。 文章目录 ReactOS系统NtReadFile函数的实现。NtReadFile函数的定义NtReadFile函数的实现 NtReadFile()是windows的一个系统调用&#xff0c;内核中有一个叫NtReadFile的函数 NtReadFile函数的定义 NTS…

【Go初阶】两万字快速入门Go语言

初见golang语法 package mainimport "fmt"func main() {/* 简单的程序 万能的hello world */fmt.Println("Hello Go")} 第一行代码package main定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包&#xff0c;如&#xff1a;package main…

如何捕捉行情爆发的前兆

在金融市场的激烈角逐中&#xff0c;每一次行情的爆发都是投资者获取丰厚回报的关键时刻。然而&#xff0c;如何识别并把握这些时刻&#xff0c;却是一门需要深厚金融专业知识和敏锐洞察力的艺术。今天&#xff0c;我们就来深入探讨行情爆发的初期信号&#xff0c;揭示那些能够…

【Linux】嵌入式Linux系统的组成、u-boot编译

Linux—嵌入式Linux系统的组成、u-boot编译 前言一、嵌入式Linux系统的组成1.1 嵌入式Linux系统和PC完整的操作系统的对比如下&#xff1a;1.2 PC机—Windows系统启动流程&#xff08;PC机—Linux系统、嵌入式ARM—linux系统的启动流程类似&#xff09; 二、编译u-boot2.1 u-bo…

【数据分享】我国第七次人口普查的100m分辨率人口栅格数据(免费获取\tif格式\2020年)

人口空间分布数据是我们在各项研究中经常使用的数据。之前我们分享过来源于LandScan数据集的2000-2022年的1km精度的人口空间分布栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff01; 相较于LandScan全球人口数据集&#xff0c;我国历次人口普查的数据对于…

【node】初识node

前言 目标 1 为什么要学习node 2 node如何安装 #mermaid-svg-KR8iFyZTmb86RU67 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KR8iFyZTmb86RU67 .error-icon{fill:#552222;}#mermaid-svg-KR8iFyZTmb86RU67 .error…

QT--QPushButton设置文本和图标、使能禁能、信号演示

按钮除了可以设置显示文本之外&#xff0c;还可以设置图标 文本 可以获取和设置按钮上显示的文本 // 获取和设置按钮的文本 QString text() const void setText(const QString &text)该属性&#xff0c;既可以在 Qt 设计师右侧的属性窗口中修改&#xff0c;也可以在代码…

OpenAI的Swarm是一个实验性质的多智能体编排框架

先上文档&#xff0c;然后解释&#xff0c;然后是代码 OpenAI的Swarm是一个实验性质的多智能体编排框架&#xff0c;旨在简化多智能体系统的构建、编排和部署。以下是对Swarm的详细介绍&#xff1a; 一、核心概念和特点 智能体&#xff08;Agent&#xff09;&#xff1a; Swar…

int QSqlQuery::size() const

返回结果的大小&#xff08;返回的行数&#xff09; 或者返回-1 &#xff08;如果大小不能被决定 或者 数据库不支持报告查询的大小信息&#xff09; 注意&#xff1a;对于非查询语句&#xff0c;将返回-1&#xff08;isSelect()返回false&#xff09; 如果查询不是活跃的&…

支付宝开放平台-开发者社区——AI 日报「10 月 15 日」

1 10年后手机有多科幻&#xff1f;清华孙茂松&#xff1a;人手一个超级大脑&#xff0c;诊病翻译搞研发 新智元&#xff5c;阅读原文 我们有办法将大模型「化大为小」&#xff0c;同时其智能能力没有太多下降&#xff0c;从而以一种「小而美」的方式达至生成式人工智能与手机…