linux mmap 内存映射

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效率、匿名内存映射、共享内存进程通信。

mmap。的更多相关文章

  1. Python多进程(2)——mmap模块与mmap对象

    本文介绍Python mmap模块与mmap对象的用法. mmap 模块提供“内存映射的文件对象”,mmap 对象可以用在使用 plain string 的地方,mmap 对象和 plain stri ...

  2. mmap DMA【转】

    转自:http://blog.csdn.net/lihaoweiv/article/details/6275241 第 13 章  mmap 和 DMA 本章将深入探讨 Linux 内存管理部分,并强 ...

  3. 内存映射MMAP和DMA【转】

    转自:http://blog.csdn.net/zhoudengqing/article/details/41654293 版权声明:本文为博主原创文章,未经博主允许不得转载. 这一章介绍Linux内 ...

  4. 进程间通讯之mmap文件共享

    进程间通讯之mmap文件共享 引文: 个人名言:“同一条河里淹死两次的人,是傻子,淹死三次及三次以上的人是超人”.经历过上次悲催的面试,决定沉下心来,好好的补充一下基础知识点.本文是这一系列第一篇:进 ...

  5. 深入理解内存映射mmap

    内存映射mmap是Linux内核的一个重要机制,它和虚拟内存管理以及文件IO都有直接的关系,这篇细说一下mmap的一些要点. 修改(2015-11-12):Linux的虚拟内存管理是基于mmap来实现 ...

  6. mmap:速度快+整块操作

    mmap使得可以将设备内存映射到用户空间,从而使得用户程序获得访问硬件的能力,mmap的动作需要由内核中的驱动来实现.在使用mmap映射后,用户程序对给定范围的内存的读写就变成了对设备内存的读写,也就 ...

  7. Linux内存管理 (9)mmap(补充)

    之前写过一篇简单的介绍mmap()/munmap()的文章<Linux内存管理 (9)mmap>,比较单薄,这里详细的梳理一下. 从常用的使用者角度介绍两个函数的使用:然后重点是分析内核的 ...

  8. python mmap对象

    ----使用内存映射的原因 为了随机访问文件的内容,使用mmap将文件映射到内存中是一个高效和优雅的方法.例如,无需打开一个文件并执行大量的seek(),read(),write()调用,只需要简单的 ...

  9. 计算机底层知识拾遗(九)深入理解内存映射mmap

    内存映射mmap是Linux内核的一个重要机制,它和虚拟内存管理以及文件IO都有直接的关系,这篇细说一下mmap的一些要点. 修改(2015-11-12):Linux的虚拟内存管理是基于mmap来实现 ...

随机推荐

  1. Eclipse用法和技巧二十八:Eclipse插件Easy Explore的今世

    先说明一下easyexplore插件的功能,easyexplore是一个类似于 Windows Explorer的Eclipse插件,它可以帮助你在不退出Eclipse的环境下迅速浏览本地文件系统. ...

  2. Ubuntu 安装启动Tomcat

    首先下载ubuntu 的tar包 官网: http://tomcat.apache.org/download-80.cgi 安装启动 1 .下载对应的tar 2 .解压任意文件夹下,更改名字tomca ...

  3. 6个最佳的开源Python应用服务器

    6个最佳的开源Python应用服务器 首先,你知道什么是应用服务器吗?应用服务器通常被描述为是存在于服务器中心架构中间层的一个软件框架. AD: 首先,你知道什么是应用服务器吗?应用服务器通常被描述为 ...

  4. 基于visual Studio2013解决面试题之1401冒泡排序

     题目

  5. Swift - 使用CGBlendMode改变UIImage颜色

    类似于PS,Swift中也可对图片UIImage进行图层混合(blending),而且提供了相当丰富的混合模式(blendMode).本文先介绍使用其中的kCGBlendModeDestination ...

  6. accept系统调用内核实现

    用户态对accept的标准使用方法: if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_siz ...

  7. 使用JDBC获取能自动增加的主键

    本篇讲述如何使用JDBC获取能自动增加的主键的值.有时候我们在向数据库插入数据时希望能返回主键的值,而不是通过查询的方式.一般来说,在多表相互关联主键约束,也就是说别的表的外键约束是该表的主键,那么在 ...

  8. mfc控件与其对应的对象的关联方法

    对话框的控件与其对应类的对象相关联:(两种方法) (1)      通过CWnd::DoDataExchange函数进行关联: 用VC++6.0的MFC ClassWizard中的Member Var ...

  9. WinSock - 建立有连接的通信

    1.建立服务端(发送端) (1)声明成员变量 public: CSocket m_sockSend; (2)每隔一秒钟发送一次数据 2.建立客户端(接收端) (1)声明成员变量 public: CSo ...

  10. win32 sdk 列表视图控件绘制

    ////////////////////////////////////////////////////////////// LRESULT ListViewCustomDraw(HWND hwnd, ...