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 ", 18) = 18 //read lseek(3, 0, SEEK_SET) = 0 //lseek write(3, "WWW.PERFGEEKS.COM ", 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("----------------------- "); brk_mm = (char *)malloc(100); memset(brk_mm, '