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>>

linux mmap 内存映射相关推荐

  1. Linux mmap内存映射

    将最近网上搜索的资料统一整理下,方便后续复查. 一.什么是mmap mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一 ...

  2. linux mmap 内存映射 mmap() vs read()/write()/lseek()

    From: http://www.perfgeeks.com/?p=723 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2().系统调用mmap()可以将某文件映射至内存(进 ...

  3. linux mmap 内存映射【转】

    转自:http://blog.csdn.net/xyyangkun/article/details/7830313 [-] mmap vs readwritelseek mmap vs malloc ...

  4. linux内存管理函数mmap和brk,brk() 和 mmap() 内存映射

    参考博文:http://www.cnblogs.com/huxiao-tee/p/4660352.html x86平台下linux进程虚拟地址空间分布(2.6.7以前版本) mmap区域与栈区域相对增 ...

  5. mmap 内存映射详解

    目录 mmap基础概念 mmap内存映射原理 mmap示例代码 mmap和常规文件操作的区别 mmap使用的细节 前言 原文对 mmap 内存映射已经表述的很清楚了,我只是在原文的基础上,附上了 mm ...

  6. mmap内存映射、system V共享内存和Posix共享内存

    linux内核支持多种共享内存方式,如mmap内存映射,Posix共享内存,以system V共享内存.当内核空间和用户空间存在大量数据交互时,共享内存映射就成了这种情况下的不二选择.它能够最大限度的 ...

  7. mmap内存映射原理

    mmap概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系.  特点:实现这样的映射关系后,进程就可以 ...

  8. mmap(内存映射)和shm(共享存储)

     mmap(内存映射)和shm(共享存储): 1.mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射. 而对于shm而言,shm每个进程最终会映射到同一块物理内存. shm保 ...

  9. linux mmap内存文件映射

    一.传统文件访问 unix访问文件的传统方法使用open打开他们,如果有多个进程访问一个文件,则每一个进程在再记得地址空间都包含有该文件的副本,这不必要地浪费了存储空间.下面说明了两个进程同时读一个文 ...

最新文章

  1. 使用OpenCV开发机器视觉项目
  2. 多种特征提取算法比较汇总
  3. Spring Boot工程支持HTTP和HTTPS,HTTP重定向HTTPS
  4. SAP SD-销售模式-寄售(客户寄售)
  5. 请在贵网站的根目录下部署一个文件_使用 github pages, 快速部署你的静态网页
  6. 1g等于多少mb计算机网络,手机流量1G等于多少MB ,如何产生,怎么节省
  7. Python笔记-UiSelector文本定位方式(3种定位并点击)
  8. 计算机网络实验报告西南科技大学,西南科技大学计算机网络-实验二.docx
  9. (转)MyBatis框架的学习(五)——一对一关联映射和一对多关联映射
  10. 为什么火车上的网速都超级慢,并且信号极差?
  11. windows下pyhton_vitrualenv虚拟环境pycharm如何创建django项目
  12. 什么是starup?
  13. zuc算法代码详解_ZUC算法原理及实现过程
  14. cpi计算机性能指标,将CPU时间=(CPI指令总数).ppt
  15. openwrt 挂载硬盘NFS共享,非SMB共享
  16. 一个新进前端小白实习僧的初次探索
  17. Unity基本认识——走进Unity
  18. 一、C++基础入门之 Windows下C/C++开发环境配置
  19. 云计算-华为虚拟化平台FusionCompute
  20. [Mac/Windows] Affinity Photo | 正品序列号 | 专业的图片编辑工具

热门文章

  1. floodlight java_floodlight学习系列(1)——在Eclipse中安装运行floodlight
  2. cmake卸载 ubuntu_ubuntu卸载/更新Cmake
  3. java泛型机制_Java泛型机制必要性及原理解析
  4. tensorflow gpu python3.5_Win10+Anaconda3下tensorflow-gpu环境配置
  5. linux ftp dns,Ubuntu下FTP与DNS服务器搭建教程PDF
  6. g++ linux 编译开栈_Linux下编写C++服务器(配置C++编译调试环境)
  7. laravel打印sql语句_SQL语句为什么慢?索引为什么失效?
  8. 从零开始学习docker(八)多台机器通信
  9. 当Sklearn遇上Plotly,会擦出怎样的火花?
  10. 实现自己的轻量级http调用工具