前言
我们首先来看一幅图
Linux内存管理的最底层是buddy内存管理方案,也就是我们常说的伙伴算法,管理伙伴算法我们不做详诉,有兴趣的可以自行百度,我们这里只要知道buddy内存池中只能分配2^n个page的内存,比如1,2,4,8……个pages,然而正常使用的时候不会碰巧就需要1,2,4,8……个pages。所以基于buddy内存池,还需要有上一级的内存管理系统,内核里面采用的是slab,slub,slob;而用户空间也有自己的内存管理方案,比如基于glibc的malloc/free内存管理方案。
关于glibc的malloc内存管理方案本文先不做详诉,我们大概了解以下几点,以便对本文的话题有个更加宏观的认识:
- 如果glibc的内存池中没有内存了,那么malloc就会通过mmap或者brk向buddy申请内存。
- free调用不会立即将内存还给buddy,而是会还给glibc的内存池。
- 如果glibc的内存池中还有足够的内存,那么malloc直接从glibc内存池中分配内存,不会像buddy申请内存。
以上引出了今天的话题mmap,关于mmap的行为总结下来就是一下几点:
- mmap的时候只会创建一个vma,此vma也有可能跟之前的vma合并成一个vma,并不会真正的分配物理内存。
- 当读mmap出来的内存的时候,会发生缺页异常,并将读所涉及到内存都映射到同一个物理页,称为zero page,zero page只有读权限,没有写权限。
- 当写mmap出来的内存的时候,会在此发生缺页异常,并将所涉及到的内存映射到真实的物理页,并且这些物理页有写权限。
本文的目的就是从行为以及代码两个方面来印证以上描述。
mmap行为验证
以下代码用于mmap行为验证
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#define MMAP_SIZE 0x4000
int main(char *argv[], int argc)
{
int i;
int tmp;
void *addr;
char *p;
printf("before mmap\n");
getchar();
addr = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
-1, 0);
printf("after mmap\n");
getchar();
p = (char *)addr;
for(i=0; i< MMAP_SIZE;i++)
tmp = p[i];
printf("after read\n");
getchar();
for(i=0; i<MMAP_SIZE; i++)
p[i] = 0x1;
printf("after write\n");
getchar();
return 0;
}
- mmap之前的page fault中断以及内存分配情况
执行test程序,打印出before mmap后,通过pidof 找到测试程序的pid为2313,并获取获得如下关键信息:
# ps -o maj_flt -o min_flt -p 2313
MAJFL MINFL
0 74
# pmap 2313
0000000000400000 4K r-x-- test
0000000000600000 4K r---- test
0000000000601000 4K rw--- test
00000000014e7000 132K rw--- [ anon ]
00007efc3f0c1000 1792K r-x-- libc-2.23.so
00007efc3f281000 2048K ----- libc-2.23.so
00007efc3f481000 16K r---- libc-2.23.so
00007efc3f485000 8K rw--- libc-2.23.so
00007efc3f487000 16K rw--- [ anon ]
00007efc3f48b000 152K r-x-- ld-2.23.so
00007efc3f696000 12K rw--- [ anon ]
00007efc3f6b0000 4K r----