2016-10-05
mmap与虚拟内存(vm)

虚拟内存

虚拟内存是为每个进程抽象出来的一层内存空间,与物理内存地址存在映射关系。

这层映射关系,是以页(page)为单位进行的。 页大小一般是4kb, *nix上可以通过sysconf PAGESIZE 查看。

mmap提供了一种直接在虚拟内存与物理磁盘之间建立映射关系的方式。它的运作原理是,当要访问该页的内容,而对应磁盘页(Virtual Page)的数据尚未加载到物理内存时,内核会产生页缺失(page faiure)的事件,该事件触发page failure handler去将磁盘页的数据加载到物理内存中。

所以,每一个进程维护有一个page table. 每个page table里的条目(PTE)记录虚拟内存里一页的信息。该信息包括页的状态,读取权限,物理页地址。

每一页有三种状态:

  1. Unallocated. 此页未与物理内存建立映射关系,不占据内存大小.

  2. Cached. 该页已建立映射关系,并且该页数据已在物理内存中. 此时PTE里的地址为物理内存地址。

  3. Uncached. 该页已建立映射关系,但数据还未缓存到物理内存中(还在磁盘上). 此时PTE里的地址为磁盘页地址。

mmap

 #include <sys/mman.h>

 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr 是目标虚拟内存地址,一般NULL(让系统分配).

prot 可以是 PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE.

flags 主要是 MAP_SHARED 和 MAP_PRIVATE(当被写入时,触发cow).

执行mmap后,只是在虚拟内存页与磁盘页建立了映射关系,即修改了page_table. 并没有将磁盘页的内容缓存到物理内存中,要通过后续对该页的访问,触发页缺失事件。

实验:

test_mmap.c:

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>

int
main(int argc, char *argv[]) {
  int f = 0;
  char* p = NULL;

  if (argc < 2) {
    printf("Usage %s FILENAME", argv[0]);
    return 1;
  }
  if ((f = open(argv[1], O_RDONLY)) < 0) {
    perror("open failed.");
    return 1;
  }

  if ((p = mmap(0, 4096, PROT_READ, MAP_SHARED, f, 0)) == MAP_FAILED) {
    perror("mmap failed.");
    return 1;
  }

  char _ = p[0];  // 访问该页,触发 page failure 事件
  return 0;
}
   > gcc test_mmap.c -o tmmap
   > /usr/bin/time -v tmmap /usr/bin/sort | grep page
   	Major (requiring I/O) page faults: 1
   	Minor (reclaiming a frame) page faults: 62

产生了一次 Major page fault(即页缺失)。

注意:要用不常用的文件测试(即没被内存缓存的文件)。上面的实验若再做一次,会显示major page fault为0, 因为刚刚的测试已经把/usr/bin/sort缓存到内存里了.(同理,用dd现场创建一个文件也不行)


lazyrobot.me