这是"操作系统系列教程"的第三十六篇。上一篇我们讲了交换空间,知道了磁盘如何充当内存的延伸。这一篇,我们来讲一个更巧妙的机制------内存映射(mmap)。它让程序可以像访问内存一样访问文件,把文件和内存之间的界限变得模糊。
一个"读写文件"的烦恼
先来看一段常见的代码。你要读取一个文件的内容,通常会这样写:
c
int fd = open("data.txt", O_RDONLY);
char buf[4096];
while (read(fd, buf, sizeof(buf)) > 0) {
// 处理 buf 中的数据
}
close(fd);
这段代码做了几件事:
- 打开文件,得到文件描述符;
- 分配一块用户缓冲区
buf; - 循环调用
read(),把数据从内核复制到用户缓冲区; - 处理数据;
- 关闭文件。
这看起来很自然,但有一个隐藏的成本:数据被复制了两次。
- 第一次:从磁盘读到内核缓冲区;
- 第二次:从内核缓冲区复制到用户缓冲区。
如果文件很大,或者读写很频繁,这两次复制会消耗大量 CPU 时间和内存带宽。
有没有一种方式,能让程序直接访问文件内容,而不需要复制?
答案是:内存映射(mmap)。
mmap 把文件的一部分或全部映射到进程的虚拟地址空间。程序访问这块内存时,操作系统自动从文件读取数据;程序修改这块内存时,操作系统自动把修改写回文件。整个过程不需要显式的 read() 和 write()。
这篇文章,我们就来把"内存映射"这件事讲清楚。
说明:本文涉及虚拟内存、页表、缺页中断、零拷贝等概念。初学者如果遇到暂时看不懂的术语,可以先跳过细节,重点理解"mmap 把文件映射到内存,程序像访问内存一样访问文件"这一核心思想。
一、内存映射到底是什么?
1.1 从"地图"说起
我们可以用"地图"来类比内存映射。
| 地图场景 | 操作系统 |
|---|---|
| 真实地形 | 磁盘上的文件 |
| 地图 | 虚拟地址空间中的映射区域 |
| 看地图 | 访问内存 |
| 地图上的标记 | 文件中的偏移 |
| 地图更新 | 写回文件 |
地图不是真实地形,但它让你可以方便地查看和操作地形。mmap 就像给文件画了一张"地图",程序通过这张地图访问文件,而不需要直接操作磁盘。
1.2 内存映射的定义
给出一个正式的定义:
内存映射(Memory Mapping,mmap)是一种将文件或设备映射到进程虚拟地址空间的机制。映射后,进程可以像访问普通内存一样读写文件内容,操作系统负责在背后完成磁盘 I/O。
这个定义里有四个关键点:
- 映射:把文件的一部分或全部映射到虚拟地址空间;
- 像访问内存一样 :程序使用指针读写,不需要
read()/write(); - 操作系统负责 I/O:缺页时自动从文件加载,修改时自动写回;
- 文件或设备:不仅可以映射文件,还可以映射设备内存。
1.3 内存映射在系统中的位置
#mermaid-svg-Yxp5IuuMykRScOrA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Yxp5IuuMykRScOrA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Yxp5IuuMykRScOrA .error-icon{fill:#552222;}#mermaid-svg-Yxp5IuuMykRScOrA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Yxp5IuuMykRScOrA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Yxp5IuuMykRScOrA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Yxp5IuuMykRScOrA .marker.cross{stroke:#333333;}#mermaid-svg-Yxp5IuuMykRScOrA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Yxp5IuuMykRScOrA p{margin:0;}#mermaid-svg-Yxp5IuuMykRScOrA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Yxp5IuuMykRScOrA .cluster-label text{fill:#333;}#mermaid-svg-Yxp5IuuMykRScOrA .cluster-label span{color:#333;}#mermaid-svg-Yxp5IuuMykRScOrA .cluster-label span p{background-color:transparent;}#mermaid-svg-Yxp5IuuMykRScOrA .label text,#mermaid-svg-Yxp5IuuMykRScOrA span{fill:#333;color:#333;}#mermaid-svg-Yxp5IuuMykRScOrA .node rect,#mermaid-svg-Yxp5IuuMykRScOrA .node circle,#mermaid-svg-Yxp5IuuMykRScOrA .node ellipse,#mermaid-svg-Yxp5IuuMykRScOrA .node polygon,#mermaid-svg-Yxp5IuuMykRScOrA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Yxp5IuuMykRScOrA .rough-node .label text,#mermaid-svg-Yxp5IuuMykRScOrA .node .label text,#mermaid-svg-Yxp5IuuMykRScOrA .image-shape .label,#mermaid-svg-Yxp5IuuMykRScOrA .icon-shape .label{text-anchor:middle;}#mermaid-svg-Yxp5IuuMykRScOrA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Yxp5IuuMykRScOrA .rough-node .label,#mermaid-svg-Yxp5IuuMykRScOrA .node .label,#mermaid-svg-Yxp5IuuMykRScOrA .image-shape .label,#mermaid-svg-Yxp5IuuMykRScOrA .icon-shape .label{text-align:center;}#mermaid-svg-Yxp5IuuMykRScOrA .node.clickable{cursor:pointer;}#mermaid-svg-Yxp5IuuMykRScOrA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Yxp5IuuMykRScOrA .arrowheadPath{fill:#333333;}#mermaid-svg-Yxp5IuuMykRScOrA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Yxp5IuuMykRScOrA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Yxp5IuuMykRScOrA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Yxp5IuuMykRScOrA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Yxp5IuuMykRScOrA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Yxp5IuuMykRScOrA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Yxp5IuuMykRScOrA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Yxp5IuuMykRScOrA .cluster text{fill:#333;}#mermaid-svg-Yxp5IuuMykRScOrA .cluster span{color:#333;}#mermaid-svg-Yxp5IuuMykRScOrA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Yxp5IuuMykRScOrA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Yxp5IuuMykRScOrA rect.text{fill:none;stroke-width:0;}#mermaid-svg-Yxp5IuuMykRScOrA .icon-shape,#mermaid-svg-Yxp5IuuMykRScOrA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Yxp5IuuMykRScOrA .icon-shape p,#mermaid-svg-Yxp5IuuMykRScOrA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Yxp5IuuMykRScOrA .icon-shape .label rect,#mermaid-svg-Yxp5IuuMykRScOrA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Yxp5IuuMykRScOrA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Yxp5IuuMykRScOrA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Yxp5IuuMykRScOrA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 磁盘
内核
进程虚拟地址空间
访问
缺页
读取
数据
填充
映射区域
页表
缺页中断处理
文件
图 1:内存映射在系统中的位置。 文件被映射到进程地址空间,访问时通过缺页中断从磁盘加载。
二、为什么需要内存映射?
2.1 传统文件读写的问题
传统的 read()/write() 方式有两个主要问题:
问题一:数据复制
read() 把数据从磁盘读到内核缓冲区,再从内核缓冲区复制到用户缓冲区。两次复制消耗 CPU 和内存带宽。
问题二:系统调用开销
每次 read()/write() 都是一次系统调用,涉及用户态和内核态的切换。对于大文件或频繁读写,系统调用开销累积起来很可观。
2.2 mmap 的优势
mmap 解决了这些问题:
| 优势 | 说明 |
|---|---|
| 减少复制 | 数据直接从磁盘映射到用户空间,不需要二次复制 |
| 减少系统调用 | 一次 mmap,后续访问都是普通内存访问 |
| 按需加载 | 只有真正访问的页才会从磁盘加载 |
| 共享内存 | 多个进程可以映射同一个文件,实现共享 |
| 简化编程 | 像访问数组一样访问文件 |
2.3 一个直观的对比
| 对比项 | read/write | mmap |
|---|---|---|
| 数据复制 | 2 次 | 0 次(或 1 次) |
| 系统调用 | 每次读写 | 一次映射 |
| 内存占用 | 用户缓冲区 | 按需加载 |
| 随机访问 | 需要 lseek | 直接指针访问 |
| 共享 | 需要额外机制 | 天然支持 |
#mermaid-svg-P5QxBGTRDGuIkGsu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P5QxBGTRDGuIkGsu .error-icon{fill:#552222;}#mermaid-svg-P5QxBGTRDGuIkGsu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P5QxBGTRDGuIkGsu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P5QxBGTRDGuIkGsu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P5QxBGTRDGuIkGsu .marker.cross{stroke:#333333;}#mermaid-svg-P5QxBGTRDGuIkGsu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P5QxBGTRDGuIkGsu p{margin:0;}#mermaid-svg-P5QxBGTRDGuIkGsu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu .cluster-label text{fill:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu .cluster-label span{color:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu .cluster-label span p{background-color:transparent;}#mermaid-svg-P5QxBGTRDGuIkGsu .label text,#mermaid-svg-P5QxBGTRDGuIkGsu span{fill:#333;color:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu .node rect,#mermaid-svg-P5QxBGTRDGuIkGsu .node circle,#mermaid-svg-P5QxBGTRDGuIkGsu .node ellipse,#mermaid-svg-P5QxBGTRDGuIkGsu .node polygon,#mermaid-svg-P5QxBGTRDGuIkGsu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P5QxBGTRDGuIkGsu .rough-node .label text,#mermaid-svg-P5QxBGTRDGuIkGsu .node .label text,#mermaid-svg-P5QxBGTRDGuIkGsu .image-shape .label,#mermaid-svg-P5QxBGTRDGuIkGsu .icon-shape .label{text-anchor:middle;}#mermaid-svg-P5QxBGTRDGuIkGsu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P5QxBGTRDGuIkGsu .rough-node .label,#mermaid-svg-P5QxBGTRDGuIkGsu .node .label,#mermaid-svg-P5QxBGTRDGuIkGsu .image-shape .label,#mermaid-svg-P5QxBGTRDGuIkGsu .icon-shape .label{text-align:center;}#mermaid-svg-P5QxBGTRDGuIkGsu .node.clickable{cursor:pointer;}#mermaid-svg-P5QxBGTRDGuIkGsu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P5QxBGTRDGuIkGsu .arrowheadPath{fill:#333333;}#mermaid-svg-P5QxBGTRDGuIkGsu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P5QxBGTRDGuIkGsu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P5QxBGTRDGuIkGsu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P5QxBGTRDGuIkGsu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P5QxBGTRDGuIkGsu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P5QxBGTRDGuIkGsu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P5QxBGTRDGuIkGsu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P5QxBGTRDGuIkGsu .cluster text{fill:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu .cluster span{color:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P5QxBGTRDGuIkGsu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P5QxBGTRDGuIkGsu rect.text{fill:none;stroke-width:0;}#mermaid-svg-P5QxBGTRDGuIkGsu .icon-shape,#mermaid-svg-P5QxBGTRDGuIkGsu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P5QxBGTRDGuIkGsu .icon-shape p,#mermaid-svg-P5QxBGTRDGuIkGsu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P5QxBGTRDGuIkGsu .icon-shape .label rect,#mermaid-svg-P5QxBGTRDGuIkGsu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P5QxBGTRDGuIkGsu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P5QxBGTRDGuIkGsu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P5QxBGTRDGuIkGsu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} mmap
映射
磁盘
用户地址空间
read/write
复制1
复制2
磁盘
内核缓冲区
用户缓冲区
图 2:read/write 与 mmap 的数据流对比。 mmap 减少了复制次数。
三、mmap 的工作原理
3.1 映射的建立
使用 mmap 时,进程调用 mmap() 系统调用,传入以下参数:
c
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
| 参数 | 说明 |
|---|---|
| addr | 建议的映射起始地址(通常传 NULL,由内核选择) |
| length | 映射的字节数 |
| prot | 保护权限(PROT_READ、PROT_WRITE、PROT_EXEC) |
| flags | 映射类型(MAP_SHARED、MAP_PRIVATE、MAP_ANONYMOUS) |
| fd | 文件描述符 |
| offset | 文件中的偏移量 |
调用成功后,内核会在进程的虚拟地址空间中找一块空闲区域,建立映射关系,并返回映射区域的起始地址。
3.2 访问映射区域
映射建立后,程序通过返回的指针访问文件内容:
c
char *ptr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
printf("%c\n", ptr[0]); // 访问文件第一个字节
当程序第一次访问某个地址时:
- CPU 发出虚拟地址;
- MMU 查页表,发现该页不在内存中(有效位为 0);
- 触发缺页中断;
- 操作系统根据映射关系,从文件中读取对应的页;
- 把页放入物理内存,更新页表;
- 重新执行指令,程序正常访问。
磁盘文件 页表 操作系统 MMU 程序 磁盘文件 页表 操作系统 MMU 程序 #mermaid-svg-Cs0m0dwaTem3ruuX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Cs0m0dwaTem3ruuX .error-icon{fill:#552222;}#mermaid-svg-Cs0m0dwaTem3ruuX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Cs0m0dwaTem3ruuX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Cs0m0dwaTem3ruuX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Cs0m0dwaTem3ruuX .marker.cross{stroke:#333333;}#mermaid-svg-Cs0m0dwaTem3ruuX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Cs0m0dwaTem3ruuX p{margin:0;}#mermaid-svg-Cs0m0dwaTem3ruuX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Cs0m0dwaTem3ruuX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Cs0m0dwaTem3ruuX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Cs0m0dwaTem3ruuX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Cs0m0dwaTem3ruuX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Cs0m0dwaTem3ruuX .sequenceNumber{fill:white;}#mermaid-svg-Cs0m0dwaTem3ruuX #sequencenumber{fill:#333;}#mermaid-svg-Cs0m0dwaTem3ruuX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Cs0m0dwaTem3ruuX .messageText{fill:#333;stroke:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Cs0m0dwaTem3ruuX .labelText,#mermaid-svg-Cs0m0dwaTem3ruuX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .loopText,#mermaid-svg-Cs0m0dwaTem3ruuX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Cs0m0dwaTem3ruuX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Cs0m0dwaTem3ruuX .noteText,#mermaid-svg-Cs0m0dwaTem3ruuX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Cs0m0dwaTem3ruuX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Cs0m0dwaTem3ruuX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Cs0m0dwaTem3ruuX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Cs0m0dwaTem3ruuX .actorPopupMenu{position:absolute;}#mermaid-svg-Cs0m0dwaTem3ruuX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Cs0m0dwaTem3ruuX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Cs0m0dwaTem3ruuX .actor-man circle,#mermaid-svg-Cs0m0dwaTem3ruuX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Cs0m0dwaTem3ruuX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问映射地址查询页表项有效位为 0触发缺页中断读取对应页返回页数据更新页表项重新执行指令再次访问返回数据
图 3:mmap 访问时的缺页中断处理流程。
3.3 写回文件
如果映射时指定了 PROT_WRITE 和 MAP_SHARED,程序对映射区域的修改会写回文件。写回时机由操作系统决定,也可以显式调用 msync() 强制写回。
c
ptr[0] = 'H'; // 修改映射区域
msync(ptr, length, MS_SYNC); // 强制写回文件
如果指定 MAP_PRIVATE,修改不会写回文件,而是触发写时复制(Copy-on-Write),修改只对当前进程可见。
3.4 映射的解除
使用完毕后,调用 munmap() 解除映射:
c
munmap(ptr, length);
解除映射后,进程不能再访问该区域,否则会触发段错误。
四、mmap 的映射类型
4.1 共享映射(MAP_SHARED)
MAP_SHARED 表示多个进程可以共享同一个映射。对映射区域的修改会写回文件,并且对其他映射同一文件的进程可见。
用途:共享内存、多进程协作。
c
void *ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
4.2 私有映射(MAP_PRIVATE)
MAP_PRIVATE 表示私有映射。对映射区域的修改不会写回文件,而是触发写时复制,修改只对当前进程可见。
用途:只读文件、加载动态库。
c
void *ptr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
4.3 匿名映射(MAP_ANONYMOUS)
MAP_ANONYMOUS 表示匿名映射,不关联任何文件。映射区域的内容初始化为 0。
用途 :分配大块内存、实现共享内存(配合 MAP_SHARED)。
c
void *ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
4.4 映射类型对比
| 类型 | 是否写回文件 | 是否共享 | 典型用途 |
|---|---|---|---|
| MAP_SHARED | 是 | 是 | 共享内存、文件读写 |
| MAP_PRIVATE | 否 | 否 | 只读文件、动态库 |
| MAP_ANONYMOUS | 不涉及文件 | 取决于 flags | 内存分配、共享内存 |
#mermaid-svg-6CQRJmlYIRzipARF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6CQRJmlYIRzipARF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6CQRJmlYIRzipARF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6CQRJmlYIRzipARF .error-icon{fill:#552222;}#mermaid-svg-6CQRJmlYIRzipARF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6CQRJmlYIRzipARF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6CQRJmlYIRzipARF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6CQRJmlYIRzipARF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6CQRJmlYIRzipARF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6CQRJmlYIRzipARF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6CQRJmlYIRzipARF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6CQRJmlYIRzipARF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6CQRJmlYIRzipARF .marker.cross{stroke:#333333;}#mermaid-svg-6CQRJmlYIRzipARF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6CQRJmlYIRzipARF p{margin:0;}#mermaid-svg-6CQRJmlYIRzipARF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6CQRJmlYIRzipARF .cluster-label text{fill:#333;}#mermaid-svg-6CQRJmlYIRzipARF .cluster-label span{color:#333;}#mermaid-svg-6CQRJmlYIRzipARF .cluster-label span p{background-color:transparent;}#mermaid-svg-6CQRJmlYIRzipARF .label text,#mermaid-svg-6CQRJmlYIRzipARF span{fill:#333;color:#333;}#mermaid-svg-6CQRJmlYIRzipARF .node rect,#mermaid-svg-6CQRJmlYIRzipARF .node circle,#mermaid-svg-6CQRJmlYIRzipARF .node ellipse,#mermaid-svg-6CQRJmlYIRzipARF .node polygon,#mermaid-svg-6CQRJmlYIRzipARF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6CQRJmlYIRzipARF .rough-node .label text,#mermaid-svg-6CQRJmlYIRzipARF .node .label text,#mermaid-svg-6CQRJmlYIRzipARF .image-shape .label,#mermaid-svg-6CQRJmlYIRzipARF .icon-shape .label{text-anchor:middle;}#mermaid-svg-6CQRJmlYIRzipARF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6CQRJmlYIRzipARF .rough-node .label,#mermaid-svg-6CQRJmlYIRzipARF .node .label,#mermaid-svg-6CQRJmlYIRzipARF .image-shape .label,#mermaid-svg-6CQRJmlYIRzipARF .icon-shape .label{text-align:center;}#mermaid-svg-6CQRJmlYIRzipARF .node.clickable{cursor:pointer;}#mermaid-svg-6CQRJmlYIRzipARF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6CQRJmlYIRzipARF .arrowheadPath{fill:#333333;}#mermaid-svg-6CQRJmlYIRzipARF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6CQRJmlYIRzipARF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6CQRJmlYIRzipARF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6CQRJmlYIRzipARF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6CQRJmlYIRzipARF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6CQRJmlYIRzipARF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6CQRJmlYIRzipARF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6CQRJmlYIRzipARF .cluster text{fill:#333;}#mermaid-svg-6CQRJmlYIRzipARF .cluster span{color:#333;}#mermaid-svg-6CQRJmlYIRzipARF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6CQRJmlYIRzipARF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6CQRJmlYIRzipARF rect.text{fill:none;stroke-width:0;}#mermaid-svg-6CQRJmlYIRzipARF .icon-shape,#mermaid-svg-6CQRJmlYIRzipARF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6CQRJmlYIRzipARF .icon-shape p,#mermaid-svg-6CQRJmlYIRzipARF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6CQRJmlYIRzipARF .icon-shape .label rect,#mermaid-svg-6CQRJmlYIRzipARF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6CQRJmlYIRzipARF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6CQRJmlYIRzipARF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6CQRJmlYIRzipARF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} mmap 映射类型
MAP_SHARED
MAP_PRIVATE
MAP_ANONYMOUS
写回文件, 多进程共享
写时复制, 私有
不关联文件, 分配内存
图 4:mmap 的三种映射类型。
五、mmap 与普通文件读写的区别
5.1 数据复制次数
| 方式 | 复制次数 | 说明 |
|---|---|---|
| read/write | 2 次 | 磁盘→内核缓冲区→用户缓冲区 |
| mmap | 0 次(或 1 次) | 直接映射,缺页时从磁盘加载 |
5.2 系统调用次数
| 方式 | 系统调用 |
|---|---|
| read/write | 每次读写都调用 |
| mmap | 一次 mmap,后续访问无系统调用 |
5.3 内存占用
| 方式 | 内存占用 |
|---|---|
| read/write | 用户缓冲区固定大小 |
| mmap | 按需加载,只有访问的页占用内存 |
5.4 随机访问
| 方式 | 随机访问 |
|---|---|
| read/write | 需要 lseek 定位 |
| mmap | 直接指针访问,像数组一样 |
5.5 适用场景
| 场景 | 推荐方式 |
|---|---|
| 小文件顺序读写 | read/write |
| 大文件随机访问 | mmap |
| 多进程共享 | mmap |
| 设备内存访问 | mmap |
| 动态库加载 | mmap |

