以前总是对mmap的作用很迷惑, 特别是在用户态和内核态概念引入之后就更加疑惑, 为什么mmap可以做到零拷贝?本篇主要是梳理虚拟内存, 内存, 磁盘以及mmap之间的关系

什么是mmap

先来看维基百科的解释

mmap是POSIX兼容的Unix系统调用, 它将文件或设备映射到内存中.
这是一种内存映射文件I / O的方法, 它实现了按需分页, 因为文件内容不直接从磁盘读取, 并且最初根本不使用物理RAM

翻译得好听一点就是:

  1. 是一个系统调用
  2. 可以将内存和文件映射
  3. 不直接从磁盘读取文件内容, 最初也不占用内存

要理解这几条概念首先得知道虚拟内存和分页技术

虚拟内存

具体虚拟内存为什么存在, 解决了哪些问题, 有什么优点可以单开一章来讲, 这里只讲一些便于理解mmap的东西

理论上讲, 虚拟内存就是存储在磁盘上的连续N个字节, 而物理内存则是这些内容的缓存

virtual-memory

如果以1个字节为单位, 那么每次从磁盘载入进物理内存的效率会非常低, 于是虚拟内存以页为单位存储, 一个页通常是4kb

那操作系统是如何知道哪些内容被缓存了, 哪些还在磁盘上呢?

在物理内存里会给每个进程维护一张页表, 当进程使用虚拟地址进行访问内存时, 查页表可知这个页是否已经被加载到物理内存里了

page-table

内存映射

虚拟内存里面的内容究竟是什么呢?

在虚拟内存初始化的时候, 有两种途径

  1. 映射存在磁盘上的普通文件(如可执行的二进制文件)
  2. 映射匿名文件(加载进物理内存时初始化全为0)

虚拟内存初始化的过程, 就叫做内存映射

通过理解以上两点, 可以解释mmap的概念了

  1. mmap就是做内存映射的一个系统调用
  2. 虚拟内存是存在磁盘上的, 所以内存映射后初始不占据物理内存, 按需从磁盘载入进物理内存

怎么用mmap

操作系统中内存映射使用的场景

  1. 加载可执行程序

    如果不做内存映射的话, 需要先把可执行程序全部读入物理内存中, 再进行命令执行

    这显然是十分低效且不可行的做法, 如果可执行程序比物理内存还大, 运行都无法成功

    采用内存映射, 首先先把可执行程序代码段的内容映射到虚拟内存中, cpu执行指令时发现有缺页错误, 再从磁盘中载入到物理内存里来

  2. 共享数据

    很多程序都会使用同样的系统共享库, 这些共享库不需要复制多份, 通过内存映射的方式可以使用同一份磁盘上的内容

    share-object

私有对象可以采用写时复制技术防止原有数据被修改

用户级别内存映射的使用

mmap就是内核提供的, 可以像操作系统一样做内存映射的一个系统调用

void *mmap(void *start, int len, int prot, int flags, int fd, int offset);

start: 指定开始的虚拟地址, 0 表示任意挑选一个地址

len fd offset: 从fd指定的文件offet位置开始映射len个字节

prot: PROT_READ, PROT_WRITE, PROT_EXEC…

flags: MAP_ANON, MAP_PRIVATE, MAP_SHARED…

返回值: 一个虚拟地址指针, 可能不是start指定的(kernel决定真正映射的地址)

mmap

  1. 读取大文件

    read读取文件流程: read系统调用->内核将文件内容读取到内核缓冲区->内核将缓冲区内容拷贝到用户空间

    mmap读取文件流程: mmap系统调用->内核进行内存映射->用户使用数据->发生缺页异常->从磁盘载入数据

  2. 进程间通讯

    进程之间的数据本身是无法互通的(虚拟地址空间), 而通过mmap内存映射的方式, 将同一个文件映射到虚拟内存中, 那么对这个文件的修改都是跨进程可见的

转载申请

知识共享许可协议

本文知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接