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二种方法实现。

  1. /*
  2. * @file: t_mmap.c
  3. */
  4. #include <stdio.h>
  5. #include <ctype.h>
  6. #include <sys/mman.h> /*mmap munmap*/
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. #include <fcntl.h>
  10. #include <unistd.h>
  11.  
  12. int main(int argc, char *argv[])
  13. {
  14. int fd;
  15. char *buf;
  16. off_t len;
  17. struct stat sb;
  18. char *fname = "/tmp/file_mmap";
  19.  
  20. fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  21. if (fd == -1)
  22. {
  23. perror("open");
  24. return 1;
  25. }
  26. if (fstat(fd, &sb) == -1)
  27. {
  28. perror("fstat");
  29. return 1;
  30. }
  31.  
  32. buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  33. if (buf == MAP_FAILED)
  34. {
  35. perror("mmap");
  36. return 1;
  37. }
  38.  
  39. if (close(fd) == -1)
  40. {
  41. perror("close");
  42. return 1;
  43. }
  44.  
  45. for (len = 0; len < sb.st_size; ++len)
  46. {
  47. buf[len] = toupper(buf[len]);
  48. /*putchar(buf[len]);*/
  49. }
  50.  
  51. if (munmap(buf, sb.st_size) == -1)
  52. {
  53. perror("munmap");
  54. return 1;
  55. }
  56. return 0;
  57. }
  58. #gcc –o t_mmap t_mmap.c
  59. #strace ./t_mmap
  60. open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3
  61. fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即文件大小18
  62. mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap文件fd=3
  63. close(3) = 0 //close文件fd=3
  64. 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)内存空间被闲置浪费了。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <ctype.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>
  8. #include <unistd.h>
  9.  
  10. int main(int argc, char *argv[])
  11. {
  12. int fd, len;
  13. char *buf;
  14. char *fname = "/tmp/file_mmap";
  15. ssize_t ret;
  16. struct stat sb;
  17.  
  18. fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
  19. if (fd == -1)
  20. {
  21. perror("open");
  22. return 1;
  23. }
  24. if (fstat(fd, &sb) == -1)
  25. {
  26. perror("stat");
  27. return 1;
  28. }
  29.  
  30. buf = malloc(sb.st_size);
  31. if (buf == NULL)
  32. {
  33. perror("malloc");
  34. return 1;
  35. }
  36. ret = read(fd, buf, sb.st_size);
  37. for (len = 0; len < sb.st_size; ++len)
  38. {
  39. buf[len] = toupper(buf[len]);
  40. /*putchar(buf[len]);*/
  41. }
  42.  
  43. lseek(fd, 0, SEEK_SET);
  44. ret = write(fd, buf, sb.st_size);
  45. if (ret == -1)
  46. {
  47. perror("error");
  48. return 1;
  49. }
  50.  
  51. if (close(fd) == -1)
  52. {
  53. perror("close");
  54. return 1;
  55. }
  56. free(buf);
  57. return 0;
  58. }
  59. #gcc –o t_rw t_rw.c
  60. open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3
  61. fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 其中文件大小18
  62. brk(0) = 0x9845000 //brk, 返回当前中断点
  63. brk(0x9866000) = 0x9866000 //malloc分配内存,堆当前最后地址
  64. read(3, "www.perfgeeks.com\n", 18) = 18 //read
  65. lseek(3, 0, SEEK_SET) = 0 //lseek
  66. write(3, "WWW.PERFGEEKS.COM\n", 18) = 18 //write
  67. 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()。

  1. /*
  2. * file:t_malloc.c
  3. */
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <stdlib.h>
  7.  
  8. int main(int argc, char *argv)
  9. {
  10. char *brk_mm, *mmap_mm;
  11.  
  12. printf("-----------------------\n");
  13. brk_mm = (char *)malloc(100);
  14. memset(brk_mm, '\0', 100);
  15. mmap_mm = (char *)malloc(500 * 1024);
  16. memset(mmap_mm, '\0', 500*1024);
  17. free(brk_mm);
  18. free(mmap_mm);
  19. printf("-----------------------\n");
  20.  
  21. return 1;
  22. }
  23.  
  24. #gcc –o t_malloc t_malloc.c
  25. #strace ./t_malloc
  26. write(1, "-----------------------\n", 24-----------------------) = 24
  27. brk(0) = 0x85ee000
  28. brk(0x860f000) = 0x860f000 //malloc(100)
  29. mmap2(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7702000 //malloc(5kb)
  30. munmap(0xb7702000, 516096) = 0 //free(), 5kb
  31. write(1, "-----------------------\n", 24-----------------------) = 24

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

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

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

  1. /*
  2. *@file master.c
  3. */
  4. root@liaowq:/data/tmp# cat master.c
  5. #include <stdio.h>
  6. #include <time.h>
  7. #include <stdlib.h>
  8. #include <sys/mman.h>
  9. #include <sys/types.h>
  10. #include <sys/stat.h>
  11. #include <fcntl.h>
  12. #include <unistd.h>
  13.  
  14. void listen();
  15.  
  16. int main(int argc, char *argv[])
  17. {
  18. listen();
  19. return 0;
  20. }
  21.  
  22. void listen()
  23. {
  24. int fd;
  25. char *buf;
  26. char *fname = "/tmp/shm_command";
  27.  
  28. char command;
  29. time_t now;
  30.  
  31. fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
  32. if (fd == -1)
  33. {
  34. perror("open");
  35. exit(1);
  36. }
  37. buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  38. if (buf == MAP_FAILED)
  39. {
  40. perror("mmap");
  41. exit(1);
  42. }
  43. if (close(fd) == -1)
  44. {
  45. perror("close");
  46. exit(1);
  47. }
  48.  
  49. *buf = '0';
  50. sleep(2);
  51. for (;;)
  52. {
  53. if (*buf == '1' || *buf == '3' || *buf == '5' || *buf == '7')
  54. {
  55. if (*buf > '1')
  56. printf("%ld\tgood job [%c]\n", (long)time(&now), *buf);
  57. (*buf)++;
  58. }
  59. if (*buf == '9')
  60. {
  61. break;
  62. }
  63. sleep(1);
  64. }
  65.  
  66. if (munmap(buf, 4096) == -1)
  67. {
  68. perror("munmap");
  69. exit(1);
  70. }
  71. }
  72.  
  73. /*
  74. *@file slave.c
  75. */
  76. #include <stdio.h>
  77. #include <time.h>
  78. #include <stdlib.h>
  79. #include <sys/mman.h>
  80. #include <sys/types.h>
  81. #include <sys/stat.h>
  82. #include <fcntl.h>
  83. #include <unistd.h>
  84.  
  85. void ready(unsigned int t);
  86. void job_hello();
  87. void job_smile();
  88. void job_bye();
  89. char get_command(char *buf);
  90. void wait();
  91.  
  92. int main(int argc, char *argv[])
  93. {
  94. wait();
  95. return 0;
  96. }
  97.  
  98. void ready(unsigned int t)
  99. {
  100. sleep(t);
  101. }
  102.  
  103. /* command 2 */
  104. void job_hello()
  105. {
  106. time_t now;
  107. printf("%ld\thello world\n", (long)time(&now));
  108. }
  109.  
  110. /* command 4 */
  111. void job_simle()
  112. {
  113. time_t now;
  114. printf("%ld\t^_^\n", (long)time(&now));
  115. }
  116.  
  117. /* command 6 */
  118. void job_bye()
  119. {
  120. time_t now;
  121. printf("%ld\t|<--\n", (long)time(&now));
  122. }
  123.  
  124. char get_command(char *buf)
  125. {
  126. char *p;
  127. if (buf != NULL)
  128. {
  129. p = buf;
  130. }
  131. else
  132. {
  133. return '0';
  134. }
  135. return *p;
  136. }
  137.  
  138. void wait()
  139. {
  140. int fd;
  141. char *buf;
  142. char *fname = "/tmp/shm_command";
  143.  
  144. char command;
  145. time_t now;
  146.  
  147. fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
  148. if (fd == -1)
  149. {
  150. perror("open");
  151. exit(1);
  152. }
  153. buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  154. if (buf == MAP_FAILED)
  155. {
  156. perror("mmap");
  157. exit(1);
  158. }
  159. if (close(fd) == -1)
  160. {
  161. perror("close");
  162. exit(1);
  163. }
  164.  
  165. for (;;)
  166. {
  167. command = get_command(buf);
  168. /*printf("%c\n", command);*/
  169. switch(command)
  170. {
  171. case '0':
  172. printf("%ld\tslave is ready...\n", (long)time(&now));
  173. ready(3);
  174. *buf = '1';
  175. break;
  176. case '2':
  177. job_hello();
  178. *buf = '3';
  179. break;
  180. case '4':
  181. job_simle();
  182. *buf = '5';
  183. break;
  184. case '6':
  185. job_bye();
  186. *buf = '7';
  187. break;
  188. default:
  189. break;
  190. }
  191. if (*buf == '8')
  192. {
  193. *buf = '9';
  194. if (munmap(buf, 4096) == -1)
  195. {
  196. perror("munmap");
  197. exit(1);
  198. }
  199. return;
  200. }
  201. sleep(1);
  202. }
  203. if (munmap(buf, 4096) == -1)
  204. {
  205. perror("munmap");
  206. exit(1);
  207. }
  208. }

执行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() vs read()/write()/lseek() 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2().系统调用mmap()可以将某文件映射至内存(进程空间), ...

  2. Linux下内存映射文件的用法简介

    由于项目需要,所以学习了一下Linux下内存映射文件的用法,在这里共享一下自己的收获,希望大家提出宝贵意见,进行交流. 简介: 内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区 ...

  3. mmap内存映射

    http://blog.csdn.net/kongdefei5000/article/details/70183119 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...

  4. 【转】Python之mmap内存映射模块(大文本处理)说明

    [转]Python之mmap内存映射模块(大文本处理)说明 背景: 通常在UNIX下面处理文本文件的方法是sed.awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力 ...

  5. sendfile“零拷贝”和mmap内存映射

    在学习sendfille之前,我们先来了解一下浏览器访问页面时,后台服务器的大致工作流程. 下图是从用户访问某个页面到页面的显示这几秒钟的时间当中,在后台的整个工作过程. 如上图,黑色箭头所示的过程, ...

  6. [转载]Linux驱动mmap内存映射

    原文地址:https://www.cnblogs.com/wanghuaijun/p/7624564.html mmap在linux哪里? 什么是mmap? 上图说了,mmap是操作这些设备的一种方法 ...

  7. Linux驱动mmap内存映射

    mmap在linux哪里? 什么是mmap? 上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED).LCD控制器.磁盘控制器,实际上就是往设备的物理地址读写数据. ...

  8. linux kernel内存映射实例分析

    作者:JHJ(jianghuijun211@gmail.com)日期:2012/08/24 欢迎转载,请注明出处 引子 现在android智能手机市场异常火热,硬件升级非常迅猛,arm cortex ...

  9. 共享内存之——mmap内存映射

    共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件 (特殊情况下还可以采用匿名映射)机制实现,也可以通过sy ...

