Linux内存映射

目录

背景

一、什么是内存映射?        

 二、mman函数

 1.权限问题

2.总线错误 

3.内存权限

4.读文件内容

5.映射与文件

 6.非法参数错误

 7.偏移量大小

 8.映射内存大小

8.1 申请6k,访问5k

8.2 申请2k,访问3k

8.3 返回值检查

三、内存映射实现

 四、匿名内存映射

 五、munmap函数


背景

        在传统的文件访问中,进程通常使用系统调用(例如open()read()write()close()等)来操作文件。这些系统调用允许进程打开文件、从文件中读取数据、向文件中写入数据以及关闭文件。这种方法需要通过系统调用在用户空间和内核空间之间进行数据传输,因此可能会产生一定的性能开销。  

        而使用内存映射技术,即将文件的内容映射到进程的虚拟内存空间中,使得进程可以直接访问内存而无需通过系统调用来读取或写入文件的内容。这样做可以提高文件访问的效率,因为读写文件的操作直接转化为对内存的操作,避免了频繁的系统调用。

        内存映射是一种将磁盘文件的内容映射到进程的虚拟内存空间中的技术。这意味着文件的内容被映射到进程的地址空间中,使得进程可以直接通过访问内存的方式来读取或写入文件的内容,而无需调用传统的read和write系统调用。这种方式能够提高对文件的访问效率,并且简化了对文件的操作。

内存映射的主要优点包括:

  1. 性能优势:由于文件内容直接映射到了内存中,因此读取文件的操作可以直接通过内存访问完成,避免了频繁的磁盘IO操作,从而提高了读取文件的速度。

  2. 简化操作:通过内存映射,文件被映射到了进程的地址空间中,进程可以像操作内存一样对文件进行读写,这样就简化了对文件的操作流程。

  3. 共享内存:多个进程可以共享同一个文件的内存映射,这样可以实现进程间的数据共享,而无需进行显式的数据传输。

一、什么是内存映射?        

        使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。

 二、mman函数

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

功能:创建共享内存映射

函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED( ((void *) -1) ),设置errno值。

参数说明:


addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。prot:指定共享内存的访问权限。
可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行),PROT_NONE(不可访问)。flags:由以下几个常值指定:MAP_SHARED(共享的),MAP_PRIVATE(私有的), MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。fd:表示要映射的文件句柄。如果匿名映射写-1。offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。

注意事项:

 1.权限问题

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>int main()
{void* addr;int fd;fd = open("test",O_RDONLY);if(fd < 0){perror("open");return 0;}
//	int len = lseek(fd,0,SEEK_END);addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");return 0;}return 0;
}

 文件的打开权限是只读,运行结果为:Permission denied

linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
mmap: Permission denied
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat mman.c

 文件打开权限修改为可读可写即可,因为PROT_READ | PROT_WRITE为可读可写,要求:

        当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。

2.总线错误 

        用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>int main()
{void* addr;int fd;fd = open("test",O_RDWR);if(fd < 0){perror("open");return 0;}
//	int len = lseek(fd,0,SEEK_END);addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");return 0;}memcpy(addr,"abcdefg",7);return 0;
}
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
Bus error (core dumped)
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 

test文件里为空, 不满足:用于映射的文件大小必须>0。直接在test里加个空格都行。

linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat test
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim test
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat testlinux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat test
ablinux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 

文件足够大,就可以显示出abcdef:

ablinux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim test
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ls -l test
-rwxrwxrwx 1 root root 104 Mar  8 00:44 test
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat test
abcdefg                                         

3.内存权限

	addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_PRIVATE,fd,0);

        当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。

linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat testlinux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim test

4.读文件内容

        注意进行强制类型转换,不然会有警告。

	printf("read = %s\n",(char*)addr);	
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat test
abcdefg                            linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
read = abcdefg                            

5.映射与文件

        映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。

linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ cat test
999999999                          
int main()
{void* addr;int fd;fd = open("test",O_RDWR);if(fd < 0){perror("open");return 0;}
//	int len = lseek(fd,0,SEEK_END);addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");return 0;}close(fd);memcpy(addr,"999999999",9);//	printf("read = %s\n",(char*)addr);	return 0;
}

 6.非法参数错误

        指定0大小创建映射区,报非法参数错误(Invalid argument)

	addr = mmap(NULL,0,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
mmap: Invalid argument
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 

可以通过lseek取文件大小

int len = lseek(fd,0,SEEK_END);
printf("%d\n",len);
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ls -l test
-rwxrwxrwx 1 root root 51 Mar  8 01:20 test
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
51
read = 999999999                          linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence) ;

