文章目录
  1. 1. 是什么
  2. 2. 为什么
    1. 2.1. mmap原理
    2. 2.2. mmap与文件操作的区别
    3. 2.3. mmap优势
  3. 3. 怎么用
  4. 4. 参考

在Native开发中遇到mmap, 于是有了这篇文章.

目录:

  • 是什么
  • 为什么
  • 怎么用

是什么

mmap: Memory-map. 可翻译成内存映射, 这是一种文件I/O操作, 它将文件(linux世界中, 设备也是文件)映射到内存中, 用户可以像操作内存一样操作文件.
mmap以及相关系统调用包含在Unix的BSD(Berkeley Software Distribution)4.2版本的设计中, 然而最先实现mmap接口的是Sun公司, BSD开发者向Sun要代码没要到, 最后BSD上的实现是基于虚拟内存系统Mach中的版本.

为什么

mmap原理

mmap将文件映射到连续的内存区域, 使进程的地址空间中某一段与被映射的文件(或共享内存)的地址一一对应.
无论内核态还是用户态都只是进程所处的一种状态, 进程拥有自己的虚拟地址空间, 但是物理区块是只有一份的, 只是这一份物理区块在不同进程中的地址不同而已. 基于这个事实, 我们只要让自己进程的一段虚拟地址, 指向我们想要载入的物理文件所在的位置, 那么对这段虚拟地址的操作, 就可以直接反映在物理文件上, 看起来就像我们已经载入(从内核空间拷贝内容到用户空间内存)了文件一样.
具体来说, 可以分为三个阶段:

  1. 在用户空间调用mmap函数, 分配一块连续的满足要求的虚拟地址空间, 并将代表该空间的数据结构初始化后插入进程的虚拟地址区域链表中.
  2. 根据mmap参数, 找到待映射文件的文件描述符, 标记为已打开, 调用内核mmap函数, 定位到文件磁盘物理地址, 建立页表实现文件地址和虚拟地址区域的映射关系. 至此地址映射完成, 但尚未拷贝数据到内存.
  3. 进程访问虚拟地址空间中该文件所在地址, 引发缺页异常使文件拷贝到主存, 然后就可以对这片区域进行读写操作了.
    虽然原理很简单, 但实际实现的时候有许多问题需要注意, 比如多个进程访问同一个文件的时候, 如果有进程写文件怎么处理. 根据参数的不同, 可能有不同的处理方式. 例如共享内存MAP_SHARED方式下, 写操作会立即对其他进程可见, 但实际将脏数据写回到文件却是经过一定时间或在msync系统调用之后. 而如果是MAP_PRIVATE方式, 则任何写操作都会引起第二次内存拷贝, 文件数据被拷贝到进程内存空间, 对其他进程不可见, 并且也不会写回文件.

mmap与文件操作的区别

linux还有一套文件操作的系统调用, 即open read write, mmap与他们区别在哪儿呢?
最大的区别就在于, 普通文件读写操作, 需要借助处于内核空间的页缓存作为中转, 将数据载入主存, 而mmap却是逻辑映射到物理文件, 直接载入数据到主存.
前者总是向页缓存索取数据, 内核通过查找页缓存来判断能否立即返回数据, 在未缓存的情况下, 先从物理磁盘拷贝数据到页缓存, 再从页缓存拷贝数据到用户空间中, 写回时也是同样, 需要先将用户空间的文件缓存拷贝到内核空间, 再由内核写回到磁盘, 都需要经过两次拷贝.
后者则是在发现需要载入数据时, 通过事先建立的页表映射关系, 直接由内核将数据拷贝到用户空间, 一次拷贝.
因此, mmap理论上来说效率更高.

mmap优势

  1. 不使用页缓存机制, 减少一次拷贝.
  2. 用户空间和内核空间对同一片区域的修改都可以直接反映在对方映射区域内, 提高了交互效率.
  3. 为共享内存和IPC通信提供一种方式.

怎么用

mmap尽管有些优势, 但并不是万能药, 例如如果你要处理的文件大到很难为之分配一个连续的虚拟地址空间, 或者你想要访问的地址的偏移量不是物理页大小(page_size)的整数倍(此时你需要自己解决偏移量问题), 甚至有些情况下, read/write是与目标文件交互的唯一方式, 见SOF: when-should-use-mmap.
mmap可以用于实现共享内存, 如上问所述.
Binder的底层使用mmap, 所以Binder才能做到只拷贝一次数据.
在使用mmap时有些要点需要注意:

  1. 由于mmap的映射是基于页大小的, 所以它一定会映射整数个页面, 哪怕你用不到那么多. 比如5000 byte数据需要2*4k(2个页)空间, 但5000~8191读写不报错但不会反映到文件中, 读为0, 写丢失.
  2. 无论你声明要使用多大空间, mmap都只会映射刚好够用的页面数量, 所以访问已映射的页面之外的地址, 会报错. 比如原始文件大小5000 byte, 声明要映射15000 byte, 那么仅前5000 byte可正常读写, 5000~8191同上, 不报错但读为0写丢失. 8192~14999无法读写, 报SIGBUS错误. 15000及以上无法读写, 报SIGSEGV错误. 如果确实需要写5000以上的位置, 那么需要在写之前增加文件大小.

参考

文章目录
  1. 1. 是什么
  2. 2. 为什么
    1. 2.1. mmap原理
    2. 2.2. mmap与文件操作的区别
    3. 2.3. mmap优势
  3. 3. 怎么用
  4. 4. 参考