本篇面向已经了解以下概念的读者:

  • 物理内存的分页管理(什么是页框、页表、多级页表)
  • MMU 和 TLB 的基本工作原理
  • 进程地址空间的布局(代码段、数据段、堆、栈)

如果你对这些还不太熟悉,建议先翻阅《深入理解计算机系统》第9章或《现代操作系统》第3章,再回来看本篇会更顺畅。

为什么还要谈虚拟内存?

物理内存管理解决了“如何分配和回收物理页框”的问题,但它有几个天生的局限:

  1. 碎片化:即使有伙伴系统和 slab,长期运行后仍可能出现外部碎片,导致大块连续物理内存难以分配。
  2. 进程隔离:每个进程直接操作物理地址,既不安全(一个进程可以篡改另一个进程的数据),也不灵活(进程地址空间大小受限于物理内存)。
  3. 程序大小受限:一个程序如果想运行,其代码和数据必须全部加载到物理内存中,否则无法执行。这在早期计算机中是一个严重瓶颈。

虚拟内存正是为了解决这些问题而诞生的。它在进程和物理内存之间增加了一层抽象——虚拟地址空间,让每个进程拥有独立的、连续的、大小远超物理内存的地址空间,并通过按需调页页面置换来高效利用有限的物理内存。

1 基本概念

1.1 传统存储管理方式特征和缺点

在虚拟内存出现之前,操作系统采用连续分配非连续分配(基本分页/分段)​ 的方式来管理内存。这些方式有一个共同的假设:一个作业必须全部装入内存才能开始运行,并且一旦装入就一直驻留在内存中直到结束

传统的两种分配方式有两个根本性的问题:

  • 一次性:作用大小受限于物理内存容量。如果一个程序需要8GB才能运行,而机器只有4GB,那就无法运行——哪怕程序在实际执行中只访问了其中很小一部分代码和数据
  • 驻留性:一旦作业被装入内存,就会一直在内存中,直至作业运行结束。这导致了严重的资源浪费——内存中有大量“冷数据”占据着宝贵的页框。

面试视角:传统方式的这两个缺点,直接催生了虚拟内存的思想。面试官可能会问:“为什么早期的操作系统不支持运行比物理内存大的程序?”——答案就在“一次性”和“驻留性”上。

1.2 局部性原理

虚拟内存之所以可行,是因为程序在执行上存在一种规律:局部性原理

  • 时间局部性:一条指令或者一个数据项被访问后,不久很可能再次被访问。典型场景:循环、递归、计数器变量
  • 空间局部性:一个内存地址被访问后,附近的地址也很快会被访问。典型场景:数组遍历,顺序执行的指令流

基于局部性原理,我们可以得出一个重要推论:程序在任意时刻,只需要访问其全部代码和数据的一小部分。也就是说,没有必要把整个程序都放在内存里,只要把当前正在使用的部分(即“工作集”)装入内存,其余部分留在磁盘上,用到时再调入即可。

面试视角:“虚拟内存的理论基础是什么?”——答案是局部性原理。面试官还可能追问:“如果没有局部性,虚拟内存还能工作吗?”——理论上可以,但效率极低,会频繁缺页,导致系统陷入抖动。

1.3 虚拟内存定义和特征

虚拟内存是一种内存管理技术,它将进程使用的逻辑地址与实际的物理地址分离,使得每个进程都拥有一个连续的、私有的地址空间,其大小可以远远超过物理内存的容量。

具体来说

  • 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序执行
  • 程序执行过程中,当所访问的信息不存在时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序
  • 若内存空间不够,由操作系统使用一些算法将暂时不用到的信息换出外存

因此在用户看来似乎有一个比实际内存大的多的内存,这就是虚拟内存

虚拟内存有三个核心特征:

  • 多次性:无需在一次作业时一次性全部装入内存,而是允许被分成多次调入内存
  • 对换性:在作业运行时无需一直常驻在内存,而是允许作业在运行过程中将作业换入换出
  • 虚拟性:从逻辑上扩充了容量,使用户看到的内存容量远大于实际的容量

面试视角:“虚拟内存的最大容量由什么决定?”——由地址总线位数外存容量共同决定。32 位系统最大虚拟地址空间为 4GB,64 位系统理论上可达 256TB(实际受硬件和操作系统限制)。但如果磁盘空间不足,即使地址空间足够,也无法容纳那么多页面。

1.4 如何实现虚拟内存技术

虚拟内存的实现依赖于以下三个机制的协同工作:

  1. 请求分页(或请求分段)管理方式:在基本分页的基础上,增加了按需调页的能力。只有当程序访问到某个页面时,才通过缺页中断将该页从磁盘加载到内存。这是虚拟内存的核心执行机制。
  2. 页面置换算法:当物理内存已满,而又需要调入一个新页面时,必须选择内存中的一个页面换出到磁盘。如何选择被换出的页面,直接影响系统的缺页率和性能。常见的算法有 OPT、FIFO、LRU、CLOCK 等。
  3. 页表机制(扩展):为了实现请求分页,页表需要增加额外的标志位:
  • 状态位:标识该页是否已在内存中。
  • 访问位:记录该页是否被访问过,供置换算法参考。
  • 修改位:记录该页是否被修改过(脏页),换出时是否需要写回磁盘。
  • 外存地址:记录该页在磁盘上的存放位置。

这三者的关系可以概括为一句话:请求分页提供了“怎么调入”,页面置换算法决定了“怎么换出”,页表机制提供了“怎么知道页在哪”。后面的三章将逐一深入展开。

面试视角:“实现虚拟内存需要哪些硬件和软件支持?”——硬件方面需要 MMU(内存管理单元)和 TLB;软件方面需要请求分页的页表机制、缺页中断处理程序、页面置换算法,以及磁盘 I/O 驱动。

2 请求分页管理方式

请求分页(Demand Paging)是实现虚拟内存的核心机制。它与基本分页管理的本质区别在于:页面不是在进程创建时全部装入,而是在实际访问时才通过缺页中断调入本章围绕三个关键环节展开:页表如何支持按需调页、缺页中断的处理流程、以及地址变换的全过程。

2.1 页表机制

基本分页的页表只需要记录页框号(物理地址映射),但请求分页的页表需要额外信息来支持按需调页和页面置换。每个页表项的结构如下:

字段

作用

页框号

该页对应的物理页框号(仅在状态位为1时有效)

状态位(存在位)

标识该页是否在内存中。0表示不在内存(需要从磁盘调入),1表示在内存

访问位(引用位)

记录该页是否被访问过,用于页面置换算法(如CLOCK)判断使用频率

修改位(脏位)

记录该页是否被修改过。如果为1,换出时需要写回磁盘;为0则可直接丢弃(若磁盘副本未过期)

外存地址

该页在磁盘上的存放位置(交换区或文件系统中的偏移量)

面试视角:为什么需要修改位?——如果页面未被修改,换出时无需写回磁盘,减少I/O开销。这也是CLOCK改进算法考虑修改位的原因。

此外,为了加速地址变换,现代CPU引入了快表(TLB)。TLB是页表的高速缓存,存储最近使用的页表项。请求分页下的TLB项也需要包含状态位等信息,以便快速判断页面是否在内存。

2.2 缺页中断机构

缺页中断是请求分页的核心事件。当CPU试图访问一个页表项状态位为0的虚拟地址时,MMU无法完成地址转换,触发缺页异常(Page Fault)。这是一个内中断(异常),发生在指令执行期间,并且可能在同一指令中多次发生(例如跨页的指令)。

缺页中断的处理流程如下:

  1. 保护现场:CPU保存当前进程的上下文(程序计数器、寄存器等),切换到内核态。
  2. 查找外存:根据页表中的外存地址,确定所需页面在磁盘上的位置。
  3. 分配页框:在物理内存中寻找一个空闲页框。
    1. 如果有空闲页框,直接分配。
    2. 如果没有空闲页框,则调用页面置换算法选择一个牺牲页框,将其换出(若脏则写回磁盘)。
  4. 读入页面:启动磁盘I/O,将所需页面从磁盘读入刚分配的页框。此过程进程通常会阻塞(等待I/O完成)。
  5. 更新页表:修改页表项的状态位为1,填入页框号,清除访问位和修改位(通常)。
  6. 恢复现场:返回到用户态,重新执行引发缺页的那条指令。

注意事项

  • 缺页中断是可恢复的异常,处理完成后会重新执行原指令,而非下一条指令。
  • 缺页处理期间,操作系统可能会调度其他进程运行(因为磁盘I/O耗时较长),以提高CPU利用率。

面试视角:“缺页中断和普通中断有什么区别?”——缺页中断发生在指令执行期间(而非指令结束后),且同一指令可能触发多次缺页(如访问跨页数据);处理完成后需要重新执行原指令,而非继续下一条。

2.3 地址变换机构

第一部分:硬件地址变换流程

  1. CPU生成逻辑指令:CPU执行指令,计算出一个逻辑地址(比如0x0804a100)
  2. 提取页号与页内偏移量:MMU将逻辑地址分为页号P和页内偏移量W
  3. 查询快表:MMU首先在TLB快表中查询页号
    1. 命中则直接获得对于的物理块号和权限位,跳转到步骤8
    2. 如果没有命中,则进入步骤4
  4. 查询页表:页号P作为索引,查找当前进程的页表(其起始地址在页表寄存器PTBR中)
  5. 检查页表:找到页表项后,硬件检测其中的有效位/存在位
    1. 如果为1,表示该页已调入内存。页表项中存放的就是物理块号f。继续步骤6。
    2. 如果为0,表示该页未存在于内存中。需要硬件触发缺页中断,CPU暂停当前进程,转而向操作系统的缺页中断处理程序(见第二部分)
  6. 更新页表TLB:将页表查到的(页号P,物理块号f)映射写入TLB方便下次快速访问
  7. 形成物理地址:将物理块号f与页内偏移W拼接,得到物理地址。
  8. 访问内存:这个物理地址访问物理内存,完成读/写操作。

第二部分:操作系统缺页中断处理流程

  1. 保护现场:硬件自动将当前进程的CPU状态(程序计数器PC、寄存器等)保存到内核栈。
  2. 分析原因:OS根据出错的逻辑地址,解析出缺失的页号P,并判断是否为非法访问(例如,访问不存在的页或权限不足)。如果是非法访问,则终止进程。
  3. 寻址空闲页框:OS试图在物理内存中找到一个空闲的物理页框。如果有则直接使用,如果没有则需要页面置换算法选择内存中的页作为“牺牲页”将其换出
  4. 页面换出(如果需要置换)
    1. 检查牺牲页是否被修改过(检查页表项中的修改位)。
    2. 如果修改位=1,说明该页被写过,必须将其写回外存
    3. 如果未0,说明该页干净(与磁盘一致),则直接丢弃
    4. 更新牺牲页的页表项,将其“存在位”置为0
  5. 页面换入。根据缺页页号P,OS从磁盘(可执行文件或交换区)中找到该页的数据,将其读入到步骤3准备好的空闲页框中。这是一个耗时的I/O操作,OS回将当前阻塞,调度其他进程运行
  6. 更新页表:页读入完成后,OS更新缺页的P的页表项
    1. 物理块号:设置为该空闲页框的号
    2. 存在位:置为1
    3. 修改为:置为0
    4. 访问位:置为1
  7. 恢复并重启指令。
    1. OS将被阻塞的进程状态改为就绪。
    2. 当该进程再次被调度时,CPU从之前保存的现场恢复,重新执行那条导致缺页的指令。
    3. 这次执行,硬件再次进行第一部分的地址变换。由于页已在内存,TLB未命中后会查询页表,此时存在位=1,于是成功得到物理地址,指令得以完成。

面试视角:“为什么缺页处理后要重新执行指令,而不是继续下一条?”——因为引发缺页的指令还没有真正执行(数据未读到),必须重新执行才能完成预期的内存访问。这也意味着指令必须是“可重启的”,设计CPU时要考虑这一点。

请求分页通过扩展页表机制、引入缺页中断,实现了“用到才加载”的按需调页。地址变换机构在TLB和慢表的配合下,尽可能快地完成虚拟地址到物理地址的转换,仅在缺页时才付出磁盘I/O的代价。下一章将讨论当物理内存不足时,如何选择被换出的页面——即页面置换算法。

3 页面置换算法

3.1 最佳置换算法(OPT)

  • 思想:置换在未来最长时间内不再被访问的页。是一种理想化思想,无法实现,但作为其他算法的benchmark
  • 操作:当需要置换时,查看当前内存中所有页面在未来访问序列的位置,选择那个下一次访问距离当前最远的页面淘汰
  • 优点:理论上缺页率最低
  • 缺点:无法实现,因为无法预知未来的页面访问序列
  • 作用:作为性能比较的理论下届

3.2 先进先出置换算法(FIFO)

  • 思想:置换在内存中驻留时间最长的页面,即最早进入内存的页面
  • 操作:将内存中的页面按进入顺序链成一个队列。缺页时,淘汰队首页面;调入新页时,新页加到队尾
  • 优点:实现简单,开销小
  • 缺点:性能差,与进程实际运行规律不符(常访问的页面也可能被淘汰)
  • 作用:可能出现 Belady 异常,即分配的物理块数增加时,缺页率反而上升。这是FIFO独有的坏特性。

3.3 最近最久未使用置换算法(LRU)

  • 思想:置换最长时间没有被访问的页面。它基于“局部性原理”:过去一段时间内未使用的页面,在将来也可能不会使用。
  • 实现
    • 计数器/时间戳:为每个页表项关联一个上次访问的时间戳。缺页时,扫描所有页面,选择时间戳最小的页面淘汰。开销大
    • 移位寄存器:周期性将访问位右移,并结合一个寄存器记录访问历史。精度较差。
    • 硬件栈(理想化):将访问的页号压入栈顶,栈中始终保持按访问时间排序,栈底即LRU页。硬件实现代价高。
  • 优点:性能接近OPT,是实际可用的最佳近似算法之一
  • 缺点:真正的LRU需要硬件全力支持,软件精确实现开销巨大

3.4 时钟置换算法(CLOCK)

  • 思想:LRU的低成本近似。它为每个页面设置一个访问位。当页面被访问时,硬件将其访问位置1。操作系统周期性将所有访问位置0。置换时,像时钟指针一样扫描页面,寻找访问位为0的页面淘汰
  • 操作:
    • 页面组织成一个环形链表,一个“时钟指针”指向下一个候选页面。
    • 当需要置换时,检查指针指向页面的访问位
      • 如果为 0,则淘汰该页,新页插入到此位置,指针前进一步。
      • 如果为 1,则将该位置 0(给它一次机会),指针前进一步,继续检查下一个页面。
  • 改进型CLOCK:考虑页面是否被修改过(脏位)
    • 淘汰顺序为:1. (访问位=0, 修改位=0) ->2. (访问位=0, 修改位=1) -> 3. (访问位=1, 修改位=0) -> 4. (访问位=1, 修改位=1)。
  • 优点:开销小,性能接近LRU,是实际系统中广泛采用的算法(如Linux的近似LRU)

4 页面分配策略

前三章讨论了“页面如何调入”和“页面如何换出”,但还有一个关键问题没有回答:操作系统应该给每个进程分配多少个物理页框?当发生缺页时,应该从所有进程中选页换出,还是只从缺页进程自身选?​ 这就是页面分配策略要解决的问题。合理的分配策略能有效降低缺页率、防止系统性能下降(抖动),是多道程序环境下虚拟内存管理的关键。

4.1 概念

在讨论具体策略之前,先明确几个核心术语:

  • 驻留集(Resident Set)一个进程当前驻留在物理内存中的页面集合。驻留集的大小决定了该进程能同时使用的物理页框数。
  • 最小页框数:保证进程正常运行所需的最少页框数。如果分配给进程的页框数少于这个值,进程会因为频繁缺页而无法推进(例如,一条指令可能涉及多个跨页的操作数,需要同时驻留多个页面)。最小页框数由计算机体系结构决定(如指令长度、寻址方式),通常为 2~3 页。
  • 工作集(Working Set):进程在当前一段时间内频繁访问的页面集合。这个概念将在 4.5 节详细展开。

面试视角:“最小页框数和驻留集有什么区别?”——最小页框数是保证进程能执行的下限,由硬件决定;驻留集是操作系统实际分配给进程的页框数,可以动态调整,但不能低于最小页框数。

4.2 分配数量策略

根据驻留集在进程生命周期内是否变化,分配数量策略分为两类:

  • 固定分配(Fixed Allocation)
    • 做法:进程创建时分配固定数量的页框,运行期间保持不变
    • 优点:实现简单,管理开销低
    • 缺点:无法适应程序的阶段性变化。如果分配过多,内存浪费;分配过少,缺页率高,可能引发抖动
    • 适用场景:早期简单系统或嵌入式环境
  • 可变分配(Variable Allocation)
    • 做法:进程的驻留集大小随运行情况动态调整。缺页率高时增加分配,内存紧张时减少分配
    • 优点:更符合程序的局部性特征——程序在不同阶段的页面访问模式不同,需要的内存页框数也不同
    • 缺点:实现复杂,需要监测缺页率并做出调整决策
    • 适用场景:现代通用操作系统(如 Linux、Windows)

面试视角:“为什么可变分配优于固定分配?”——程序的局部性是动态变化的(例如循环阶段需要较多页框,顺序执行阶段需要较少),固定分配无法适应这种变化,要么浪费要么不足。

4.3 置换范围策略

当发生缺页且内存无空闲页框时,操作系统需要选择一个页面换出。这个选择范围决定了置换策略:

  • 全局置换(Global Replacement)
    • 做法:从整个系统的所有内存页面中选择一个换出,不限于是缺页进程自身的页面。
    • 优点:灵活性高,系统可以根据整体负载动态调整内存分配。
    • 缺点:一个进程的缺页可能导致其他进程的页面被换出,造成进程间相互影响。极端情况下,一个失控的进程可能“抢占”其他进程的内存,导致系统不稳定。
    • 典型实现:Linux 的页面回收机制(kswapd)本质上是一种全局置换。
  • 局部置换(Local Replacement)
    • 做法:只从缺页进程自身的驻留集中选择一个页面换出。
    • 优点:进程间隔离性好,一个进程的缺页行为不会影响其他进程。
    • 缺点:灵活性差。如果某个进程的驻留集过小,它只能在自身有限的页面中反复换入换出,无法借用其他进程的空闲内存。
    • 典型实现:早期 Unix 系统的一些版本。

面试视角:“全局置换有什么潜在风险?”——进程间相互影响,可能导致一个进程“饿死”其他进程。例如,一个内存泄漏的进程不断触发缺页,持续从其他进程抢走页面,导致系统整体性能下降。

4.4 组合策略与对比

将分配数量策略与置换范围策略组合,可以得到四种可能的策略:

组合

分配策略

置换策略

特点

典型场景

固定分配 + 局部置换

固定

局部

最简单,每个进程独立管理自己的页框,互不影响

嵌入式系统、早期 OS

固定分配 + 全局置换

固定

全局

矛盾组合:页框数固定却允许全局置换,不合理,实践中罕见

几乎不用

可变分配 + 局部置换

可变

局部

每个进程的驻留集可调,但置换不出本进程范围;需要操作系统持续监控并调整分配

部分实时系统

可变分配 + 全局置换

可变

全局

最灵活,也是最常用的策略。系统根据整体负载动态调整分配,缺页时从全局选页换出

现代通用 OS(Linux、Windows)

为什么“可变分配 + 全局置换”成为主流?

  • 系统可以动态平衡各进程的内存需求:活跃进程的缺页率下降时,可以保留更多页面;不活跃进程的页面自然会被其他进程置换出去。
  • 全局置换配合工作集模型,可以有效防止抖动(见 4.5 节)。

潜在问题:全局置换下,如果不对进程的行为做任何限制,一个高缺页率的进程可能会过度抢占内存。为此,现代操作系统引入了页面老化机制内存控制组(memory cgroup)​ 来限制单个进程的内存使用上限。

面试视角:“为什么固定分配 + 全局置换不合理?”——固定分配意味着每个进程的页框数固定,但全局置换允许从任何进程换出页面,这会导致某个进程的页框数被动减少,破坏了“固定”的承诺。

4.5 抖动与工作集

抖动(Thrashing)​ 是指系统中频繁发生缺页中断,导致 CPU 大部分时间花费在页面换入换出上,而非执行用户程序的现象。表现为:CPU 利用率很低,但磁盘 I/O 非常高

抖动成因

抖动的根本原因是:进程的驻留集小于其工作集

  • 工作集(Working Set)是进程在当前一段时间内(通常定义为窗口时间 Δ)实际访问的页面集合。
  • 当驻留集大小小于工作集大小时,进程在短时间内会反复缺页——刚换出一个页面,马上又要访问它,导致频繁的换入换出。

举个例子:一个进程在时间窗口 Δ 内需要访问页面 {A, B, C, D}(工作集大小为 4),但操作系统只给它分配了 3 个页框(驻留集大小为 3)。那么无论怎么置换,总会有一个页面缺失,导致进程不断缺页。

如何检测抖动

  • CPU 利用率下降:由于大量时间等待磁盘 I/O,CPU 空闲时间增加。
  • 磁盘 I/O 显著升高:页面换入换出频繁。
  • 缺页率急剧上升:可通过 /proc/vmstat中的 pgfault指标观察。

防止抖动方法

  1. 调整驻留集大小:通过监测缺页率,动态增加或减少进程的页框分配。缺页率高于阈值则增加分配,低于阈值则回收多余页面。
  2. 挂起部分进程:当系统整体内存不足时,选择挂起一个或多个进程(将其所有页面换出),释放内存给活跃进程。挂起的进程通常是优先级低或内存占用大的。
  3. 工作集模型:操作系统跟踪每个进程的工作集,确保驻留集不小于工作集。但这需要硬件支持(如引用位)和定期采样,实现成本较高。

工作集模型由 Peter Denning 在 1968 年提出,核心思想是:

  • 定义一个滑动窗口 Δ(如 10,000 次内存访问)。
  • 统计当前窗口内访问过的页面集合,即为工作集。
  • 操作系统确保每个进程的驻留集大小不低于其工作集大小。
  • 如果所有进程的工作集之和超过了物理内存总量,则必须挂起部分进程。

面试视角:“抖动的本质是什么?”——本质是进程的驻留集小于工作集,导致缺页率超过临界值,系统陷入“换页 → 执行一点点 → 又缺页”的死循环。面试官可能追问:“如何确定工作集的大小?”——通过采样页表的访问位,在一个时间窗口内统计被访问的页面数。

5 内存映射文件

5.1 定义

内存映射文件是指将一个磁盘文件(或匿名内存区域)的全部或部分内容映射到进程的虚拟地址空间中。映射完成后,应用程序可以通过指针直接访问这段虚拟内存,而无需显式调用 read/ write等系统调用。操作系统负责在背后完成数据的按需加载(缺页时从磁盘读入)和脏页的回写(通过 msync或内核自动刷新)。

在 Linux 中,内存映射通过 mmap系统调用实现

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • fd:待映射文件的文件描述符(若为 -1且指定 MAP_ANONYMOUS,则为匿名映射,用于分配大块内存)
  • prot:保护权限(PROT_READ、PROT_WRITE等)
  • flags:映射类型(MAP_SHARED表示对映射区的修改会写回文件;MAP_PRIVATE表示写时复制,修改不写回文件)

映射成功后,mmap返回映射区域的起始虚拟地址。此后对该地址范围内的读写,会触发缺页中断,由内核自动从文件中读取相应页框

面试视角:“mmap返回后,物理内存中立即有数据吗?”——不一定。mmap只是建立了虚拟地址到文件的映射关系,并未实际加载任何数据。只有当程序首次访问映射区域时,才会触发缺页中断,从磁盘读入相应的页面

5.2 与传统IO对比

传统文件 I/O(read/ write)与内存映射文件在数据路径上有本质区别:

传统I/O流程:磁盘 → 内核缓冲区(页缓存) → 用户缓冲区 → 应用程序

  • 用户调用 read时,数据先从磁盘读入内核的页缓存(Page Cache),再从页缓存拷贝到用户指定的缓冲区。这个过程涉及两次数据拷贝(磁盘→内核,内核→用户),且上下文切换开销较大。
  • 写操作类似:用户缓冲区 → 内核页缓存 → 磁盘(延迟写)

内存映射文件的数据流:磁盘 → 内核页缓存(直接映射到用户地址空间) → 应用程序直接访问

  • mmap将文件映射到进程地址空间,内核页缓存中的页面直接映射到用户空间(通过缺页机制)。应用程序访问映射区域时,直接操作内核页缓存,无需额外的数据拷贝。
  • 写操作直接修改页缓存中的页面,由内核在适当时机(或用户调用 msync)写回磁盘。

维度

传统 I/O(read/write)

内存映射文件(mmap)

数据拷贝次数

2 次(磁盘→内核→用户)

0 次(直接操作页缓存)

系统调用次数

每次读写均需系统调用

映射后无需系统调用(缺页除外)

随机访问性能

差(每次随机读都需要系统调用+拷贝)

好(缺页后直接访问内存)

顺序读性能

较好(预读机制成熟)

可能因缺页抖动而略逊

共享内存

不支持(需额外 IPC 机制)

支持(MAP_SHARED多进程共享同一文件映射)

文件大小限制

无特殊限制

受虚拟地址空间大小限制(32 位系统 4GB)

5.3 主要应用场景

  1. 动态链接库/共享库加载:.so/ .dll文件被映射到进程空间,实现代码共享
  2. 大型数据文件处理
  3. 进程间通信:通过映射同一个文件,多个进程可以在共享的内存区域直接交换数据,这是共享内存IPC的一种高效实现
  4. 实现“零拷贝”:网络服务器(如Nginx)可以用mmap将文件内容映射到内存,然后直接用writev或splice等系统调用发送,避免内核到用户空间的拷贝

零拷贝机制

  • 传统的文件传输read+write:4次内核/用户态切换,4次拷贝

  • 零拷贝mmap+write:4次内核/用户态切换,3次拷贝

  • 零拷贝sendfile:2次内核/用户态切换,3次拷贝

  • 零拷贝sendfile+SG-DMA:2次内核/用户态切换,2次拷贝

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