返回值:成功:返回文件新的偏移量(成功) 

lessk()函数的参数说明:
第一个参数是文件描述符;
第二个参数是偏移量,
参数 offset可正可负,负数时向文件开头偏移,正数相对于文件末尾偏移
第三个参数是有三个选项:
1.SEEK_SET:将文件指针偏移到传入字节数处(文件头开始偏移)
2.SEEK_CUR:将文件指针偏移到当前位置加上传入字节数处;((当前位置开始偏移)
3.SEEK_END:将文件指针偏移到文件末尾加上传入字节数处(作为拓展作用,必须再执行一次写操作)

 7.偏移量大小

        文件偏移量必须为0或者4K的整数倍(不是,会报非法参数Invalid argument错误)

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
最后一个参数是偏移量  
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
mmap: Invalid argument
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 
	addr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,1);

 8.映射内存大小

        映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误。

        实际是根据文件大小分配空间的,文件大小是51。你申请2k的空间,实际分配的是4k的空间,而你申请6k,实际分配的还是4k空间,所以报总线错误。

8.1 申请6k,访问5k
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
Bus error (core dumped)
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 
	addr = mmap(NULL,6000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);memcpy((addr+5000),"999999999",9);
8.2 申请2k,访问3k
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ vim mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mman mman.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mman
read = 999999999
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ 
	addr = mmap(NULL,2000,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);memcpy((addr+3000),"999999999",9);

 

8.3 返回值检查

        mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

    void* addr;    addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");return 0;}

三、内存映射实现

 mmanw.c

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>int main()
{void* addr;int fd;fd = open("test",O_RDWR);if(fd < 0){perror("open");return 0;}int len = lseek(fd,0,SEEK_END);addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");return 0;}close(fd);int i = 0;while(i<2048){memcpy((addr+i),"a",1);i++;sleep(1);}return 0;
}

mmanr.c

#include<stdio.h>#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>int main()
{void* addr;int fd;fd = open("test",O_RDWR);if(fd < 0){perror("open");return 0;}int len = lseek(fd,0,SEEK_END);addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(addr == MAP_FAILED){perror("mmap");return 0;}close(fd);while(1){printf("read = %s\n",(char*)(addr));sleep(1);}return 0;
}

 四、匿名内存映射

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

         fd:表示要映射的文件句柄。如果匿名映射写-1。

        匿名映射适用于具有亲缘关系的进程之间。

linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ gcc -o mmap_n mmap_n.c
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mmap_n.c
./mmap_n.c: line 13: syntax error near unexpected token `('
./mmap_n.c: line 13: `int main()'
linux@linux:/mnt/hgfs/linuxshare/linux_code/mman$ ./mmap_n
read father value = 1234567890
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{void* addr;//匿名映射不需要文件操作addr = mmap(NULL,2048,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);if(addr == MAP_FAILED){perror("mmap");return 0;}pid_t pid;pid = fork();if(pid < 0){perror("pid");return 0;}else if(pid > 0){memcpy(addr,"1234567890",10);wait(NULL);}else{sleep(1);printf("read father value = %s\n",(char*)addr);}return 0;
}

        使用mmap函数创建一个大小为2048字节的共享内存区域。mmap的第一个参数是指定的映射地址(这里为NULL表示由系统决定),然后指定映射的大小为2048字节,设置读写权限为可读可写,使用MAP_SHARED标志来创建一个共享映射,MAP_ANONYMOUS标志表示不与任何文件关联 。

        使用fork函数创建一个子进程。如果fork返回负值,则表示创建失败,父进程会输出错误信息并退出。如果fork返回0,则表示当前代码运行在子进程中,子进程将等待1秒后,从共享内存中读取数据并打印输出。如果fork返回正值,则表示当前代码运行在父进程中,父进程使用memcpy将字符串"1234567890"复制到共享内存中,然后等待子进程结束。

为什么?

父进程在写入数据后,可能会导致子进程读取数据,
而如果父进程提前退出,操作系统会清理共享内存,
这可能导致子进程读取到的数据不完整或者无效。通过调用wait(NULL),父进程等待子进程执行完毕,
确保子进程能够正常读取共享内存中的数据。
只有在子进程执行完毕后,父进程才会继续执行后续的操作,包括退出程序,
这样就不会导致共享内存被提前清理。

 

 五、munmap函数

int munmap(void *addr, size_t length);​​​​​​​返回值:成功返回0,失败返回-1,并设置errno值。函数参数:addr:调用mmap函数成功返回的映射区首地址length:映射区大小(即:mmap函数的第二个参数)

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

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

相关文章

