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

#include

#include

#include

#include

#include

#include

#include

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]);

}

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

#include

#include

#include

#include

#include

#include

#include

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]);

}

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()。

#include

#include

#include

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就是通过映射同一个普通文件进行通信的。

root@liaowq:/data/tmp# cat master.c

#include

#include

#include

#include

#include

#include

#include

#include

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

}

}#include

#include

#include

#include

#include

#include

#include

#include

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

}

void job_hello()

{

time_t now;printf("%ld\thello world\n", (long)time(&now));

}

void job_simle()

{

time_t now;printf("%ld\t^_^\n", (long)time(&now));

}

void job_bye()

{

time_t now;printf("%ld\t|

}

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

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的作用是将硬盘文件的内容映射到内存中,采用闭链哈希建立的索引文件非常适合利用mmap的方式进行内存映射,利用mmap返回的地址指针就是索引文件在内存中的首地址,这样我们就可以放心大胆的访问这些内容了。

使用过mmap映射文件的同学会发现一个问题,search程序访问对应的内存映射时,处理query的时间会有latecny会陡升,究其原因是因为mmap只是建立了一个逻辑地址,linux的内存分配测试都是采用延迟分配的形式,也就是只有你真正去访问时采用分配物理内存页,并与逻辑地址建立映射,这也就是我们常说的缺页中断。

缺页中断分为两类,一种是内存缺页中断,这种的代表是malloc,利用malloc分配的内存只有在程序访问到得时候,内存才会分配;另外就是硬盘缺页中断,这种中断的代表就是mmap,利用mmap映射后的只是逻辑地址,当我们的程序访问时,内核会将硬盘中的文件内容读进物理内存页中,这里我们就会明白为什么mmap之后,访问内存中的数据延时会陡增。

出现问题解决问题,上述情况出现的原因本质上是mmap映射文件之后,实际并没有加载到内存中,要解决这个文件,需要我们进行索引的预加载,这里就会引出本文讲到的另一函数madvise,这个函数会传入一个地址指针,已经一个区间长度,madvise会向内核提供一个针对于于地址区间的I/O的建议,内核可能会采纳这个建议,会做一些预读的操作。例如MADV_SEQUENTIAL这个就表明顺序预读。

如果感觉这样还不给力,可以采用read操作,从mmap文件的首地址开始到最终位置,顺序的读取一遍,这样可以完全保证mmap后的数据全部load到内存中。

linux map内存在哪里分配,brk  mmap  madvise 内存分配以及共享内存相关推荐

  1. linux mmap实例_Linux下通过共享内存和mmap实现进程间通讯(含实例)

    前言 最近在学习GNU/Linux内核,看到mmap的时候书上说: mmap/munmap接口函数是用户最常用的两个系统调用接口,无论是在用户程序中分配内存.读写大文件.链接动态库文件,还是多进程间共 ...

  2. 【Linux篇】第十二篇——进程间通信(管道+system V共享内存)

    进程间通信介绍 概念 目的 本质 分类 管道 什么是管道 匿名管道 匿名管道的原理 pipe函数 匿名管道使用步骤 管道读写规则 管道的特点 管道的大小 命名管道 命名管道的原理 使用命令创建命名管道 ...

  3. mmap映射区和shm共享内存的区别总结

    linux中的两种共享内存.一种是我们的IPC通信System V版本的共享内存,另外的一种就是我们今天提到的存储映射I/O(mmap函数) 在说mmap之前我们先说一下普通的读写文件的原理,进程调用 ...

  4. Linux 下的进程间通信:管道、消息队列、共享文件、共享内存

    Table of Contents 无名管道 命名管道 消息队列 共享文件 示例 1. 生产者程序 示例 2. 消费者程序 共享内存 示例 3. memwriter 进程的源程序 示例 4. memr ...

  5. oracle共享内存设置spfile,IPC 资源、kernel.shmmax和Oracle 共享内存的调整

    ㈠ IPC 资源查看与释放 [root@david ~]# ipcs -m ------ Shared Memory Segments -------- key shmid owner perms b ...

  6. linux open 头文件_linux下通过共享内存和mmap实现进程间通讯

    前言 最近在学习GNU/Linux内核,看到mmap的时候书上说: mmap/munmap接口函数是用户最常用的两个系统调用接口,无论是在用户程序中分配内存.读写大文件.链接动态库文件,还是多进程间共 ...

  7. linux如何看分配固定共享内存段,Linux共享内存的查看和删除

    在使用共享内存的程序异常退出时,由于没有释放掉共享内存,在调试时会出现错误.您可以使用shell命令来查看与释放已经分配的共享内存,下面将详细说明如何进行查看和释放分配的共享内存的方法. 预备知识 L ...

  8. 【Linux 应用编程】进程管理 - 进程间通信IPC之共享内存 mmap

    IPC(InterProcess Communication,进程间通信)是进程中的重要概念.Linux 进程之间常用的通信方式有: 文件:简单,低效,需要代码控制同步 管道:使用简单,默认阻塞 匿名 ...

  9. Linux四种共享内存技术(附源码):SystemV、POSIX mmap、memfd_create、dma-buf

    <Linux 下的进程间通信:管道.消息队列.共享文件.共享内存> <[共享内存]基于共享内存的无锁消息队列设计> <File Sealing & memfd_c ...

最新文章

  1. 运维基础(4)流量监控工具篇
  2. 18行代码AC_Wet Shark and Bishops CodeForces - 621B(数学推导+映射)
  3. 国家高性能计算环境的虚拟数据空间运行支撑技术研究
  4. 回头看看的时候openeim001
  5. 车牌识别EasyPR(4)——字符识别MSER
  6. SpringCloud 从菜鸟到大牛之九 服务跟踪 spring CLoud sleuth + Zikpin
  7. Python各类图像库的图片读写方式总结
  8. liferay录入中文乱码问题
  9. 动态文本_(302期)【动态】|| 立足相同文本,描绘不同风景 ——工作室开展“同课异构”活动...
  10. ResponsibleChain(责任链模式)
  11. 如何监控 Tomcat?Zabbix 与 Cloud Insight 对比
  12. BZOJ1014 [JSOI2008]火星人
  13. Java学习资料(一)——Java书籍
  14. 2021年秋季Python程序设计相关课程教材推荐
  15. 一文读懂:完整的支付系统整体架构!
  16. Word文档docx的图标显示异常,doc的显示正常,但是可以用,解决办法
  17. 前端下载 “不支持打开该类型文件或文件已损坏“问题
  18. Java程序启动QQ(九)
  19. 【转】使用Eclipse和BlackBerry JDE开发黑莓应用程序
  20. 新手如何学习单片机?

热门文章

  1. 数组指针、函数指针与函数指针数组
  2. 2019-03-06【午夜游乐园玩法】
  3. 架构师如何教小学生写作文
  4. 如何计算环境遗传相关 | 育种中的基因与环境互作
  5. 产品经理懂点技术之:4K/8K
  6. java 自定义异常 好处,什么是自定义异常 自定义异常类有什么好处呢
  7. Android自定义控件onMeasure、onLayout介绍
  8. linux删除目录下文件|删除文件保持目录结构|各种删除方法总结
  9. 客户案例|AI助力财税行业低成本精准获客
  10. Winform制作简易串口通信助手的相关问题及解决方案