Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

共享内存与内存映射

复习

  • 文件怎样落到磁盘:数据块、元数据和索引节点的基本关系
  • 文件分配与空闲空间:数据块怎样找到、占用和回收
  • 缓冲与页面缓存:内存怎样减少缓慢的设备访问

TL;DR

  • 内存映射(mmap):把一个文件映射进进程的地址空间
  • 之后读写这块内存,就等于读写文件,不必再调用 read/write
  • 多个进程映射同一个文件,就实现了共享内存
  • 它把“文件访问”和“共享页面”统一到了同一套机制上

正文

  走到这里,我们已经有了两个世界:一边是文件(字节流),一边是内存(页)。这一章,把两者打通。

把文件“贴”到内存里

  平时的文件读写是 read / write:程序把数据在内存和文件之间搬来搬去。可如果程序只是想在文件的某一段上反复读写,每次都调系统调用就很啰嗦。

  内存映射(memory mapping,通常叫 mmap)换了个思路:直接把文件的某一段,映射到进程地址空间里的一块区域。之后:

  • 程序像访问普通内存一样读写这块区域
  • 背后,读写会落到对应的文件页上
文件的一段 ──映射──> 进程地址空间的某片区域
程序直接读写内存 == 读写文件

  省去了显式的 read/write,代码更简洁,数据也不必在内核与用户之间来回搬。

顺手实现共享内存

  更妙的是:如果多个进程都把同一个文件映射进来,那么它们看到的,就是同一批物理页。

  于是,一个进程往这块内存里写,另一个进程马上就能读到——这就是共享内存。前面讲进程间通信时,我们说过共享内存最快,但需要自己同步;mmap 正是实现它的一种常见方式。

进程 A ─┐
        ├── 映射同一文件 ──> 同一批物理页
进程 B ─┘

和页面缓存的配合

  别忘了上一章的页面缓存。mmap 读文件时,数据其实也是通过页面缓存来的:

  • 访问映射区域,缺页时把文件页读进页面缓存
  • 映射区域指过去,多个进程共享同一份缓存页
  • 写入时页变“脏”,由系统择机写回文件

  于是,文件访问、共享内存、页面缓存,三件事被统一到同一套“页”的机制里。这种“用一个模型同时解决多个问题”的简洁,正是好设计的标志。当然,它也继承了延迟写的风险:映射区改了、还没写回文件时断电,改动同样可能丢失。

思考题

  mmap 让“读写文件”变成了“读写内存”,看起来方便很多。为什么它不能完全取代 read/write?在读写远小于文件大小、或者顺序流式读写时,哪种更合适?

小结

知识点

  • mmap 把文件映射进地址空间
  • 读写映射区等于读写文件
  • 多进程映射同一文件即共享内存
  • 与页面缓存配合,统一了文件访问与共享页面

参考资料

  1. Wikipedia(zh):内存映射文件:mmap
  2. Wikipedia(zh):共享内存:shared memory

思考题答案(仅供参考)

  因为 mmap 要建立映射、处理缺页,本身有开销;对于“只读一小段就完事”的访问,反而比一次 read 更重。而在大文件上做顺序流式读写时,read/write 配合预读往往更顺手,也不必占着地址空间。mmap 的优势场景,是“反复随机访问文件的一部分”,或者“多个进程要共享同一份数据”。一句话:工具各有适用的场合,mmap 不是万能替代。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