31

浅谈mmap

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU4ODgyMDI0Mg%3D%3D&%3Bmid=2247486982&%3Bidx=1&%3Bsn=21e4078621a2c29b33c98141b6c5c760
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

女主宣言

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

本篇文章转载自360技术

PS:丰富的一线技术、多元化的表现形式,尽在“ 3 60云计算 ”,点关注哦!

相关背景知识

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

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

eMBVnmr.jpg!web

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

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

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

UfaMBzv.jpg!web

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. 用个示意图来说明一下:

AbmEnuF.jpg!web

  • 用作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作磁盘文件映射时的特别说明

  • mmap映射的虚拟地址长度(即mmap的第二个参数)需要对齐到物理页大小,在32位系统上通常是4K, 这一特点会导致一些有趣的事情发生,我们来看一下:假如一个文件的大小是5000Byte, 刚好比4K大一些,我们用mmap来从文件开始的位置来映射它,mmap的第二个参数给5000, 因为需要页面对齐,实现映射的虚拟地址长度将是两个4k,即8192, 8192 - 5000 = 3192的部分用0填充,也是可以被访问到;

  • 如果用mmap映射某个文件时,这个文件大小为0, 不会分配任何的物理内存,也不能作任何的读写访问;当向文件中写入数据后,通过mmap返回的虚拟地址可以访问这部分文件内容;

mmap与内存换入换出

  • 由前面的介绍我们知道mmap不管是映射磁盘文件,还是作匿名映射,最终都会分配物理内存页,因此这个物理内存页在内存紧张时就有备换出的可能,当然mmap提供了MAP_LOCKED,可以锁定内存不被换出,我们不考虑这种情况;

  • 如果使用mmap映射的是磁盘文件,其存在物理页的内容会被清空,pte将记录这种情况,再次需要访问时,会重新读取磁盘文件,缓存在page cache中;

  • 如果使用mmap作匿名映射,没有相关联的磁盘文件(或者使用MAP_PRIVATE方式映射磁盘文件),发生内存换出时,将被交换到swap中,swap实际上也对应着磁盘块,最终也是写在磁盘上;

360云计算

由360云平台团队打造的技术分享公众号,内容涉及 数据库、大数据、微服务、容器、AIOps、IoT 等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

bqeuaey.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK