彻底理解 mmap:从虚拟内存、缺页中断、写时复制到 Page Cache
发布时间:2026/11/2 1:35:35 锦皓数字建站

在接触 Linux 内存管理时我们反复听到虚拟内存、缺页中断、写时复制COW和Page Cache这些概念。而 mmap 正是将这些零散知识点串联起来的绝佳案例。本文用一张思维导图的脉络通俗拆解 mmap 的底层原理与使用细节让你真正“学透”而不仅仅是“背过”。前置知识回顾在进入 mmap 之前先快速过一遍背景虚拟内存每个进程看到的是独立的、连续的地址空间背后映射到不连续的物理页框。缺页中断当进程访问的虚拟地址没有对应物理页时触发缺页异常内核分配物理页并填充数据。COW写时复制多个进程共享同一物理页一旦有进程需要写就复制一份私有副本保证隔离。Page Cache内核用于缓存文件数据的物理内存区域读写文件实际上优先与 Page Cache 交互。mmap 正是建立在这一套基础设施之上的上层机制。一、mmap 到底是什么mmap 在进程虚拟地址空间创建一段内存区间和磁盘文件建立映射关系访问这段虚拟地址时由缺页异常按需加载文件数据到内核页缓存进程直接操作页面省去传统 read/write 中内核缓冲区到用户缓冲区的数据拷贝磁盘 IO 依然会在缺页时发生优势是简化数据拷贝路径并且可以减少频繁读写场景下的系统调用**。三个核心关键字虚拟地址映射mmap 调用时内核只是在进程的页表中建立“虚拟地址 → 文件偏移”的逻辑绑定并不会立刻把文件内容读进物理内存。缺页中断当你第一次真正访问这段虚拟地址时CPU 发现没有对应物理页触发缺页中断。内核会分配物理内存并从磁盘读取一页数据通常 4KB到物理内存的 Page Cache 中然后更新页表。按需加载你访问哪一页内核才加载哪一页而不是一次性把整个文件读进来。这就实现了懒加载节省内存与时间。mmap vs read/write 的本质区别传统read()流程磁盘 → 内核 Page Cache → CPU拷贝 → 用户缓冲区用户态内存两次数据拷贝①磁盘到 Page CacheDMA② Page Cache 到用户缓冲区CPU 拷贝。每次读写都要通过 read/write 系统调用频繁切入内核态。用户调用 read 时会预先传入一块用户态虚拟地址 VAbuf这块 VA 背后的物理页分两种情况之前访问过物理页已经存在copy_to_user 直接把 Page Cache 的数据复制到这个已存在的用户物理页不分配新物理页。从未访问过缺页copy_to_user 访问该用户 VA 时触发用户缺页内核现场分配物理页再拷贝数据进去。数据存在两份独立物理页副本一份内核 Page Cache全局共享文件唯一内存副本一份用户 buf 对应的物理页进程私有副本本质数据拷贝内核把 Page Cache 内容复制到用户空间物理页。mmap 流程磁盘 → 内核 Page Cache 用户进程直接通过虚拟地址读写这块物理内存减少一次 CPU 拷贝数据从磁盘到 Page Cache 后用户空间直接就能访问无需再拷贝到用户缓冲区。减少系统调用之后读写就像操作普通内存指针无需反复 read/write。天然适合随机访问想读文件任意偏移处的数据直接指针偏移即可内核只加载用到的页。mmap 系统调用本身不读磁盘、不分配 Page Cache、不拷贝数据仅修改当前进程页表把一段用户 VA直接映射到将来的 Page Cache 物理页此时映射条目标记为无效。第一次访问这片 mmap 出来的用户 VA触发缺页异常分两种情况✅ cache 命中该偏移对应的 Page Cache 物理页已经存在直接在当前进程页表建立映射共享这块已有物理页无磁盘 IO、不分配新物理页。❌ cache 未命中分配新物理页发起磁盘 IO 把文件数据读到 Page Cache再把进程页表映射到这块刚填充好的物理页。数据只有同一份物理页用户进程和内核 Page Cache 共享不存在 CPU 拷贝。写的时候修改的就是 Page Cache 本身标记脏页后续内核线程异步回写磁盘。补充区分MAP_PRIVATE私有文件映射读的时候依然共享 Page Cache一旦写入触发 COW 缺页内核分配新物理页、复制页面内容之后这一页变成进程私有副本不再和原 Page Cache 共享修改不会写回原文件。两者共性 Page Cache、缺页异常处理、脏页回收、回写、页表维护全部由内核管理 用户拿到的都是虚拟地址虚拟地址只是编号物理内存是按需分配懒分配。总结mmap 仅在初始化映射时执行一次 mmap 系统调用。当目标文件页面已经载入 PageCache进程通过虚拟地址访问内存MMU 完成地址翻译全程用户态运行无内核切换。如果页面尚未加载访问内存触发硬件缺页异常被动进入内核加载数据这和主动调用 read/write 系统调用不是一回事。read/write 模式每一次 IO 操作都需要程序主动发起系统调用并且额外存在一次 PageCache 到用户缓冲区的 CPU 拷贝这也是 mmap 性能优势来源。二、mmap 怎么用文件映射与匿名映射mmap 主要分为两大类背后的载体截然不同。1. 文件映射绑定磁盘文件通过 mmap 将一个普通文件映射进虚拟地址空间底层依赖的就是 Page Cache。也就是说被映射的文件数据会以页为单位缓存在 Page Cache 里进程直接读写这些页。2. 匿名映射MAP_ANONYMOUS不关联任何磁盘文件内核直接分配空白物理页。数据没有文件背景不经过 Page Cache。glibc 中当 malloc 申请的内存大于 128KB 时底层就会改用匿名 mmap而不是调整堆的 brk。 深入对比文件映射与匿名映射的缺页和页面回收文件映射带 fd 普通 mmapmmap(fd, offset, len)创建阶段虚拟区间逻辑绑定「磁盘文件 文件偏移」没有物理内存。首次访问触发缺页内核根据记录的磁盘位置读取数据到 PageCache生成文件页虚拟页 ↔ 物理文件页。页面回收可以直接丢弃不需要 Swap再次访问重新从磁盘文件加载。匿名映射MAP_ANONYMOUSmmap(NULL, len, prot, MAP_ANONYMOUS | MAP_SHARED/MAP_PRIVATE, -1, 0)创建阶段虚拟区间没有绑定任何磁盘文件内核只标记这是一段匿名映射区域依旧不分配物理内存。首次访问触发缺页内核不会去磁盘读取任何文件直接分配一块清零的物理内存页 → 匿名页虚拟页 ↔ 匿名页。页面回收匿名页没有对应的磁盘原始文件内存不足时必须把页面内容写入 Swap 分区下次访问缺页从 Swap 读回内存。MAP_SHARED vs MAP_PRIVATE重点理解 COW这两个标志决定了多个进程之间以及与文件之间的交互方式。MAP_SHARED共享映射多个进程映射同一个文件时它们共享同一份 Page Cache 物理页。任何一个进程对内存的修改其他进程立刻可见。修改产生的脏页最终由内核异步刷回磁盘或调用msync()强制同步。MAP_PRIVATE私有映射 写时复制初始状态同样共享底层文件的物理页Page Cache。一旦某个进程尝试写 → 触发 写时复制COW内核为当前进程分配一个全新的物理页复制原页内容然后修改页表指向新页。后续的写入操作都在这个私有副本上进行。修改不会回写到原文件对其他进程不可见。 与 fork 的联动fork()创建子进程时父子进程的私有内存也是通过 COW 共享同一物理页。这背后的硬件机制页表项写保护位、缺页陷阱与 MAP_PRIVATE 完全一致。即MAP_PRIVATE 相当于在文件映射上实现了类似 fork 的 COW 行为。三、mmap 常用在哪些场景大文件随机访问例如日志解析、简单的数据库存储引擎。利用 mmap 可以将随机读请求转化为直接内存访问热数据留在 Page Cache无需频繁 lseek read。进程间通信共享内存 IPC使用 MAP_SHARED | MAP_ANONYMOUS 创建共享内存区域多个进程可直接读写同一块物理内存吞吐高、延迟低。⚠️ 但内核不提供任何同步机制需要用户自己用互斥锁、信号量或原子操作来避免竞态。动态库加载Linux 加载动态链接库.so时底层就是用 mmap 将库文件映射到进程地址空间且通常使用 MAP_PRIVATE修改不写回文件或 MAP_SHARED代码段只读。大块匿名内存分配如前所述glibc 的 malloc 对于超过 MMAP_THRESHOLD默认 128KB的请求直接使用匿名 mmap避免堆碎片化并方便独立释放。四、为什么 mmap 性能好三点优势总结减少系统调用映射建立后读写变成内存访问无需反复 read/write。减少一次 CPU 数据拷贝从磁盘到用户空间只需一次 DMA 到 Page Cache用户态可直接使用。按需加载 页缓存复用只有真正访问到的页才会加载且 Page Cache 可被多个映射共享节省物理内存。五、注意事项不要用于小文件或一次性顺序读写mmap 本身有系统调用和页表建立的固定开销对于很小的文件或只需要顺序读一次的场景传统 read 更快。文件截断风险SIGBUS如果文件被外部 truncate 截断而进程还在访问已映射但超出文件大小的地址会收到 SIGBUS 信号直接崩溃。必须通过信号处理或预先保护。MAP_SHARED 不是线程安全 / 进程安全的多个进程同时写共享映射区必须自己用锁或原子操作保证同步否则数据会错乱。缺页风暴如果映射了超大区域又进行完全随机的访问可能导致大量缺页中断性能不升反降。六、总结mmap 能够把磁盘文件映射至进程虚拟地址空间将文件 I/O 转变为直接内存访问。它依靠缺页中断完成按需加载核心收益是减少数据拷贝与系统调用开销。该机制广泛用于大文件随机读写、进程间共享内存、动态链接库加载、大容量内存分配场景。同时存在诸多使用限制不适用于小规模顺序 I/O文件截断可能触发 SIGBUSMAP_SHARED 模式要求上层实现同步纯粹随机访问会引发频繁缺页造成性能损耗。七、极易混淆的两个补充知识点文件映射的缺页 vs 匿名映射的缺页文件映射缺页内核从磁盘文件读取数据到 Page Cache然后映射给进程。匿名映射缺页内核分配一个空白物理页通常全零填充物理内存紧张时会被换出到 Swap 分区。文件映射页回收与 Swap 无关当内存紧张需要回收 Page Cache 中的文件映射页时如果页是干净的未修改直接丢弃如果是脏页先写回文件再释放。它绝不会被换出到 Swap。这是因为文件映射的数据本身就有文件作为后备存储缺页时重新从文件读取即可。而匿名页无文件背景没有后备存储只能暂存到 Swap。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。