浅谈 mmap

阅读数:59 2019 年 11 月 14 日 14:07

浅谈mmap

作者说:最近在工作中遇到一个 mmap 使用相关的问题,造成了一定的困惑,于是花了些时间补了下 mmap 的功课,在这里分享给大家,错误和不足之处大家多指教。

相关背景知识

  • 说到 mmap 的使用,我们首先要了解一下进程的虚拟进程地址空间的概念。Linux 上为了作进程隔离,每个进程都运行在自己的单独的虚拟进程空间,同时物理机上内存有限,每个进程使用虚拟内存地址来隔离又共享物理内存。我们平时在代码里获取的地址就是虚拟地址 ;

  • 放一张进程虚拟地址空间草图,网上也可以很容易找到更精美的

浅谈mmap

1. 我们在程序中申请内存的操作,实际上只是在进程地址空间相应部分申请了一段虚拟地址,当实际对这段虚拟地址进行读写操作时,才会分配真正的物理内存 ;

2. 通常 x86 Linux 采用段页式的内存管理模式,这块不具体展开,简单来说就是 CPU 访问的逻辑地址,然后经过分段机制转换成线性地址(你可以简单理解成等价于上面说的虚拟地址),再经过分页机制转换成物理地址,第一次访问的时候由于实现物理地址还没有分配,会产生缺页中断来分配物理地址,用它来填充对应的页表项 ;

3. 通过 read 系统调用来读取磁盘上的文件时,文件内容会先被读到内存的 page inode 部分,然后再从 page cache 中拷贝到应用层的读缓存 buffer 中 ; 对于打开的文件,内核都会在内存中维护一个 inode 结构体(对于同一个文件,即使被 open 多次,内核也仅维护这一个 inode),其有一个成员是 struct address_space *i_mapping , 它用来维护这个文件被读取的所有部分在内存中的缓存,其使用 xarray(全新封装了基数树的操作) 来存储这个物理页 (struct page), 如下图:

浅谈mmap浅谈mmap

mmap 简介

  • 先看原型:void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)

  • 功能:

1. 分配一块新的连续的进程虚拟地址段(对应内核中的结构体就是 vm_area_struct)并返回其起始地址,如果给定了第一个参数,就优先从这个地址开始分配进程虚拟地址 ;
2. 如果提供了 fd 文件句枘,则映射文件内容到进程虚拟地址 ;
3.mmap 的参数较多,其中 prot 和 flags 的可选项也比较多,具体大家可以使用 man 命令查看 ;

mmap 的几种典型应用

  • 不同进程 (可以是非父子进程) 间共享映射

1. 这种情况需要借助磁盘文件,实际上是共享这个磁盘文件,将这个磁盘文件映射到各自的进程虚拟地址空间,但是其虚拟地址空间分页转换后其页表项对应的物理内存是相同的 ;
2. 典型用法是需提供一个打开的文件句柄,使用 MAP_SHARED flag

int fd = open ("[filepath]", O_RDWR))
void *addr = mmap (NULL, [mmaping length], PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)

  • 非子进程间通讯

1. 父进程使用 fork 创建子进程,父子进程间可以使用 mmap 来通读 ;
2. 典型用法是无需提供打开的文件句柄, 使用 MAP_SHARED | MAP_ANONYMOUS flag,

复制代码
void *addr = mmap (NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  • 进程通过 mmap 来读写文件

1. 从上面 相关背景知识 一节可知使用 read 系统调用读文件时,数据需经过 磁盘拷贝到 page cache, page cache 再拷贝到应用层缓存 bufffer, 这两个数据拷贝 ;
2. 使用 mmap 时,磁盘数据也是先读到 page cache 中,然后会将 mmap 返回的虚拟地址最终对应的页项表内容设定为和前面的 page chache 相同的物理页, 这样一来就免去了第二次的数据拷贝 ;
3. 用个示意图来说明一下:

浅谈mmap浅谈mmap

  • 用作 glibc 中 malloc 申请内存

1. 通常我们都说是通过调用 malloc 来申请堆上内存,但实际上其内部实现使用了 brk 和 mmap 两种系统调用,当申请的内存大于 128K 时,使用 mmap
2. 典型用法是无需提供打开的文件句柄, 使用 MAP_PRIVATE | MAP_ANONYMOUS flag

复制代码
void *addr = mmap (NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  • mmap 的写时拷贝

1. 如果我们在调用 mmap 时提供一个打开的文件句两,但使用 MAP_PRIVATE 的 flags, 那这时对其的写操作并不能真正修改对应的磁盘文件,它会作写时拷贝,退化成匿名映射

## mmap 作磁盘文件映射时的特别说明

1.mmap 映射的虚拟地址长度 (即 mmap 的第二个参数) 需要对齐到物理页大小,在 32 位系统上通常是 4K, 这一特点会导致一些有趣的事情发生, 我们来看一下:假如一个文件的大小是 5000Byte, 刚好比 4K 大一些,我们用 mmap 来从文件开始的位置来映射它,mmap 的第二个参数给 5000, 因为需要页面对齐,实现映射的虚拟地址长度将是两个 4k, 即 8192, 8192 - 5000 = 3192 的部分用 0 填充,也是可以被访问到 ;
2. 如果用 mmap 映射某个文件时,这个文件大小为 0, 不会分配任何的物理内存,也不能作任何的读写访问;当向文件中写入数据后,通过 mmap 返回的虚拟地址可以访问这部分文件内容 ;

mmap 与内存换入换出

1. 由前面的介绍我们知道 mmap 不管是映射磁盘文件,还是作匿名映射,最终都会分配物理内存页,因此这个物理内存页在内存紧张时就有备换出的可能,当然 mmap 提供了 MAP_LOCKED,可以锁定内存不被换出,我们不考虑这种情况 ;
2. 如果使用 mmap 映射的是磁盘文件,其存在物理页的内容会被清空,pte 将记录这种情况,再次需要访问时,会重新读取磁盘文件,缓存在 page cache 中 ;
3. 如果使用 mmap 作匿名映射,没有相关联的磁盘文件(或者使用 MAP_PRIVATE 方式映射磁盘文件),发生内存换出时,将被交换到 swap 中,swap 实际上也对应着磁盘块,最终也是写在磁盘上 ;

本文转载自公众号 360 云计算(ID:hulktalk)。

原文链接:

https://mp.weixin.qq.com/s/h4LwSRAsDgRqOq3mLt_SCw

评论

发布