随机推荐

  1. 构造方法后面加上了:base

    今天看公司软件的代码碰到一个奇怪的方法 ,寻早了各种方法后终于明白了,在构造方法后面加上了:base(message),该类如下: public NONEDIException(string mess ...

  2. Java 找到数组中两个元素相加等于指定数的所有组合

    思路1:可以用hash表来存储数组中的元素,这样我们取得一个数后,去判断sum - val 在不在数组中,如果在数组中,则找到了一对二元组,它们的和为sum,该算法的缺点就是需要用到一个hash表,增 ...

  3. Maven 命令操作项目

    1.创建一个多模块的Java项目 shift+鼠标右键 创建项目命令: 旧版: mvn archetype:create -DgroupId=com.qhong -DartifactId=MavenP ...

  4. RouterOS 软路由开启SSH服务器

    RouterOS软路由可以支持多种服务,例如SSH.FTP.Telnet.www等等 图形介面操作 命令操作 [admin@MikroTik] > ip service print        ...

  5. News: Visual Studio Code support debugging Linux Apps

    http://arstechnica.com/information-technology/2015/11/visual-studio-now-supports-debugging-linux-app ...

  6. set方法内存分析

    // //  main.m //  04-set方法的内存管理分析 // //  Created by apple on 14-3-17. // // #import <Foundation/F ...

  7. java io读书笔记(2)什么是stream

    什么是stream?stream就是一个长度不确定的有序字节序列. Input streams move bytes of data into a Java program from some gen ...

  8. lvs负载均衡的搭建

       lvs负载均衡的搭建 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.         在部署环境前,我们需要了解一下一些协议 一.什么是arp 地址解析协议,即ARP(Addr ...

  9. 超炫的3D翻转模板

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. spring纯java注解式开发(一)

    习惯了用XML文件来配置spring,现在开始尝试使用纯java代码来配置spring. 其实,spring的纯java配置,简单来说就是将bean标签的内容通过注解转换成bean对象的过程,没什么神 ...