【Unity】分拣机的数字双胞胎集成到Unity3D开发平台中

Unity HMI 一、前言 该项目的重点是通过OPC UA进行客户端-服务器通信的简单演示&#xff0c;该演示在Unity3D中实现&#xff08;服务器- B&R Automation PLC&#xff0c;客户端- Unity3D&#xff09;。该项目展示了数字孪生的分拣机与一些额外的功能。该应用程序使用多线程…

探索Web中的颜色选择:不同取色方法的实现

在Web开发中&#xff0c;提供用户选择颜色的功能是很常见的需求。无论是为了个性化UI主题&#xff0c;还是为了图像编辑工具&#xff0c;一个直观且易用的取色器都是必不可少的。本文将介绍几种在Web应用中实现取色功能的方法&#xff0c;从简单的HTML输入到利用现代API的高级技…

【DP】蓝桥杯第十三届-费用报销

#include<iostream> #include<algorithm> #include<cstring> #include<set> #include<queue> using namespace std; const int N1010; int dp[N][5010];//dp[i][j]:选到第i个物品是否能取到价值j&#xff1b; int month[13]{0,31,28,31,30,31,30…

从零开始的LeetCode刷题日记:142.环形链表II

一.相关链接 视频链接&#xff1a;代码随想录&#xff1a;142.环形链表II 题目链接&#xff1a;142.环形链表II 二.心得体会 这道题是一道链表题&#xff0c;但他没有对头结点的操作&#xff0c;所以不用虚拟头结点。这道题要分两步进行&#xff0c;第一步是判断链表有没有环…

解析医疗影像中的dicom文件

一、DICOM文件概述 我们先了解一下DICOM文件是什么&#xff0c;干嘛用的&#xff0c;以及DICOM内部有哪些信息&#xff0c;然后再谈如何去解析这些信息并转换成java对象。 医学影像学概览 医学影像学 这一学科致力于利用X射线、电磁场、超声波等多种介质与人体相互作用原理&…

力扣大厂热门面试算法题 6-8

6. Z 字形变换&#xff0c;7. 整数反转&#xff0c;8. 字符串转换整数 (atoi)&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.08 可通过leetcode所有测试用例。 目录 6. Z 字形变换 解题思路 边界条件 完整代码 Python Ja…

智慧园区综合运营数字化解决方案

1. 楼栋管理 2. 物业管理 3. 安防管理 4. 门禁管理 5. 停车管理 6. 能源管理 7. 环保管理 8. 园区生活服务 9. 招商管理 10. 收费中心 11. 园区地图 12. 门户网站 智慧园区软件方案&#xff1a;智慧园区软件解决方案&#xff0c;园区运营管理系统&#xff08;源码&#xff09;-…

知识图谱 | 2023年图书馆学、情报学CSSCI期刊论文主题透视

数据来源 检索平台来源期刊年份有效数据中国知网大学图书馆学报国家图书馆学刊情报科学情报理论与实践情报学报情报杂志情报资料工作数据分析与知识发现图书馆建设图书馆论坛图书馆学研究图书馆杂志图书情报工作图书情报知识图书与情报现代情报信息资源管理学报中国图书馆学报2…

形参化类 ‘Result‘ 的原始使用

在编程中&#xff0c;特别是在面向对象编程&#xff08;OOP&#xff09;中&#xff0c;当我们说“形参化类”或“参数化类”&#xff0c;我们实际上是指泛型&#xff08;Generics&#xff09;的概念。泛型允许在定义类、接口和方法时使用类型参数。这样&#xff0c;你可以创建可…

量化投资实战(三)之配对交易策略--协整模型法

点赞、关注&#xff0c;养成良好习惯 Life is short, U need Python 量化投资实战系列&#xff0c;不断更新中 1. 初识配对交易策略 配对交易&#xff08;Pairing Trading&#xff09;是指八十年代中期华尔街著名投行Morgan Stanley的数量交易员Nunzio Tartaglia成立的一个数量…

springboot+xjar加密打包部署教程

需求背景 为了跟上时代的步伐&#xff0c;为了更好的生存。开个玩笑&#xff0c;就是心血来潮&#xff0c;使用xjar加密部署jar包&#xff0c;于是就测试一下。 xjar教程 1-maven配置文件修改 首先找到自己ideal配置的maven文件夹&#xff0c;然后找到apache-maven-3.9.3\co…

微信小程序可拖拽视频播放案例

微信小程序可拖拽视频播放案例 如图所示 使用原生小程序组件 movable-area movable-view 注意movable-view必须在area内 官方组件地址 wxml <movable-area class"movableArea"><movable-view class"movableView" out-of-bounds"false&q…