linux mmap 内存映射【转】

简介:
 

http://www.perfgeeks.com/?p=723

 

 

mmap() vs read()/write()/lseek()

通过strace统计系统调用的时候,经常可以看到mmap()与mmap2()。系统调用mmap()可以将某文件映射至内存(进程空间),如此可以把对文件的操作转为对内存的操作,以此避免更多的lseek()与read()、write()操作,这点对于大文件或者频繁访问的文件而言尤其受益。但有一点必须清楚:mmap的addr与offset必须对齐一个内存页面大小的边界,即内存映射往往是页面大小的整数倍,否则maaped_file_size%page_size内存空间将被闲置浪费。

演示一下,将文件/tmp/file_mmap中的字符转成大写,分别使用mmap与read/write二种方法实现。

/*
* @file: t_mmap.c
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/mman.h> /*mmap munmap*/
#include <sys/types.h>
#include <sys/stat.h> #include <fcntl.h> #include <unistd.h>   int main(int argc, char *argv[]) { int fd; char *buf; off_t len; struct stat sb; char *fname = "/tmp/file_mmap";   fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); return 1; } if (fstat(fd, &sb) == -1) { perror("fstat"); return 1; }   buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { perror("mmap"); return 1; }   if (close(fd) == -1) { perror("close"); return 1; }   for (len = 0; len < sb.st_size; ++len) { buf[len] = toupper(buf[len]); /*putchar(buf[len]);*/ }   if (munmap(buf, sb.st_size) == -1) { perror("munmap"); return 1; } return 0; } #gcc –o t_mmap t_mmap.c #strace ./t_mmap open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3 fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即文件大小18 mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap文件fd=3 close(3) = 0 //close文件fd=3 munmap(0xb7867000, 18) = 0 //munmap,移除0xb7867000这里的内存映射

虽然没有看到read/write写文件操作,但此时文件/tmp/file_mmap中的内容已由www.perfgeeks.com改变成了WWW.PERFGEEKS.COM .这里mmap的addr是0(NULL),offset是18,并不是一个内存页的整数倍,即有4078bytes(4kb-18)内存空间被闲置浪费了。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h> #include <fcntl.h> #include <unistd.h>   int main(int argc, char *argv[]) { int fd, len; char *buf; char *fname = "/tmp/file_mmap"; ssize_t ret; struct stat sb;   fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { perror("open"); return 1; } if (fstat(fd, &sb) == -1) { perror("stat"); return 1; }   buf = malloc(sb.st_size); if (buf == NULL) { perror("malloc"); return 1; } ret = read(fd, buf, sb.st_size); for (len = 0; len < sb.st_size; ++len) { buf[len] = toupper(buf[len]); /*putchar(buf[len]);*/ }   lseek(fd, 0, SEEK_SET); ret = write(fd, buf, sb.st_size); if (ret == -1) { perror("error"); return 1; }   if (close(fd) == -1) { perror("close"); return 1; } free(buf); return 0; } #gcc –o t_rw t_rw.c open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3 fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 其中文件大小18 brk(0) = 0x9845000 //brk, 返回当前中断点 brk(0x9866000) = 0x9866000 //malloc分配内存,堆当前最后地址 read(3, "www.perfgeeks.com\n", 18) = 18 //read lseek(3, 0, SEEK_SET) = 0 //lseek write(3, "WWW.PERFGEEKS.COM\n", 18) = 18 //write close(3) = 0 //close

这里通过read()读取文件内容,toupper()后,调用write()写回文件。因为文件太小,体现不出read()/write()的缺点:频繁访问大文件,需要多个lseek()来确定位置。每次编辑read()/write(),在物理内存中的双份数据。当然,不可以忽略创建与维护mmap()数据结构的成本。需要注意:并没有具体测试mmap vs read/write,即不能一语断言谁孰谁劣,具体应用场景具体评测分析。你只是要记住:mmap内存映射文件之后,操作内存即是操作文件,可以省去不少系统内核调用(lseek, read, write)。

mmap() vs malloc()

使用strace调试的时候,通常可以看到通过mmap()创建匿名内存映射的身影。比如启用dl(‘apc.so’)的时候,就可以看到如下语句。
mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 //30M

通常使用mmap()进行匿名内存映射,以此来获取内存,满足一些特别需求。所谓匿名内存映射,是指mmap()的时候,设置了一个特殊的标志MAP_ANONYMOUS,且fd可以忽略(-1)。某些操作系统(像FreeBSD),不支持标志MAP_ANONYMOUS,可以映射至设备文件/dev/zero来实现匿名内存映射。使用mmap()分配内存的好处是页面已经填满了0,而malloc()分配内存后,并没有初始化,需要通过memset()初始化这块内存。另外,malloc()分配内存的时候,可能调用brk(),也可能调用mmap2()。即分配一块小型内存(小于或等于128kb),malloc()会调用brk()调高断点,分配的内存在堆区域,当分配一块大型内存(大于128kb),malloc()会调用mmap2()分配一块内存,与堆无关,在堆之外。同样的,free()内存映射方式分配的内存之后,内存马上会被系统收回,free()堆中的一块内存,并不会马上被系统回收,glibc会保留它以供下一次malloc()使用。

这里演示一下malloc()使用brk()和mmap2()。

/*
* file:t_malloc.c
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main(int argc, char *argv) { char *brk_mm, *mmap_mm;   printf("-----------------------\n"); brk_mm = (char *)malloc(100); memset(brk_mm, '\0', 100); mmap_mm = (char *)malloc(500 * 1024); memset(mmap_mm, '\0', 500*1024); free(brk_mm); free(mmap_mm); printf("-----------------------\n");   return 1; }   #gcc –o t_malloc t_malloc.c #strace ./t_malloc write(1, "-----------------------\n", 24-----------------------) = 24 brk(0) = 0x85ee000 brk(0x860f000) = 0x860f000 //malloc(100) mmap2(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7702000 //malloc(5kb) munmap(0xb7702000, 516096) = 0 //free(), 5kb write(1, "-----------------------\n", 24-----------------------) = 24

通过malloc()分别分配100bytes和5kb的内存,可以看出其实分别调用了brk()和mmap2(),相应的free()也是不回收内存和通过munmap()系统回收内存。

mmap()共享内存,进程通信

内存映射mmap()的另一个外常见的用法是,进程通信。相较于管道、消息队列方式而言,这种通过内存映射的方式效率明显更高,它不需要任务数据拷贝。这里,我们通过一个例子来说明mmap()在进程通信方面的应用。我们编写二个程序,分别是master和slave,slave根据master不同指令进行不同的操作。Master与slave就是通过映射同一个普通文件进行通信的。

/*
 *@file master.c
 */
root@liaowq:/data/tmp# cat master.c 
#include <stdio.h>
#include <time.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>   void listen();   int main(int argc, char *argv[]) { listen(); return 0; }   void listen() { int fd; char *buf; char *fname = "/tmp/shm_command";   char command; time_t now;   fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { perror("open"); exit(1); } buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { perror("mmap"); exit(1); } if (close(fd) == -1) { perror("close"); exit(1); }   *buf = '0'; sleep(2); for (;;) { if (*buf == '1' || *buf == '3' || *buf == '5' || *buf == '7') { if (*buf > '1') printf("%ld\tgood job [%c]\n", (long)time(&now), *buf); (*buf)++; } if (*buf == '9') { break; } sleep(1); }   if (munmap(buf, 4096) == -1) { perror("munmap"); exit(1); } }   /* *@file slave.c */ #include <stdio.h> #include <time.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>   void ready(unsigned int t); void job_hello(); void job_smile(); void job_bye(); char get_command(char *buf); void wait();   int main(int argc, char *argv[]) { wait(); return 0; }   void ready(unsigned int t) { sleep(t); }   /* command 2 */ void job_hello() { time_t now; printf("%ld\thello world\n", (long)time(&now)); }   /* command 4 */ void job_simle() { time_t now; printf("%ld\t^_^\n", (long)time(&now)); }   /* command 6 */ void job_bye() { time_t now; printf("%ld\t|<--\n", (long)time(&now)); }   char get_command(char *buf) { char *p; if (buf != NULL) { p = buf; } else { return '0'; } return *p; }   void wait() { int fd; char *buf; char *fname = "/tmp/shm_command";   char command; time_t now;   fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); if (fd == -1) { perror("open"); exit(1); } buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { perror("mmap"); exit(1); } if (close(fd) == -1) { perror("close"); exit(1); }   for (;;) { command = get_command(buf); /*printf("%c\n", command);*/ switch(command) { case '0': printf("%ld\tslave is ready...\n", (long)time(&now)); ready(3); *buf = '1'; break; case '2': job_hello(); *buf = '3'; break; case '4': job_simle(); *buf = '5'; break; case '6': job_bye(); *buf = '7'; break; default: break; } if (*buf == '8') { *buf = '9'; if (munmap(buf, 4096) == -1) { perror("munmap"); exit(1); } return; } sleep(1); } if (munmap(buf, 4096) == -1) { perror("munmap"); exit(1); } }

执行master与slave,输出如下
root@liaowq:/data/tmp# echo “0″ > /tmp/shm_command
root@liaowq:/data/tmp# ./master
1320939445 good job [3]
1320939446 good job [5]
1320939447 good job [7]
root@liaowq:/data/tmp# ./slave
1320939440 slave is ready…
1320939444 hello world
1320939445 ^_^
1320939446 |<--

master向slave发出job指令2,4,6。slave收到指令后,执行相关逻辑操作,完成后告诉master,master知道slave完成工作后,打印good job并且发送一下job指令。master与slave通信,是通过mmap()共享内存实现的。

总结

