Skip to content

内存映射文件

2026 大纲 三(二)5 内存映射文件(memory-mapped files)——属「三、内存管理 →(二)虚拟内存管理」。

把文件当成内存来读写

到这里,请求分页的整套机制已经齐了:页表项里有存在位,不在就缺页, 缺页就从对换区把它调进来。

现在换个角度看这套机制——它其实和"读文件"是同一件事。

平时读文件要走 read():陷入内核、把数据从磁盘读进页高速缓存、 再从页高速缓存拷贝一份到你给的用户缓冲区,然后返回。 每读一段就走一遍,还得自己管缓冲区、自己记读到哪了。

可虚存机制做的事情几乎一模一样:你访问一个地址, 它发现不在内存,就去后备存储把那一页取回来,然后你直接访问。 唯一的差别是"后备存储"是对换区而不是你的文件。

那把后备存储换成文件不就行了?——这就是内存映射文件: 把文件的某一段,登记为进程虚拟地址空间里某一段的后备存储。 登记之后,读写这段内存就等于读写这段文件,用指针,不再走任何系统调用

三处最容易想错的,读之前先立住:

mmap() 返回时一个字节都没读——它只划出虚拟地址区间、登记对应关系、 把页表项置成"不在内存"。所以映射一个 100 GB 的文件几乎是瞬时的, mmap 的耗时与文件大小无关,而 read 正比于字节数

它省下的不是磁盘 I/O,而是"页高速缓存 → 用户缓冲区"那一次 CPU 拷贝; "磁盘 → 页高速缓存"那次 DMA 谁也省不掉。

它也不是零成本——它把系统调用换成了缺页中断,而缺页中断更贵。

一、基本概念

内存映射文件(Memory-Mapped File):将磁盘上的文件内容映射到进程的虚拟地址空间,之后对该区域的读写就等价于对文件的读写。

建立映射的系统调用是 mmap,它的参数直接决定了上面几条语义:

参数含义它决定了什么
addr建议的映射起始虚拟地址通常传空,由内核选一段空闲的虚拟地址区间
length映射长度划出多大一段虚拟地址空间
prot访问权限:可读 / 可写 / 可执行落进页表项的保护位
flags共享 / 私有 / 匿名修改是否对别人可见、是否写回文件
fd被映射文件的描述符后备存储是哪个文件;匿名映射时不指定
offset文件内偏移(须按页对齐)映射文件的哪一段

一个由"页是唯一计量单位"推出的边界

映射区的长度若不是页的整数倍,最后一页的尾部余量同样是可读写的(那一页整页都被分配了),但超出文件长度的那部分修改不会写回文件——文件不会因此变长。要让文件变长,得先用别的手段把文件扩大再映射。

二、修改什么时候写回文件

这是内存映射文件最含糊、也最必须讲清的一处。"OS 负责写回"是对的,但什么时候写回有四个不同的触发点:

触发点谁发起时机确定吗
该页被置换出去时页面置换算法不确定——取决于内存压力和置换算法选了谁
OS 的周期性回写内核的回写机制(按脏页停留时间或脏页比例阈值)不确定——只保证"最终会写"
进程显式请求同步(msync程序自己确定:调用返回即表示指定范围已落盘
解除映射(munmap)或进程退出内核确定,但要等到那个时候

想要确定性(比如写完必须保证掉电不丢),只能靠显式的 msync。这与 write() 的语义其实是对称的——write() 返回只表示数据进了页高速缓存,同样不保证落盘,要落盘得靠 fsync

三、与页高速缓存的关系:这决定了"少一次拷贝"成不成立

常规 read()/write() 走的路是:磁盘 →(DMA)→ 页高速缓存(内核缓冲区)→(CPU 拷贝)→ 用户缓冲区。 内存映射走的路是:磁盘 →(DMA)→ 页高速缓存→ 把这一页的页框号直接填进用户进程的页表项

关键在于两条路的中间那一站是同一个东西。 反过来推一遍就明白这个前提有多要紧:假如映射页和文件缓存是两份独立的副本,那么用户改了映射页就必须再同步回文件缓存,一次拷贝也省不掉;而且一个进程用 write() 写、另一个进程用 mmap 读同一个文件,两边看到的内容还会不一致。

把"少一次拷贝"算成具体数字:4 MB 文件、4 KB 页,read 与 mmap 逐项对照(想知道省下来的到底是哪几项、多进程时差多少时展开)

题目(数据自造):某系统页大小 4 KB,要把一个 4 MB 的文件完整读一遍。方案 A 用 read(),每次读 4 KB;方案 B 用 mmap() 建立共享映射后顺序访问一遍。

第一步:算页数。 映射与缺页的计量单位都是页,后面每一项开销都要按页数来数。

4 MB4 KB=4×1024×10244096=1024 

第二步:逐项对照

项目方案 A:read() 1024 次方案 B:mmap() + 顺序访问
系统调用次数10241(只有那次 mmap)
用户态/内核态切换2048 次2 次
缺页中断次数01024(每页一次)
DMA 传输量(磁盘→内存)4 MB4 MB
CPU 参与的内存拷贝4 MB(页高速缓存 → 用户缓冲区)0
物理内存占用页高速缓存 4 MB + 用户缓冲区 4 MB = 8 MB页高速缓存 4 MB(用户页表直接指向它)= 4 MB

把系统调用和缺页并列是要害:mmap 不是没有代价,它把 1024 次系统调用换成了 1024 次缺页中断,而缺页中断比系统调用更贵(还含一次磁盘 I/O)。所以 mmap 真正省下的是那 4 MB 的 CPU 拷贝4 MB 的物理内存。若用预调页一次调入多页,缺页次数还能进一步压低。

第三步:两个进程同读一个文件

方案 A:各自 read()方案 B:各自共享映射
页高速缓存4 MB(两进程共用)4 MB
用户缓冲区4 MB × 2 = 8 MB0(两进程页表指向同一批页框)
合计12 MB4 MB

进程越多差距越大——n 个进程时 read 方案占 4(n+1) MB,mmap 方案恒为 4 MB。这也正是"多进程映射同一文件即得到共享内存"的物理解释:它们的页表项指向的本来就是同一批页框。

四、它与共享内存、与请求分页的关系

多个进程把同一个文件用共享映射映入各自的地址空间,页表项就指向同一批物理页框——这就是一块共享内存。它与匿名共享内存并不是两种机制,而是同一种机制的两种后备存储

内存映射文件(共享映射)匿名共享内存
后备存储被映射的文件对换区,不落盘
怎么找到对方文件路径——无亲缘关系的任意进程都能打开同一个文件靠内核对象的标识,或父子进程继承
数据持久性进程全退出后内容还在文件里进程退出、内核释放后内容消失
通信本质完全相同:让多个进程的页表项指向同一批物理页框同左

再往下看一层,映射页与普通虚存页的差别同样只有"后备存储是谁"这一项:

环节普通虚存页映射页一样吗
首次访问缺页中断,从对换区/文件区调入缺页中断,从被映射文件调入机制相同,来源不同
页表项字段P / A / M / 外存地址P / A / M / 文件内位置相同
内存不足时按置换算法换出按置换算法换出相同
脏页换出写回对换区写回被映射的文件机制相同,去向不同
干净页换出直接丢弃直接丢弃相同

匿名映射说明了什么

flags 设成匿名、不指定文件,就得到一段不关联任何文件的映射区——它跟普通的堆、栈没有本质区别。这说明 mmap 的本质不是"访问文件的新方式",而是"操作虚拟地址空间的通用手段":在地址空间里划一段区间,并指定它的后备存储。指定成文件,就是内存映射文件;不指定,就是普通的匿名内存。"映射文件"只是这个通用机制最有名的一个用法。

关于共享存储与消息传递、管道的选型判据(以及一道量化对比的算例),见进程间通信

考点速记

  1. 核心机制一句话:把文件的某一段,登记为进程虚拟地址空间里某一段的"后备存储"。普通虚存页的后备存储是对换区,映射页换成了这个文件——除此之外两者走完全相同的请求分页流程。⚠️ 是映射到进程的虚拟地址空间,不是映射到系统的物理地址空间。
  2. mmap() 返回时一个字节都没读:它只划出虚拟地址区间、登记对应关系、把页表项置成"不在内存",真正读入发生在首次访问触发缺页中断时。判据:mmap 的耗时与文件大小无关,read 的耗时正比于字节数
  3. 之后直接用指针读写,不再经过任何系统调用
  4. flags 决定语义共享映射——修改对其他映射者可见、会写回文件私有映射——写时复制,修改互不可见、永远不写回文件匿名映射——不关联文件,后备存储是对换区、区内初始全为 0
  5. ⚠️私有映射"永远不写回文件"最容易搞反:一旦某进程写,内核为它单独复制一份页,从此与文件、与其他进程都断开,那份私有页的后备存储变成对换区。可执行文件的代码段通常就用私有映射。
  6. offset 必须页对齐——页表项只能整页地指向一个页框,页是映射机制唯一的计量单位prot 落进页表项的保护位,越权访问触发保护故障。
  7. 写回时机四个触发点,只有一个确定:① 该页被置换时(不确定)② OS 周期性回写(只保证"最终会写")③ 进程显式 msync唯一确定)④ 解除映射或进程退出时。
  8. "少一次拷贝"的前提是 mmap 与 read/write 共用同一份页高速缓存。少掉的是"页高速缓存 → 用户缓冲区"那次 CPU 拷贝;"磁盘 → 页高速缓存"那次 DMA 谁也省不掉。
  9. mmap 不是零成本:它把系统调用换成了缺页中断(缺页中断更贵,还含一次磁盘 I/O)。真正省下的是那笔 CPU 拷贝和物理内存。
  10. 共享映射即共享内存,因此可实现进程间通信:多进程映射同一文件时,它们的页表项指向同一批物理页框——一方写入,另一方立刻看得见。⚠️ 但各进程的虚拟地址可以完全不同,相同的是映射到的物理地址
  11. 与匿名共享内存的差别只有两处后备存储(文件 vs 对换区)与命名方式(文件路径 vs 内核对象标识或父子继承)。判据一句话:看这批共享页框换出去时写到哪儿

这一节在真题里被考过的形式

两道题都是近年新出的,且都在考同一组分辨点——映射到哪、共享的是什么、地址相不相同。

  • 判断内存映射文件的四条叙述(2025-30)。答可实现进程间通信 + 将文件映射到进程的虚拟地址空间两条。⚠️ 错的两条各有一个字的差别:"映射到系统的物理地址空间"错——映射的对象是进程的虚拟地址空间"实现了页面到磁盘块的映射"错——那是文件系统本来就在做的事,内存映射建立的是虚拟页到文件内容的对应。
  • 两个进程共享一个映射文件,判断页表项与虚拟地址的关系(2026-30)。答 W1 和 W2 映射的物理地址相同。⚠️ 另三条正好把三处分辨点各说反一次:"页表项内容完全不同"错——两者的页框号是相同的(指向同一批物理页框),只是保护位等可能不同;"P1 的修改不影响 P2"错——共享映射下一方写入另一方立刻可见,这正是它能用来通信的原因;"虚拟地址相同"错——各进程的虚拟地址由各自的地址空间独立分配,通常并不相同

复习优先级必须拿满,且两道题的判据高度重合。 把速记第一条和第十条记死即可—— 映射到虚拟地址空间共享的是物理页框而非虚拟地址。 第五条(私有映射不写回)目前没考过但极易被设成错项,也要记。 "少一次拷贝"那笔账属于理解层面,不会直接设问。

易错:说内存映射文件把文件映射到物理地址空间。是映射到进程的虚拟地址空间

易错:认为共享同一文件的两个进程,虚拟地址也相同。虚拟地址各自独立分配、通常不同,相同的是映射到的物理地址

易错:认为共享映射下一方的修改不影响另一方。页表项指向同一批物理页框,写入立刻可见——这正是它能做 IPC 的原因。

易错:认为私有映射的修改最终也会写回文件。永远不写回——一写就复制一份私有页,后备存储改成对换区。

易错:认为 mmap() 会把文件内容读进内存。它一个字节都不读,首次访问才缺页调入。

易错:认为 mmap 省掉了磁盘 I/O。省的是"页高速缓存 → 用户缓冲区"那次 CPU 拷贝,DMA 那次谁也省不掉。

易错:认为 mmap 一定比 read 快。它把系统调用换成了更贵的缺页中断,省下的是拷贝和物理内存。

相关知识

页框分配与回收请求页式管理虚拟存储性能与改进进程间通信

真题练习

相关真题(2题)