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>>
linux mmap 内存映射【转】的更多相关文章
- linux mmap 内存映射
mmap() vs read()/write()/lseek() 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2().系统调用mmap()可以将某文件映射至内存(进程空间), ...
- Linux下内存映射文件的用法简介
由于项目需要,所以学习了一下Linux下内存映射文件的用法,在这里共享一下自己的收获,希望大家提出宝贵意见,进行交流. 简介: 内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区 ...
- mmap内存映射
http://blog.csdn.net/kongdefei5000/article/details/70183119 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...
- 【转】Python之mmap内存映射模块(大文本处理)说明
[转]Python之mmap内存映射模块(大文本处理)说明 背景: 通常在UNIX下面处理文本文件的方法是sed.awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力 ...
- sendfile“零拷贝”和mmap内存映射
在学习sendfille之前,我们先来了解一下浏览器访问页面时,后台服务器的大致工作流程. 下图是从用户访问某个页面到页面的显示这几秒钟的时间当中,在后台的整个工作过程. 如上图,黑色箭头所示的过程, ...
- [转载]Linux驱动mmap内存映射
原文地址:https://www.cnblogs.com/wanghuaijun/p/7624564.html mmap在linux哪里? 什么是mmap? 上图说了,mmap是操作这些设备的一种方法 ...
- Linux驱动mmap内存映射
mmap在linux哪里? 什么是mmap? 上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED).LCD控制器.磁盘控制器,实际上就是往设备的物理地址读写数据. ...
- linux kernel内存映射实例分析
作者:JHJ(jianghuijun211@gmail.com)日期:2012/08/24 欢迎转载,请注明出处 引子 现在android智能手机市场异常火热,硬件升级非常迅猛,arm cortex ...
- 共享内存之——mmap内存映射
共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件 (特殊情况下还可以采用匿名映射)机制实现,也可以通过sy ...
随机推荐
- 构造方法后面加上了:base
今天看公司软件的代码碰到一个奇怪的方法 ,寻早了各种方法后终于明白了,在构造方法后面加上了:base(message),该类如下: public NONEDIException(string mess ...
- Java 找到数组中两个元素相加等于指定数的所有组合
思路1:可以用hash表来存储数组中的元素,这样我们取得一个数后,去判断sum - val 在不在数组中,如果在数组中,则找到了一对二元组,它们的和为sum,该算法的缺点就是需要用到一个hash表,增 ...
- Maven 命令操作项目
1.创建一个多模块的Java项目 shift+鼠标右键 创建项目命令: 旧版: mvn archetype:create -DgroupId=com.qhong -DartifactId=MavenP ...
- RouterOS 软路由开启SSH服务器
RouterOS软路由可以支持多种服务,例如SSH.FTP.Telnet.www等等 图形介面操作 命令操作 [admin@MikroTik] > ip service print ...
- News: Visual Studio Code support debugging Linux Apps
http://arstechnica.com/information-technology/2015/11/visual-studio-now-supports-debugging-linux-app ...
- set方法内存分析
// // main.m // 04-set方法的内存管理分析 // // Created by apple on 14-3-17. // // #import <Foundation/F ...
- java io读书笔记(2)什么是stream
什么是stream?stream就是一个长度不确定的有序字节序列. Input streams move bytes of data into a Java program from some gen ...
- lvs负载均衡的搭建
lvs负载均衡的搭建 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在部署环境前,我们需要了解一下一些协议 一.什么是arp 地址解析协议,即ARP(Addr ...
- 超炫的3D翻转模板
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- spring纯java注解式开发(一)
习惯了用XML文件来配置spring,现在开始尝试使用纯java代码来配置spring. 其实,spring的纯java配置,简单来说就是将bean标签的内容通过注解转换成bean对象的过程,没什么神 ...