mmap

义项:内存映射系统调用
mmap

mmap是类Unix操作系统中的一种内存映射系统调用,用于把文件、设备或其他对象映射到调用进程的虚拟地址空间,使程序可以像访问内存一样用指针读写这些对象[1]。它也是匿名内存分配、进程间共享内存和动态链接库装载的底层机制之一,因省去内核缓冲与用户缓冲之间的多次数据拷贝,在大文件读写场景中常被采用[2]。该接口在POSIX标准中有明确规定,Linux、FreeBSD、macOS等系统都提供实现[3]。

目录
  1. 定义
  2. 原理
  3. 发展历程
  4. 应用
  5. 局限
  6. 参见

定义

mmap是操作系统内核提供的一个系统调用,作用是新建一段映射,把文件、设备或者一段不依附于文件的内存接到调用进程的虚拟地址空间上,并返回这段区域的首地址[1]。调用返回的是一个指针,此后程序访问这个指针指向的字节,就等于访问被映射对象中相应位置的字节[4]。与之配对的munmap用于撤销指定地址范围内的映射[5]。

在典型调用形式中,程序依次给出建议起始地址、长度、内存保护方式、标志位、文件描述符和文件内偏移量6个参数[6]。若只需一块可由程序自由使用的内存,可以不传文件,改用匿名映射,此时内容初始化为0。

mmap是POSIX标准定义的接口,其行为在POSIX.1-2001和POSIX.1-2008中被规定,并被SVr4、4.4BSD等系统采纳[3]。在支持mmap、msync和munmap的POSIX系统上,头文件unistd.h中会定义_POSIX_MAPPED_FILES,其值大于0,程序可据此判断该能力是否可用[7]。

原理

映射建立在虚拟内存和分页机制之上。mmap并不立即把文件内容读进物理内存,而是在进程的地址空间中登记一段映射关系;当程序真正访问其中某个地址而对应的物理页尚不存在时,处理器触发缺页异常,内核再把文件中相应部分调入物理内存并在页表中登记[8]。这种按需装入的方式属于请求分页,起始时并不占用物理内存,磁盘读取发生在具体位置被访问之后[2]。

调用参数决定了映射的可访问性。保护参数可以是PROT_NONE,也可以是PROT_READ、PROT_WRITE、PROT_EXEC的组合,分别表示可读、可写、可执行[6]。标志参数中必须且只能指定MAP_SHARED或MAP_PRIVATE之一:前者对映射区的写入会反映到其他映射同一对象的进程并最终写回文件,后者建立写时复制的私有映射,修改不会影响原文件[4]。私有映射在发生首次写入时才复制被改动的页面,因此只有被修改的页被复制[9]。

偏移量通常必须是页大小的整数倍,文件按页大小的整数倍进行映射;文件长度不是页大小整数倍时,结尾不足一页的部分在映射时补零,对该区域的写入也不会写回文件[7]。建立映射后可以立即关闭文件描述符而不影响映射的有效性,映射要一直保留到被munmap撤销或被新映射取代为止[4]。MAP_SHARED映射上的写入是否落到文件,可以用msync显式提交来控制时机[1]。

如果希望内核在建立映射时就预先填好页表、减少后续缺页阻塞,可使用MAP_POPULATE标志;对文件映射而言,这还会触发文件预读[5]。与之相关的MAP_HUGETLB标志(自Linux 2.6.32起)允许用大页分配映射,以减轻页表与地址转换开销[10]。

发展历程

文件与进程内存直接打通的设计并非源于Unix,而最早出现在TOPS-20操作系统上,其1969年的PMAP系统调用允许程序把文件的一部分直接当作主存使用[11]。mmap的接口形态则借鉴了TENEX/TOPS-20的PMAP,后者又把地址空间表示为页面到文件的映射[12]。

在Unix谱系中,mmap的最早实现被认为出现于1981年由约翰·赖泽完成的32V R3版本,该版本具备完整的请求分页并采用统一缓冲缓存,且已使用现代的6参数接口[13]。4.2BSD的系统手册中已经写入了mmap的接口描述,但该版本与4.3BSD都未真正实现[2]。

使mmap真正可用的工作随后在Sun公司完成。SunOS 4.0重写了虚拟内存系统,成为第一个让mmap按手册所描述方式工作的发行版,动态链接库也由此得以借助映射机制装载[14]。4.3BSD-Reno则改为基于Mach的虚拟内存系统提供实现,而4.4BSD采纳了Mach的虚拟内存系统[2]。此前Berkeley曾向Sun索取该实现未果[2]。

进入Linux时代后接口持续扩展:匿名映射与MAP_SHARED的组合自Linux 2.4起得到支持[15]。随后加入的还有MAP_STACK(2.6.27起)、MAP_UNINITIALIZED(2.6.33起)等标志,用于栈分配和嵌入式场景下的性能优化[5]。较新的MAP_SHARED_VALIDATE和MAP_SYNC(均自Linux 4.15起)则为持久内存等场景提供了更严格的语义检查[1]。

应用

最直接的用途是文件输入输出。把文件映射进地址空间后,程序用指针和下标即可读写文件内容,不需要seek、不需要为read和write准备额外缓冲区,顺序访问与随机访问的差别也不再明显[16]。多个进程可以各自映射同一个文件从而共享数据,无需复制,这使得它适合用于共享库等场景[16]。

匿名映射常被用作内存分配器的底层手段,一些malloc实现就用它来处理较大的分配请求[2]。共享的匿名映射也是进程间通信的一种方式,多个进程映射同一段不留存于文件的区域即可交换数据[2]。Oracle的文档指出,映射/dev/zero可以拿到一块由系统选定地址、内容为零的虚拟内存,用作暂存区[4]。

在数据库领域,一些系统选择用mmap代替自建的缓冲池。专栏文章列举称,MonetDB把各列存为内存映射文件,SQLite提供使用mmap替代默认读写调用的选项,LMDB则完全依赖mmap[8]。PostgreSQL也支持把动态共享内存的类型设为mmap,即由进程自行打开文件并直接映射。此外,帧缓冲设备等硬件在通过映射方式访问时更便于直接对显示地址做随机操作[4]。

局限

页面对齐会带来空间浪费。映射总是按页大小对齐,多数系统页大小为4 KiB,因此一个5 KiB的文件会占用8 KiB,多出的部分成为空隙[11]。映射的粒度是页,小于一页的精细控制无法实现。

错误处理方式比较苛刻。映射区被访问时若出现越界或权限不符,内核不会通过返回值报错,而是直接向进程发送信号:对只读区域写入会收到SIGSEGV,访问超出被映射文件末尾的部分会收到SIGBUS[3]。若映射期间另一个进程把文件截短,访问已不存在的区域同样触发SIGBUS[4]。

MAP_FIXED标志要求内核必须使用指定地址,它会覆盖该范围内的原有映射,在可移植性和安全性上都存在问题,只有在地址范围已由其他映射预留时才可安全使用,否则多线程程序可能因此破坏自身的地址空间[10]。此外,指定的地址即使可用,也可能因超出CPU的虚拟地址空间而失败[7]。

在数据量超过物理内存的负载下,mmap的性能也有上限。相关实验指出其瓶颈包括页表争用、仅由单个内核线程执行页面回收,以及回收页面时需要跨处理器中断来清除其他核心的地址转换缓存,后者的开销尤其难以通过应用层规避[8]。该实验还发现,在基于闪存的高带宽环境中,随着缓存被填满并开始回收页面,mmap的随机读取吞吐会明显下降,而直接I/O基线则保持稳定[8]。因此,即使是采用mmap的数据库,也普遍需要配合madvise给出访问模式提示,并以msync保证数据落盘[8]。

参见

  • munmap —— 撤销mmap所建立映射的配套系统调用。

  • 虚拟内存 —— mmap赖以工作的地址空间与分页机制。

  • 请求分页 —— mmap按需把文件内容装入物理内存所依赖的机制。

  • 写时复制 —— MAP_PRIVATE私有映射的实现基础。

  • 共享内存 —— 借助mmap映射同一对象实现进程间数据共享。

  • POSIX —— 规定mmap接口行为的标准。

参考资料

  1. mmap64.3.fr [引用日期2026-10-14]
  2. Mmap | EPFL Graph Search - Skip to main content [引用日期2026-10-14]
  3. mmap.2.en [引用日期2026-10-14]
  4. Memory Management Interfaces [引用日期2026-10-14]
  5. munmap.2.en [引用日期2026-10-14]
  6. man [引用日期2026-10-14]
  7. mmap.2.en [引用日期2026-10-14]
  8. p13-crotty(PDF) [引用日期2026-10-14]
  9. 第15页中的“内存管理接口”介绍接口和高速缓存控制 [引用日期2026-10-14]
  10. munmap.2 [引用日期2026-10-14]
  11. epfl.ch 上的网页 [引用日期2026-10-14]
  12. On 8/17/2020 12:27 PM, Noel Chiappa wrote: [引用日期2026-10-14]
  13. tuhs.org 上的网页 [引用日期2026-10-14]
  14. [TUHS] Re: TUHS digest, Vol 1 #145 [引用日期2026-10-14]
  15. mmap.2freebsd [引用日期2026-10-14]
  16. inspirehep.net 上的网页 [引用日期2026-10-14]
词条评价
词条统计

浏览次数:5 次

阅读量:0 次 · 阅读完成量:0 次

最近更新:2026-10-13T22:04:05Z

历史版本

本条目引用的词条
munmap 虚拟内存 请求分页 写时复制 共享内存 POSIX