操作系统内存管理 —— 虚拟内存
本篇面向已经了解以下概念的读者:
- 物理内存的分页管理(什么是页框、页表、多级页表)
- MMU 和 TLB 的基本工作原理
- 进程地址空间的布局(代码段、数据段、堆、栈)
如果你对这些还不太熟悉,建议先翻阅《深入理解计算机系统》第9章或《现代操作系统》第3章,再回来看本篇会更顺畅。
为什么还要谈虚拟内存?
物理内存管理解决了“如何分配和回收物理页框”的问题,但它有几个天生的局限:
- 碎片化:即使有伙伴系统和 slab,长期运行后仍可能出现外部碎片,导致大块连续物理内存难以分配。
- 进程隔离:每个进程直接操作物理地址,既不安全(一个进程可以篡改另一个进程的数据),也不灵活(进程地址空间大小受限于物理内存)。
- 程序大小受限:一个程序如果想运行,其代码和数据必须全部加载到物理内存中,否则无法执行。这在早期计算机中是一个严重瓶颈。
虚拟内存正是为了解决这些问题而诞生的。它在进程和物理内存之间增加了一层抽象——虚拟地址空间,让每个进程拥有独立的、连续的、大小远超物理内存的地址空间,并通过按需调页和页面置换来高效利用有限的物理内存。
1 基本概念
1.1 传统存储管理方式特征和缺点
在虚拟内存出现之前,操作系统采用连续分配或非连续分配(基本分页/分段) 的方式来管理内存。这些方式有一个共同的假设:一个作业必须全部装入内存才能开始运行,并且一旦装入就一直驻留在内存中直到结束。
传统的两种分配方式有两个根本性的问题:
- 一次性:作用大小受限于物理内存容量。如果一个程序需要8GB才能运行,而机器只有4GB,那就无法运行——哪怕程序在实际执行中只访问了其中很小一部分代码和数据
- 驻留性:一旦作业被装入内存,就会一直在内存中,直至作业运行结束。这导致了严重的资源浪费——内存中有大量“冷数据”占据着宝贵的页框。
面试视角:传统方式的这两个缺点,直接催生了虚拟内存的思想。面试官可能会问:“为什么早期的操作系统不支持运行比物理内存大的程序?”——答案就在“一次性”和“驻留性”上。
1.2 局部性原理
虚拟内存之所以可行,是因为程序在执行上存在一种规律:局部性原理
- 时间局部性:一条指令或者一个数据项被访问后,不久很可能再次被访问。典型场景:循环、递归、计数器变量
- 空间局部性:一个内存地址被访问后,附近的地址也很快会被访问。典型场景:数组遍历,顺序执行的指令流
基于局部性原理,我们可以得出一个重要推论:程序在任意时刻,只需要访问其全部代码和数据的一小部分。也就是说,没有必要把整个程序都放在内存里,只要把当前正在使用的部分(即“工作集”)装入内存,其余部分留在磁盘上,用到时再调入即可。
面试视角:“虚拟内存的理论基础是什么?”——答案是局部性原理。面试官还可能追问:“如果没有局部性,虚拟内存还能工作吗?”——理论上可以,但效率极低,会频繁缺页,导致系统陷入抖动。
1.3 虚拟内存定义和特征
虚拟内存是一种内存管理技术,它将进程使用的逻辑地址与实际的物理地址分离,使得每个进程都拥有一个连续的、私有的地址空间,其大小可以远远超过物理内存的容量。
具体来说
- 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序执行
- 程序执行过程中,当所访问的信息不存在时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序
- 若内存空间不够,由操作系统使用一些算法将暂时不用到的信息换出外存
因此在用户看来似乎有一个比实际内存大的多的内存,这就是虚拟内存
虚拟内存有三个核心特征:
- 多次性:无需在一次作业时一次性全部装入内存,而是允许被分成多次调入内存
- 对换性:在作业运行时无需一直常驻在内存,而是允许作业在运行过程中将作业换入换出
- 虚拟性:从逻辑上扩充了容量,使用户看到的内存容量远大于实际的容量
面试视角:“虚拟内存的最大容量由什么决定?”——由地址总线位数和外存容量共同决定。32 位系统最大虚拟地址空间为 4GB,64 位系统理论上可达 256TB(实际受硬件和操作系统限制)。但如果磁盘空间不足,即使地址空间足够,也无法容纳那么多页面。
1.4 如何实现虚拟内存技术
虚拟内存的实现依赖于以下三个机制的协同工作:
- 请求分页(或请求分段)管理方式:在基本分页的基础上,增加了按需调页的能力。只有当程序访问到某个页面时,才通过缺页中断将该页从磁盘加载到内存。这是虚拟内存的核心执行机制。
- 页面置换算法:当物理内存已满,而又需要调入一个新页面时,必须选择内存中的一个页面换出到磁盘。如何选择被换出的页面,直接影响系统的缺页率和性能。常见的算法有 OPT、FIFO、LRU、CLOCK 等。
- 页表机制(扩展):为了实现请求分页,页表需要增加额外的标志位:
- 状态位:标识该页是否已在内存中。
- 访问位:记录该页是否被访问过,供置换算法参考。
- 修改位:记录该页是否被修改过(脏页),换出时是否需要写回磁盘。
- 外存地址:记录该页在磁盘上的存放位置。

这三者的关系可以概括为一句话:请求分页提供了“怎么调入”,页面置换算法决定了“怎么换出”,页表机制提供了“怎么知道页在哪”。后面的三章将逐一深入展开。
面试视角:“实现虚拟内存需要哪些硬件和软件支持?”——硬件方面需要 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)。这是一个内中断(异常),发生在指令执行期间,并且可能在同一指令中多次发生(例如跨页的指令)。
缺页中断的处理流程如下:
- 保护现场:CPU保存当前进程的上下文(程序计数器、寄存器等),切换到内核态。
- 查找外存:根据页表中的外存地址,确定所需页面在磁盘上的位置。
- 分配页框:在物理内存中寻找一个空闲页框。
- 如果有空闲页框,直接分配。
- 如果没有空闲页框,则调用页面置换算法选择一个牺牲页框,将其换出(若脏则写回磁盘)。
- 读入页面:启动磁盘I/O,将所需页面从磁盘读入刚分配的页框。此过程进程通常会阻塞(等待I/O完成)。
- 更新页表:修改页表项的状态位为1,填入页框号,清除访问位和修改位(通常)。
- 恢复现场:返回到用户态,重新执行引发缺页的那条指令。
注意事项:
- 缺页中断是可恢复的异常,处理完成后会重新执行原指令,而非下一条指令。
- 缺页处理期间,操作系统可能会调度其他进程运行(因为磁盘I/O耗时较长),以提高CPU利用率。
面试视角:“缺页中断和普通中断有什么区别?”——缺页中断发生在指令执行期间(而非指令结束后),且同一指令可能触发多次缺页(如访问跨页数据);处理完成后需要重新执行原指令,而非继续下一条。
2.3 地址变换机构
第一部分:硬件地址变换流程
- CPU生成逻辑指令:CPU执行指令,计算出一个逻辑地址(比如0x0804a100)
- 提取页号与页内偏移量:MMU将逻辑地址分为页号P和页内偏移量W
- 查询快表:MMU首先在TLB快表中查询页号
- 命中则直接获得对于的物理块号和权限位,跳转到步骤8
- 如果没有命中,则进入步骤4
- 查询页表:页号P作为索引,查找当前进程的页表(其起始地址在页表寄存器PTBR中)
- 检查页表:找到页表项后,硬件检测其中的有效位/存在位
- 如果为1,表示该页已调入内存。页表项中存放的就是物理块号f。继续步骤6。
- 如果为0,表示该页未存在于内存中。需要硬件触发缺页中断,CPU暂停当前进程,转而向操作系统的缺页中断处理程序(见第二部分)
- 更新页表TLB:将页表查到的(页号P,物理块号f)映射写入TLB方便下次快速访问
- 形成物理地址:将物理块号f与页内偏移W拼接,得到物理地址。
- 访问内存:这个物理地址访问物理内存,完成读/写操作。
第二部分:操作系统缺页中断处理流程
- 保护现场:硬件自动将当前进程的CPU状态(程序计数器PC、寄存器等)保存到内核栈。
- 分析原因:OS根据出错的逻辑地址,解析出缺失的页号P,并判断是否为非法访问(例如,访问不存在的页或权限不足)。如果是非法访问,则终止进程。
- 寻址空闲页框:OS试图在物理内存中找到一个空闲的物理页框。如果有则直接使用,如果没有则需要页面置换算法选择内存中的页作为“牺牲页”将其换出
- 页面换出(如果需要置换)
- 检查牺牲页是否被修改过(检查页表项中的修改位)。
- 如果修改位=1,说明该页被写过,必须将其写回外存
- 如果未0,说明该页干净(与磁盘一致),则直接丢弃
- 更新牺牲页的页表项,将其“存在位”置为0
- 页面换入。根据缺页页号P,OS从磁盘(可执行文件或交换区)中找到该页的数据,将其读入到步骤3准备好的空闲页框中。这是一个耗时的I/O操作,OS回将当前阻塞,调度其他进程运行
- 更新页表:页读入完成后,OS更新缺页的P的页表项
- 物理块号:设置为该空闲页框的号
- 存在位:置为1
- 修改为:置为0
- 访问位:置为1
- 恢复并重启指令。
- OS将被阻塞的进程状态改为就绪。
- 当该进程再次被调度时,CPU从之前保存的现场恢复,重新执行那条导致缺页的指令。
- 这次执行,硬件再次进行第一部分的地址变换。由于页已在内存,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指标观察。
防止抖动方法
- 调整驻留集大小:通过监测缺页率,动态增加或减少进程的页框分配。缺页率高于阈值则增加分配,低于阈值则回收多余页面。
- 挂起部分进程:当系统整体内存不足时,选择挂起一个或多个进程(将其所有页面换出),释放内存给活跃进程。挂起的进程通常是优先级低或内存占用大的。
- 工作集模型:操作系统跟踪每个进程的工作集,确保驻留集不小于工作集。但这需要硬件支持(如引用位)和定期采样,实现成本较高。
工作集模型由 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 机制) |
支持( |
|
文件大小限制 |
无特殊限制 |
受虚拟地址空间大小限制(32 位系统 4GB) |
5.3 主要应用场景
- 动态链接库/共享库加载:.so/ .dll文件被映射到进程空间,实现代码共享
- 大型数据文件处理
- 进程间通信:通过映射同一个文件,多个进程可以在共享的内存区域直接交换数据,这是共享内存IPC的一种高效实现
- 实现“零拷贝”:网络服务器(如Nginx)可以用mmap将文件内容映射到内存,然后直接用writev或splice等系统调用发送,避免内核到用户空间的拷贝
零拷贝机制:
- 传统的文件传输read+write:4次内核/用户态切换,4次拷贝

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

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

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

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