图 5:mmap 与 read/write 的适用场景对比。
六、mmap 的实际应用
6.1 动态库加载
当你运行一个程序时,动态链接器使用 mmap 把共享库(如 libc.so)映射到进程地址空间。多个进程可以共享同一个库的物理内存,节省内存。
bash
# 查看进程的内存映射
cat /proc/PID/maps
输出中可以看到 libc.so 等库被映射到地址空间。
6.2 共享内存
多个进程可以通过 mmap 映射同一个文件(或匿名映射)来实现共享内存。这是进程间通信(IPC)的一种高效方式。
c
// 进程 A
int fd = open("shared.txt", O_RDWR);
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
strcpy(ptr, "Hello from A");
// 进程 B
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("%s\n", ptr); // 输出:Hello from A
6.3 大文件处理
处理大文件时,mmap 可以避免一次性加载整个文件,只加载访问到的部分。数据库、日志分析工具常用 mmap 处理大文件。
6.4 数据库
数据库系统(如 SQLite、MongoDB)使用 mmap 把数据文件映射到内存,实现高效的随机访问和缓存。
6.5 设备驱动
mmap 也可以映射设备内存。例如,显卡驱动把显存映射到用户空间,让应用程序直接访问显存,提高图形性能。
#mermaid-svg-ldUuAT4d1cqB0Rft{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ldUuAT4d1cqB0Rft .error-icon{fill:#552222;}#mermaid-svg-ldUuAT4d1cqB0Rft .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ldUuAT4d1cqB0Rft .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ldUuAT4d1cqB0Rft .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ldUuAT4d1cqB0Rft .marker.cross{stroke:#333333;}#mermaid-svg-ldUuAT4d1cqB0Rft svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ldUuAT4d1cqB0Rft p{margin:0;}#mermaid-svg-ldUuAT4d1cqB0Rft .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft .cluster-label text{fill:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft .cluster-label span{color:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft .cluster-label span p{background-color:transparent;}#mermaid-svg-ldUuAT4d1cqB0Rft .label text,#mermaid-svg-ldUuAT4d1cqB0Rft span{fill:#333;color:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft .node rect,#mermaid-svg-ldUuAT4d1cqB0Rft .node circle,#mermaid-svg-ldUuAT4d1cqB0Rft .node ellipse,#mermaid-svg-ldUuAT4d1cqB0Rft .node polygon,#mermaid-svg-ldUuAT4d1cqB0Rft .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ldUuAT4d1cqB0Rft .rough-node .label text,#mermaid-svg-ldUuAT4d1cqB0Rft .node .label text,#mermaid-svg-ldUuAT4d1cqB0Rft .image-shape .label,#mermaid-svg-ldUuAT4d1cqB0Rft .icon-shape .label{text-anchor:middle;}#mermaid-svg-ldUuAT4d1cqB0Rft .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ldUuAT4d1cqB0Rft .rough-node .label,#mermaid-svg-ldUuAT4d1cqB0Rft .node .label,#mermaid-svg-ldUuAT4d1cqB0Rft .image-shape .label,#mermaid-svg-ldUuAT4d1cqB0Rft .icon-shape .label{text-align:center;}#mermaid-svg-ldUuAT4d1cqB0Rft .node.clickable{cursor:pointer;}#mermaid-svg-ldUuAT4d1cqB0Rft .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ldUuAT4d1cqB0Rft .arrowheadPath{fill:#333333;}#mermaid-svg-ldUuAT4d1cqB0Rft .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ldUuAT4d1cqB0Rft .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ldUuAT4d1cqB0Rft .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ldUuAT4d1cqB0Rft .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ldUuAT4d1cqB0Rft .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ldUuAT4d1cqB0Rft .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ldUuAT4d1cqB0Rft .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ldUuAT4d1cqB0Rft .cluster text{fill:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft .cluster span{color:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ldUuAT4d1cqB0Rft .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ldUuAT4d1cqB0Rft rect.text{fill:none;stroke-width:0;}#mermaid-svg-ldUuAT4d1cqB0Rft .icon-shape,#mermaid-svg-ldUuAT4d1cqB0Rft .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ldUuAT4d1cqB0Rft .icon-shape p,#mermaid-svg-ldUuAT4d1cqB0Rft .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ldUuAT4d1cqB0Rft .icon-shape .label rect,#mermaid-svg-ldUuAT4d1cqB0Rft .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ldUuAT4d1cqB0Rft .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ldUuAT4d1cqB0Rft .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ldUuAT4d1cqB0Rft :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} mmap 应用
动态库加载
共享内存
大文件处理
数据库
设备驱动
图 6:mmap 的典型应用场景。
七、mmap 的优缺点
7.1 优点
| 优点 | 说明 |
|---|---|
| 减少复制 | 避免内核到用户空间的二次复制 |
| 减少系统调用 | 一次映射,后续访问无系统调用 |
| 按需加载 | 只加载访问的页 |
| 共享方便 | 多进程共享同一文件 |
| 随机访问快 | 像数组一样访问 |
| 简化编程 | 不需要手动管理缓冲区 |
7.2 缺点
| 缺点 | 说明 |
|---|---|
| 映射开销 | 建立映射需要系统调用和页表操作 |
| 缺页中断 | 首次访问页时触发缺页,有延迟 |
| 文件大小固定 | 映射后文件大小不能轻易改变 |
| 信号处理复杂 | 访问非法映射区域会触发 SIGSEGV |
| 写回时机不可控 | 操作系统决定何时写回 |
| 不适合小文件 | 映射开销可能超过收益 |

图 7:mmap 的优缺点总结。
八、常见误区澄清
| 误区 | 真相 |
|---|---|
| "mmap 一定比 read 快" | 小文件或顺序读写,read 可能更快 |
| "mmap 没有数据复制" | 缺页时仍有一次磁盘到内存的复制 |
| "mmap 映射后文件立即加载" | 按需加载,只有访问的页才会加载 |
| "mmap 可以映射任意大小" | 受虚拟地址空间和文件大小限制 |
| "mmap 修改立即写回文件" | 写回时机由操作系统决定 |
| "mmap 只用于文件" | 也可以映射设备、匿名内存 |
| "munmap 后数据立即消失" | 共享映射的修改可能已写回文件 |