1、 Linux采用了投机取巧的分配策略,用到时,才分配物理内存。也就是说进程调用brk()或mmap()时,只是占用了虚拟地址空间,并没有真正占用物理内存。这也正是free –m中used并不意味着消耗的全都是物理内存。
2、 mmap()通过指定标志(flag) MAP_ANONYMOUS来表明该映射是匿名内存映射,此时可以忽略fd,可将它设置为-1。如果不支持MAP_ANONYMOUS标志的类unix系统,可以映射至特殊设备文件/dev/zero实现匿名内存映射。
3、 调用mmap()时就决定了映射大小,不能再增加。换句话说,映射不能改变文件的大小。反过来,由文件被映射部分,而不是由文件大小来决定进程可访问内存空间范围(映射时,指定offset最好是内存页面大小的整数倍)。
4、通常使用mmap()的三种情况.提高I/O效率、匿名内存映射、共享内存进程通信。

相关链接
1.高性能网络编程
2.内存管理内幕
3.C语言指针与内存泄漏
4.read系统调用剖析
5. linux环境进程间通信:共享内存
6. <<Linux系统编程>> <<unix网络编程2>>













本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5691579.html,如需转载请自行联系原作者


相关文章
|
机器人 C++
ROS问题汇总以及vsode配置问题
ROS问题汇总以及vsode配置问题
ROS问题汇总以及vsode配置问题
|
12月前
UE5使用Dash插件实现程序化地形场景制作
本文介绍了如何在Unreal Engine 5中使用Dash插件来实现程序化地形场景制作,包括插件激活、基本使用、资产导入路径和练习成果展示。
351 0
UE5使用Dash插件实现程序化地形场景制作
|
10月前
|
机器学习/深度学习 前端开发 JavaScript
前端小白也能学会的高大上技巧:如何让你的网页支持语音控制?
【10月更文挑战第31天】你是否曾梦想过只需动动嘴皮子就能操控网页?现在,这个梦想触手可及。即使你是前端小白,也能轻松学会让网页支持语音控制的高大上技巧。本文将介绍语音控制的基本概念、实现方法和具体示例,带你走进语音控制的奇妙世界。通过Web Speech API,你只需掌握基本的HTML、CSS和JavaScript知识,就能实现语音识别和控制功能。快来尝试吧!
1047 4
|
Ubuntu 虚拟化
Ubuntu安装VMtools实现与主机之间复制粘贴
一、安装 VMware Tools 右键点击你创建的系统,然后出现菜单下滑找到安装 VMware Tools(T) 这个点击安装; 右键点击你创建的系统,然后出现菜单下滑找到设置; 然后弹出虚拟机设置-->点击选项-->客户机隔离-->启用复制粘贴; 二、Ubuntu命令 这些命令假设你的Ubuntu系统已连接到互联网并且已配置为使用apt软件包管理器; 如果你的网络环境有限,你可能需要提前配置好网络或更换软件源; Ubuntu上安装和配置VMtools以实现与主机之间的文本复制粘贴功能; 1、打开终端(Terminal)。 2、运行以下命令以卸载旧版本的open-vm-tools: su
2548 1
|
机器学习/深度学习 算法 大数据
Vision Transformer 必读系列之图像分类综述(三): MLP、ConvMixer 和架构分析(下)
在 Vision Transformer 大行其道碾压万物的同时,也有人在尝试非注意力的 Transformer 架构(如果没有注意力模块,那还能称为 Transformer 吗)。这是一个好的现象,总有人要去开拓新方向。相比 Attention-based 结构,MLP-based 顾名思义就是不需要注意力了,将 Transformer 内部的注意力计算模块简单替换为 MLP 全连接结构,也可以达到同样性能。典型代表是 MLP-Mixer 和后续的 ResMLP。
1339 0
Vision Transformer 必读系列之图像分类综述(三): MLP、ConvMixer 和架构分析(下)
|
SQL 弹性计算 运维
EDS 微服务治理
EDS 微服务治理
1069 0
EDS 微服务治理
|
存储 Java 关系型数据库
客户端、服务器、数据库之间的时区转换
做国外的项目经常会遇到时区转换的问题,这里简单针对遇到的时区问题做个记录,也希望对大家有所帮助,少走弯路。(本文设计开发语言为java)
客户端、服务器、数据库之间的时区转换
|
机器学习/深度学习 JSON 自然语言处理
[信息抽取]基于ERNIE3.0的多对多信息抽取算法:属性关系抽取
本项目讲解了基于ERNIE信息抽取技术,对属性和关系的抽取涉及多对多抽取,主要是使用可ERNIEKIT组件,整体效果非常不错,当然追求小样本学习的可以参考之前UIE项目或者去官网看看paddlenlp最新的更新,对训练和部署进行了提速。
|
存储 负载均衡 Java
Nifi 架构 | 学习笔记
快速学习 Nifi 架构
389 0
Nifi 架构  |  学习笔记
|
存储 SQL XML
iOS小技能:常用的数据存储方式
iOS小技能:常用的数据存储方式
679 0
iOS小技能:常用的数据存储方式