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

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

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


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

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

相关文章

[react] React组件的构造函数是必须的吗?

[react] React组件的构造函数是必须的吗&#xff1f; 构造函数并不是必须的,对于无状态组件&#xff0c;内部没有维护自己的state&#xff0c;只接收外部传入的props 是不需要声明构造函数的 个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#x…

为什么dos下的com文件都要org 0100h呢?为什么系统启动时要org 07c00h呢

这是因为 .com 载入内存后的起始偏址就是100h. 前面的100h字节是该程序的PSP 部分. 所以, 为了程序中对地址引用的正确, 必需加上org 100h语句。----------------------------------------------------------------------------------------------------------------…

Qt中的TableWidget初始化表头、行高、选中、自动扩展和接受修改

一、需求 设置QT中的TableWidget样式&#xff0c;初始化表头&#xff0c;行高和颜色&#xff0c;行选中&#xff0c;是否修改&#xff0c;是否自动扩展宽度等。 二、代码 void MainWindow::TimerListInit() {QStringList InfHeader;int columnNum;columnNum 7;ui->table…

JTAG、SWD接口定义

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请注明。 博客已转到 http://blog.csdn.net/upc_xbt https://blog.csdn.net/u014124220/article/details/50829713Jlink仿真器接口仿真器端口连接目标板备注1. VCCMCU电源VCCVCC2. VCCMCU电源VCCVCC3. TRSTTRSTTest ReS…

[leedcode 215] Kth Largest Element in an Array

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element. For example,Given [3,2,1,5,6,4] and k 2, return 5. Note: You may assume k is always valid, 1 ≤ k ≤ arrays lengt…

Ubuntu 修改默认的PDF打开方式

Ubuntu自带了可以打开PDF的文档编辑器&#xff0c;如GIMP和文档查看器&#xff0c;但这些自带的文档编辑器并不是很好用&#xff0c;很可能会产生乱码&#xff0c;以至于不方便…… 由于在WIN下对foxit的产品感觉很满意&#xff0c;并且知道它也为linux做了一个PDF查看器&#…

[react] React组件的构造函数有什么作用?

[react] React组件的构造函数有什么作用&#xff1f; 在react的新的写法中&#xff0c;每一个组件都是一个类&#xff0c;这个很符合es6的语法规范&#xff0c;在es6中要想创建一个对象&#xff0c;就要调用相应的构造函数, react的组件渲染有两种情况&#xff0c;第一种情况是…

高级Linux程序设计第五章:进程间通信

From: http://www.cnblogs.com/forfuture1978/archive/2010/04/29/1723417.html 五种进程间通信的方式&#xff1a; 共享内存(shared memory)&#xff1a;其允许多个进程通过读写同一块内存地址来相互通信。 内存映射(Mapped memory)&#xff1a;其和共享内存相似&#xff0c;…

Qt 给应用程序添加图标

一、需求 给应用程序.exe添加图标。 二、代码 1、资源文件中添加进去main.ico 32X32pt 2、xxxx.pro文件中 添加RC_ICONS main.ico

#pragma pack(push,1)与#pragma pack(1)的区别

这是给编译器用的参数设置&#xff0c;有关结构体字节对齐方式设置&#xff0c; #pragma pack是指定数据在内存中的对齐方式。#pragma pack (n) 作用&#xff1a;C编译器将按照n个字节对齐。#pragma pack () 作用&#xff1a;取消自定义字节对齐方式…

Drainage Ditches - poj 1273(网络流模板)

题意&#xff1a;1是源点&#xff0c;m是汇点&#xff0c;求出来最大流量&#xff0c;没什么好说的就是练习最大流的模板题 ************************************************************** 先用Edmonds-Karp的算法做一下试试吧重边贡献了 1W&#xff0c;要加上所有的重边才算…

sawmill全方位日志分析大师

Sawmill 是一套崭新的集中式(中文)日志报表系统&#xff0c;除了提供收集设备或服务的日志&#xff0c;整合、分析成有效报表外&#xff0c;并能降低企业成本与信息管理人员减少学习报表时间&#xff0c;且能于全球各地上网即可获得IT信息。 集中式 &#xff1a; 集中整合各式设…

[react] React中在哪捕获错误?

[react] React中在哪捕获错误&#xff1f; 在react 15 极其以前的版本中,组件内的UI异常将中断组件内部状态&#xff0c;导致下一次渲染时触发隐藏异常。React并未提供友好的异常捕获和处理方式&#xff0c;一旦发生异常&#xff0c;应用将不能很好的运行。而React 16版本有所…

VC6.0 控件Radio Button的使用

From: http://www.cppblog.com/Lee7/archive/2007/09/13/32152.html 使用方法: 1.建立一个基于对话框的用用程序,在其中加入三个Radio Button,ID分别为: IDC_RADIO1,IDC_RADIO2,IDC_RADIO3 2.控件的初始化: 在对话框类的OnInitDialog中加入代码: …

Qt 实现QT控件中的QLabel显示图片并自适应显示

一、需求 实现QT控件中的QLabel显示图片&#xff0c;并自适应显示。 二、代码 QImage Image; Image.load(":/image/image/logo.jpg"); QPixmap pixmap QPixmap::fromImage(Image); int with ui->label_logo->width(); int height ui->label_logo->…

linux RTC 驱动模型分析

linux RTC 驱动模型分析RTC(real time clock)实时时钟&#xff0c;主要作用是给Linux系统提供时间。RTC因为是电池供电的&#xff0c;所以掉电后时间不丢失。Linux内核把RTC用作“离线”的时间与日期维护器。当Linux内核启动时&#xff0c;它从RTC中读取时间与日期&#xff0c;…

Install Docker Mac OS X

检查 Mac OS version 要求必须是 OS X 10.6 Snow Leopard or newer to run Boot2Docker安装 Boot2Docker 列表内容下载地址&#xff1a;https://github.com/boot2docker/osx-installer/releases/download/v1.7.0/Boot2Docker-1.7.0.pkg 下载后点击安装&#xff0c;就是按照提示…

linq to sql报错,

以上是由于我把关联表中的string类型写成int类型所导致的&#xff0c;记一下&#xff0c;备用。转载于:https://www.cnblogs.com/server126/archive/2011/05/25/2057416.html

[react] React怎样引入svg的文件?

[react] React怎样引入svg的文件&#xff1f; import React from react; import logo from ./logo.png; // Tell Webpack this JS file uses this imagefunction Header() {// Import result is the URL of your imagereturn <img src{logo} alt"Logo" />; } …

[VC6] RadioBox使用入门

基于对话框的应用程序&#xff0c;界面如下&#xff1a; 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 具体请参考代码&#xff1a; 关键代码&#xff1a; // del2Dlg.cpp : implementation file //#include "stdaf…