Linux 内存映射mmap原理介绍

众多内存数据库如MongoDB操作数据,就是把文件磁盘内容映射到内存中进行处理,为什么会提高效率? 很多人不解. 下面就深入分析内存文件映射.

所谓内存文件映射就是可以将文件映射到内存, 怎么映射到内存的?

咱们先引入一个概念:所谓虚拟进程地址空间是虚拟的,这并不是废话,每个进程都有自己的私有用户空间,这个空间对系统中的其他进程是不可见的,最高的1G内核空间则由所有进程以及内核所共享.另外,进程的"用户空间" 也叫 "地址空间" ,用户空间是独立的,每个进程都有属于自己的最大3G的用户空间,一个进程对其中某个地址的访问,与其它进程对同一地址的访问绝不冲突. 以前OS只有物理内存的概念.

知道虚拟地址空间, 其实操作系统调用mmap后,只是使用了自己的地址空间, 并未真正使用物理内存,只要等真正使用这块内存时才真正创建这块物理内存.

先看下mmap的原型:

SYNOPSIS       #include <sys/mman.h>       void *mmap(void *addr, size_t length, int prot, int flags,                  int fd, off_t offset);

addr:映射区的地址length:映射区的长度 ( 按block计算 4K)prot:映射区的内存保护标志(PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE(页不可以被访问))flags:指定映射对象的类型,映射选项和映射页是否可以共享fd:文件描述符offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,必须是分页大小的整数倍.

通过程序来分析内存使用.

#include <stdio.h>#include <sys/mman.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(int argc, char *argv[]){off_t len;struct stat st;char* cpFileName = "./1.rar";int iFd;iFd = open(cpFileName, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (-1 == iFd) {perror("open");return -1;}if (fstat(iFd, &st) == -1) {perror("fstat");return -1;}printf("FileSize=%dBytes\n", st.st_size);char* cpBuf;cpBuf = mmap(0, st.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, iFd, 0);if (MAP_FAILED == cpBuf) {perror("mmap");return -1;}printf("%s\n", "未使用物理内存!");getchar();int i;for (i = 0; i < st.st_size; i++) {cpBuf[i] = 'a';}printf("%s\n", "已使用物理内存!");getchar();if (close(iFd) == -1) {perror("close");return -1;}if (munmap(cpBuf, st.st_size) == -1) {perror("munmap");return -1;}return 0;}

运行程序得到:

~/MyCodes/CX-Codes/welcomeC/ >>./a.outFileSize=144611678Bytes未使用物理内存!

在还未使用物理内存的情况下 我们来查看下 进程的相关信息:

~/MyCodes/ >> ps aux | grep a.outUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND1000 1109 0.0 0.0 175316 744 pts/3 S+ 11:26 0:00 ./a.out

可以看到VSZ为 175316KB, 而RSS为744KB

可以再通过pmap -d 1109看下:

...00007f53f3591000 16 rw--- 0000000000000000 000:00000 [ anon ]00007fff6faf9000 136 rw--- 0000000000000000 000:00000 [ stack ]00007fff6fbdd000 4 r-x-- 0000000000000000 000:00000 [ anon ]ffffffffff600000 4 r-x-- 0000000000000000 000:00000 [ anon ]mapped: 175316K writeable/private: 332K shared: 141224K

我们继续运行这段程序按下回车.

~/MyCodes/CX-Codes/welcomeC/ >> ./a.outFileSize=144611678Bytes未使用物理内存!已使用物理内存!

这个时候程序显示已经使用物理内存了,我们来验证下.

~/MyCodes/ >>ps aux | grep a.outUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND1000      1109  0.0  7.0 175316 141972 pts/3   S+   11:26   0:00 ./a.out

这个时候RSS就变大了,就是实际占用的物理内存了.

知道mmap基本原理,我们来看看这段程序调用系统调用的过程.把这段程序的输出(printf)操作全部注释掉.

通过 strace ./a.out,得到

open("./1.rar", O_RDWR|O_CREAT, 0600) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=144611678, ...}) = 0mmap(NULL, 144611678, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7f296eebc000close(3) = 0munmap(0x7f296eebc000, 144611678) = 0exit_group(0) = ?

可以看出这里只有open->fstat->mmap->munmap过程,并没有write和read等系统调用.这就是mmap优势所在.如果经常写文件或者读大文件 普通程序会调用write和read来操作,这样效率相对于直接操作内存来说 效率是很低的.

另外通过malloc来分配大内存其实调用的是mmap,我们来看下面这段程序:

#include <stdio.h>#include <sys/mman.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(int argc, char *argv[]){printf("%s\n", "----------------Begin--------------------");char* cpBuf;cpBuf = malloc(10);cpBuf = malloc(10 * 1024 * 1024);printf("%s\n", "----------------End--------------------");return 0;}

通过strace,堆栈如下:

write(1, "----------------Begin-----------"..., 42----------------Begin--------------------) = 42brk(0) = 0xdba000brk(0xddb000) = 0xddb000brk(0) = 0xddb000mmap(NULL, 10489856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fae9edf5000write(1, "----------------End-------------"..., 40----------------End--------------------) = 40

可见在malloc(10)的时候调用的是brk, malloc(10 * 1024 * 1024)调用的是mmap;http://www.crazyshell.org/blog/?p=337志在山顶的人,不会贪念山腰的风景。

Linux 内存映射mmap原理介绍

相关文章:

你感兴趣的文章:

标签云: