想象你走进一家城市图书馆:前台只摆着几十本热门书,真正的海量馆藏躺在三层楼以下的地下书库。你要借一本《操作系统设计与实现》,前台没有,于是按响服务铃。这一声铃,就是一次“缺页中断”。CPU 访问虚拟地址时,内存管理单元 MMU 查找页表,发现想找的物理页不在内存中,它无法完成翻译,就向内核发出这个“缺书信号”。操作系统这位图书管理员开始处理:先验证你的借阅资格,再决定是去地下书库取书,还是从旁边白纸堆里抽一张新纸,最后更新索引、把“书”交到你手里。整个过程围绕着“按需分页”展开:书不会提前搬上架,只有你真正要读时才触发取书。

岗位关联

理解缺页中断的触发、分支与性能影响,是排查内存瓶颈、优化数据库与 AI 推理服务内存占用的基本功,也是面试操作系统内核时的高频考点。

目录
  1. 可视化演示
  2. 缺页中断概览
  3. 处理主流程
  4. 匿名页缺页
  5. 文件页缺页
  6. 写时复制
  7. 按需分页
  8. 缺页统计
  9. 预读与换入
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

缺页中断概览

图书管理员最常听到的铃声,不是读者大声喊叫,而是“前台没有这本书”的无声提示。计算机里,CPU 发出虚拟地址后,MMU 会对照页表这本“索引目录”查找物理页。如果页表项显示无效或权限不足,MMU 无法继续翻译,就会像按铃一样向 CPU 抛出缺页异常。这个异常不是程序错误,而是虚拟内存系统“按需取书”的启动信号。

进入内核后,缺页处理的第一步是确认“借书请求是否合法”。内核读取触发缺页的地址,检查它是否落在进程的某个虚拟内存区域 VMA 中,并核对访问类型是读、写还是执行。如果地址非法,进程会收到段错误信号;如果合法,缺页处理才会继续向下走,根据 VMA 类型和页表状态分流到不同分支。

衡量缺页发生频率,需要一个直观指标——缺页率:

图书馆不可能把百万册书都放前台,虚拟内存与物理内存之间也天然存在“缺书”时刻。理解这个信号如何产生,才能继续观察管理员如何响应。

p=NfaultNaccess\displaystyle p = \frac{N_{\text{fault}}}{N_{\text{access}}}
缺页率 p 表示每次内存访问触发缺页的概率。它虽然很小,却是后续所有性能公式的核心输入。
  1. 分子 \(N_{\text{fault}}\)记录考察期间发生缺页中断的次数,相当于管理员按铃的次数。
    NfaultN_{\text{fault}}
  2. 分母 \(N_{\text{access}}\)进程全部内存访问次数,包括命中、未命中,以及缺页后重新执行的访问。
    NaccessN_{\text{access}}
  3. 比值 \(p\)缺页次数占总访问次数的比例,反映系统“缺书”有多频繁。
    p=NfaultNaccess\displaystyle p = \frac{N_{\text{fault}}}{N_{\text{access}}}

处理主流程

管理员听到铃声后,会按一套固定动作处理:登记读者信息、核对借阅证、决定取书路径、更新索引、最后把书交给读者。内核的 do_page_fault 函数正是这套动作的总调度。

do_page_fault 首先保存当前 CPU 现场,防止处理过程中破坏寄存器;接着读取缺页地址和控制信息,判断访问来自用户态还是内核态,再查找 VMA。如果地址非法,直接向进程发送 SIGSEGV。如果合法,根据页表项和 VMA 类型,进入三大分支:匿名页缺页、文件页缺页、写时复制。最后,内核对页表进行修复,刷新 TLB,并返回到触发缺页的那条指令重新执行。因为整个过程对 CPU 透明,应用进程几乎感知不到。

缺页处理对性能影响有多大,可以看有效访问时间模型:

如果管理员每 1000 次借阅就要跑一次地下书库,读者等待时间的期望立刻会被拉高。这也解释了为什么内核要努力把 \(p\) 压到极低。

EAT=(1−p)×tm+p×tf\displaystyle \text{EAT} = (1-p)\times t_m + p\times t_f
EAT 是加了缺页惩罚后的平均内存访问时间。\(t_f\) 尽管出现概率 \(p\) 很小,但数量级远大于 \(t_m\),所以不能忽视。
  1. \(t_m\)页表命中时的一次内存访问耗时,对应前台直接取书的时间。
    tm\displaystyle t_m
  2. \(t_f\)发生一次缺页并完成处理的耗时,可能包含磁盘 I/O,对应跑一趟地下书库。
    tf\displaystyle t_f
  3. 加权平均以 \((1-p)\) 和 \(p\) 为权重,得到平均访问时间的期望值。
    EAT=(1−p)×tm+p×tf\displaystyle \text{EAT} = (1-p)\times t_m + p\times t_f

匿名页缺页

有些“书”其实是空白笔记本——读者第一次打开时,管理员直接从旁边白纸堆抽一张纸放前台,根本不用去地下书库。匿名页缺页正是这种场景:进程第一次访问堆、栈或匿名 mmap 得到的虚拟页时,页表项无效,但背后没有磁盘文件可读。

内核进入匿名页分支后,会从伙伴系统分配一个物理页帧,并把它清成全零,然后建立虚拟页到物理页的映射。由于不涉及磁盘 I/O,这种缺页被归为 minor fault(轻微缺页),耗时只有微秒级。常见触发点包括 malloc 后第一次写内存、函数调用压栈等。

匿名页缺页的开销可以简化为:

空白纸可以现场“造”,所以匿名页缺页很便宜。但这也意味着频繁申请大块内存时,内核只是先记账,后面再一张张补纸。

tanon=talloc+tzero\displaystyle t_{\text{anon}} = t_{\text{alloc}} + t_{\text{zero}}
\(t_{\text{anon}}\) 是一次匿名页缺页的处理时间,通常远小于磁盘 I/O,这也是它属于 minor fault 的原因。
  1. \(t_{\text{alloc}}\)从物理内存分配器获取一个页帧的时间,对应从白纸堆抽纸。
    talloc\displaystyle t_{\text{alloc}}
  2. \(t_{\text{zero}}\)将页帧清零或映射内核零页的时间,保证数据不会泄漏。
    tzero\displaystyle t_{\text{zero}}
  3. 相加两者相加就是匿名页建立的代价。
    tanon=talloc+tzero\displaystyle t_{\text{anon}} = t_{\text{alloc}} + t_{\text{zero}}

文件页缺页

当读者要读《史记》某一页,管理员得判断这本书是否已经放在中转台——也就是页缓存。如果中转台有,直接取过来;如果没有,才需要下到地下书库取原书。文件页缺页处理的就是这种“有源头的书页”。

进程通过 mmap 映射文件、加载动态库、读取配置文件时,都会访问文件页。内核先查询页缓存,若命中则直接把页缓存页映射进进程地址空间,这仍属于 minor fault;若未命中,则向文件系统发起磁盘 I/O,把数据读入页缓存后再映射,这就是 major fault 的主要来源。do_page_fault 的文件页分支会与预读机制联动:既然已经读了一个页,为什么不顺势读入后面几页?

文件页缺页的期望开销:

管理员不会每次取书都下书库;很多书已经通过预读放在中转台,文件页缺页也因此被大大柔化。

tfile=h×tcache+(1−h)×(tdisk+tcache)\displaystyle t_{\text{file}} = h\times t_{\text{cache}} + (1-h)\times (t_{\text{disk}} + t_{\text{cache}})
\(h\) 是页缓存命中率。命中时只需建立映射,未命中时还要叠加磁盘读页时间。
  1. \(t_{\text{cache}}\)页缓存命中后更新页表并返回的时间,相当于从中转台拿书。
    tcache\displaystyle t_{\text{cache}}
  2. \(t_{\text{disk}}\)从磁盘或 SSD 读取一个页的时间,是从地下书库取书的真正成本。
    tdisk\displaystyle t_{\text{disk}}
  3. 加权以命中率 \(h\) 为权重,计算平均文件页缺页时间。
    tfile=h×tcache+(1−h)×(tdisk+tcache)\displaystyle t_{\text{file}} = h\times t_{\text{cache}} + (1-h)\times (t_{\text{disk}} + t_{\text{cache}})

写时复制

两个读者共用一本参考书,前台只放一本,借书卡上写着“只读”。谁想写笔记,管理员才复印一页交给谁——这就是写时复制的思想。Linux 的 fork() 创建子进程时,不会复制父进程所有物理内存,而是复制父进程页表并把这些页标记为只读,父子进程共享同一批物理页。

当任一进程尝试写共享页时,MMU 发现页表项只读,触发写保护缺页。内核识别这是 COW 页后,分配一个新物理页,把旧页内容复制过去,再更新进程页表为可写。整个过程只复制被实际写入的页,没有写操作的页继续共享,因此 fork 后再 exec 的场景尤其高效。

一次 COW 缺页的代价:

参考书借力打力,只有真正动笔才复印,这让 fork 从“复制整座图书馆”变成“复制一张张笔记纸”。

tCOW=talloc+tcopy+tpte\displaystyle t_{\text{COW}} = t_{\text{alloc}} + t_{\text{copy}} + t_{\text{pte}}
COW 缺页需要分配页帧、复制旧页内容、更新页表三个步骤,但仍不需要磁盘 I/O,属于 minor fault。
  1. \(t_{\text{alloc}}\)为新物理页分配页帧,相当于准备一张复印纸。
    talloc\displaystyle t_{\text{alloc}}
  2. \(t_{\text{copy}}\)把旧物理页内容逐字节复制到新页,是 COW 的主要开销。
    tcopy\displaystyle t_{\text{copy}}
  3. \(t_{\text{pte}}\)更新页表项并刷新 TLB,让新页对进程可见。
    tpte\displaystyle t_{\text{pte}}

按需分页

一个学生向图书馆申请“想借整个书架”,管理员不会真的把书架搬来,而只是在借阅卡上记一笔,等他真正伸手拿某一本时才去取。按需分页也是这个道理:应用调用 malloc(1GB) 时,内核只是创建一个 VMA 记录,并不分配物理页;只有进程真正读写某个虚拟页,才会触发缺页并把该页分配出来。

这种 lazy 分配策略让内存的感受占用远小于虚拟内存。稀疏数组、超大预留缓冲区、以及 fork 后立即 exec 的场景,都不必为用不到的页付出物理内存代价。代价则是首次访问新页时多一次缺页,但 minor fault 很便宜,整体收益依然巨大。

评估惰性分配的收益,可以用物理驻留率:

图书馆不会提前把书全部搬上架,操作系统也不会提前分配所有物理页。按需分页,是“先登记、后取书”的精明管理。

R=SresidentSvirtual\displaystyle R = \frac{S_{\text{resident}}}{S_{\text{virtual}}}
驻留率 \(R\) 是实际使用的物理内存与虚拟内存之比。\(R\) 越小,说明按需分配的“省纸”效果越明显。
  1. \(S_{\text{virtual}}\)进程虚拟地址空间大小,对应借阅卡上登记的“可借书架”。
    Svirtual\displaystyle S_{\text{virtual}}
  2. \(S_{\text{resident}}\)实际驻留在物理内存中的页面量,对应真正从书架上取下来的书。
    Sresident\displaystyle S_{\text{resident}}
  3. 比值 \(R\)反映惰性分配后物理内存的利用效率。
    R=SresidentSvirtual\displaystyle R = \frac{S_{\text{resident}}}{S_{\text{virtual}}}

缺页统计

管理员的工作日志会区分两种跑腿:从地下书库取书是“大跑腿”,从旁边中转台或白纸堆拿取是“小跑腿”。Linux 在 /proc/<pid>/stat 中用 majflt 和 minflt 分别统计 major fault 与 minor fault 的次数。

major fault 需要磁盘 I/O,比如从 swap 换入页、从文件系统读页;minor fault 不需要磁盘 I/O,比如匿名页分配零页、页缓存命中、COW 复制。性能分析时,majflt 突然上升往往意味着内存压力变大,甚至出现 swap 抖动;而 minflt 频繁则更多是正常按需分配。

总缺页处理时间可以写成:

通过日志,看门人一眼就能知道系统有多少次“大跑腿”。这也是许多性能监控工具判断内存压力的基础。

T=nmajor×tIO+nminor×tminor\displaystyle T = n_{\text{major}}\times t_{\text{IO}} + n_{\text{minor}}\times t_{\text{minor}}
\(T\) 是统计周期内所有缺页处理的总耗时。major 缺页数量虽少,但每次的 I/O 代价极大。
  1. \(n_{\text{major}}\)主缺页次数,每一次都相当于一次真实磁盘 I/O。
    nmajor\displaystyle n_{\text{major}}
  2. \(t_{\text{IO}}\)单次磁盘 I/O 时间,毫秒级,对应地下书库跑一趟。
    tIO\displaystyle t_{\text{IO}}
  3. \(n_{\text{minor}}\times t_{\text{minor}}\)所有 minor fault 的累计时间,微秒级,通常不是瓶颈。
    nminor×tminor\displaystyle n_{\text{minor}}\times t_{\text{minor}}

预读与换入

管理员跑一趟地下书库不容易,所以常常一次取回附近几页,而不是读者要哪页取哪页。这个策略叫预读(readahead),缺点是可能多读一些用不到的书页,造成“读放大”。内核在文件页缺页和 swap 换入时都会应用类似思想。

当进程顺序读文件时,预读算法会逐渐扩大预读窗口,一次读入更多相邻页;swap 换入时也常按“簇”一次读入多个页,降低磁盘寻道开销。但如果预读窗口过大,读入的数据很多没被使用,反而浪费 I/O 带宽,这就是读放大。内核需要根据访问模式动态调整:顺序访问时加大预读,随机访问时减少预读。

读放大程度用放大系数表示:

管理员一次性带回几页,来回跑腿少了,但也可能多搬几本没人翻的书。预读的智慧,正是在减少跑腿与避免浪费之间找到平衡。

AMP=BreadBused\displaystyle \text{AMP} = \frac{B_{\text{read}}}{B_{\text{used}}}
AMP 是实际读入字节数与真正被使用字节数的比值。AMP 越接近 1,预读越精准。
  1. \(B_{\text{read}}\)从磁盘或 swap 实际读入的字节数,包括预读的“多余书页”。
    Bread\displaystyle B_{\text{read}}
  2. \(B_{\text{used}}\)进程真正访问并使用的字节数,是最终有用的部分。
    Bused\displaystyle B_{\text{used}}
  3. 比值 AMP衡量预读效率,AMP 大于 1 说明存在读放大。
    AMP=BreadBused\displaystyle \text{AMP} = \frac{B_{\text{read}}}{B_{\text{used}}}

AI 应用场景

AI推理服务的内存瓶颈定位

阶段:模型推理性能调优

解决的问题:大模型权重加载和推理过程中,如果服务器发生大量 major fault,请求延迟会剧烈抖动。传统排查只看内存总量,很难判断是 swap 抖动还是页缓存不足。借助 major/minor 缺页统计,可以区分“真 I/O”和“按需分配”,从而针对性地调整内存预留、预处理和数据预取策略。

具体案例:某推理服务在业务高峰时 P99 延迟翻倍,通过读取 /proc/<pid>/stat 的 majflt 发现每秒数百次主缺页,确认是模型权重页被换出。平台工程师将关键页用 mlock 锁定,并提高 page cache 上限,延迟恢复平稳。

容器内存超卖与共享

阶段:集群资源管理

解决的问题:多个 AI 容器运行相同 Python 镜像和模型文件,如果每个容器都独立复制物理内存,内存会迅速耗尽。按需分页和页缓存共享让相同文件页在物理内存中只存一份,所有容器共享映射;fork 出的 Worker 进程则靠写时复制降低内存开销。

具体案例:K8s 节点同时运行 8 个基于同一镜像的推理 Pod,镜像约 2GB。由于页缓存共享,实际物理内存占用远低于 16GB,剩余资源可再调度更多 Pod,节点装箱率明显提升。

大数据 Shuffle 的读放大治理

阶段:训练数据准备

解决的问题:Spark/Flink 的 Shuffle 过程会产生大量临时文件页缺页和磁盘读放大,拖慢 AI 训练前的数据预处理。理解预读和 swap 簇换入机制后,可调整文件系统 readahead 参数,让顺序读更高效,同时避免随机读被无关预读浪费带宽。

具体案例:某推荐系统训练任务每日处理 TB 级日志,Shuffle 阶段磁盘 I/O 飙升。调低随机读场景的预读窗口、提高顺序读的预读量后,ETL 耗时下降约 30%。

Python 演示

python67 行
class PageTableEntry:    def __init__(self):        self.valid = False        self.frame = -1        self.readonly = False class MemorySimulator:    def __init__(self, total_frames=8):        self.frames = [False] * total_frames        self.page_table = {}        self.major_faults = 0        self.minor_faults = 0     def _alloc_frame(self):        for i, used in enumerate(self.frames):            if not used:                self.frames[i] = True                return i        raise MemoryError("no free frame")     def _is_anon(self, addr):        # 地址大于等于 0x100000 视为匿名页        return addr >= 0x100000     def access(self, addr, write=False):        pte = self.page_table.get(addr)        if pte is None or not pte.valid:            self._page_fault(addr, write)            return "page_fault"        if write and pte.readonly:            self._cow_fault(addr)            return "cow_fault"        return "hit"     def _page_fault(self, addr, write):        if self._is_anon(addr):            frame = self._alloc_frame()            self.page_table[addr] = PageTableEntry()            self.page_table[addr].valid = True            self.page_table[addr].frame = frame            self.minor_faults += 1        else:            # 模拟从磁盘读入页,属于 major fault            frame = self._alloc_frame()            self.page_table[addr] = PageTableEntry()            self.page_table[addr].valid = True            self.page_table[addr].frame = frame            self.major_faults += 1     def _cow_fault(self, addr):        old = self.page_table[addr]        new_frame = self._alloc_frame()        old.frame = new_frame        old.readonly = False        self.minor_faults += 1     def mark_readonly(self):        for pte in self.page_table.values():            pte.readonly = True if __name__ == "__main__":    sim = MemorySimulator()    print(sim.access(0x100000, write=False))  # 匿名页缺页    print(sim.access(0x200000, write=False))  # 文件页缺页    sim.mark_readonly()    print(sim.access(0x100000, write=True))   # 写时复制缺页    print("major:", sim.major_faults, "minor:", sim.minor_faults)
目的:用小型模拟器演示匿名页、文件页和写时复制三类缺页的处理分支,并统计 major/minor 缺页次数。
  1. pte = self.page_table.get(addr)相当于 MMU 查询页表项,确认虚拟页是否有映射。
  2. if pte is None or not pte.valid:页表项不存在或无效,触发缺页处理。
  3. if write and pte.readonly:写一个只读页,触发写时复制缺页。
  4. if self._is_anon(addr):判别匿名页还是文件页,决定是“抽白纸”还是“去磁盘取”。
  5. self.minor_faults += 1匿名页分配零页不碰磁盘,计入 minor fault。
  6. self.major_faults += 1文件页需要模拟磁盘读入,计入 major fault。
  7. new_frame = self._alloc_frame()为写时复制分配新物理帧,让进程获得独立副本。

常见误区

误区缺页中断就是把页面从磁盘读进内存,所以只要看到进程有缺页,性能就一定崩到 I/O 级别。

正解缺页只表示页表当前没有有效映射,是否访问磁盘要看类型。匿名页首次访问是分配零页,COW 是内存复制,文件页若命中页缓存也无须磁盘 I/O,这些都属于 minor fault,耗时只有微秒级。只有 major fault 才需要从磁盘或 swap 换入。就像管理员可能从旁边白纸堆直接拿一张,而不是每次都跑地下书库。

为何常见:教材常以“缺页要换入换出”为例,容易让人把缺页和磁盘 I/O 画上等号。

误区程序执行 `malloc(1GB)` 后,任务管理器应该显示进程占用了 1GB 物理内存。

正解现代 malloc 通常通过 brk 或 mmap 扩展虚拟地址空间,内核只为这段区域创建一个 VMA 记录,并不分配物理页。只有进程真正访问某个虚拟页,才触发匿名页缺页并分配物理页。所以申请大内存可能只是虚拟内存膨胀,物理内存占用远小于预期。这就像办了一张“可借整个书架”的卡,但书并没有真的搬上架。

为何常见:任务管理器往往同时显示虚拟内存和常驻内存,初学者容易混淆两者。

误区`fork()` 很慢,因为它要把父进程所有物理内存完整复制一份给子进程。

正解现代 Linux 的 fork() 采用写时复制:子进程复制父进程的页表,所有物理页标记为只读并共享。只有进程真正写某个页时,内核才分配新页并复制内容。因此 fork() 速度快,也不会让物理内存瞬间翻倍。

为何常见:早期 Unix 确实在 fork 时复制全部内存,一些旧教材或博客会让读者误以为现代系统仍然如此。

误区只要看到 `majflt` 很大,就说明系统缺页太频繁,应该让所有页面常驻内存,彻底消灭缺页。

正解major fault 大确实说明很多页需要从磁盘或 swap 换入,往往提示内存压力,需要关注。但 minor fault 是正常且便宜的,不能也不应消灭。调优的目标是降低 major fault,比如增大页缓存、调整预读、避免 swap 抖动,而不是追求“零缺页”。

为何常见:看到 “fault” 就觉得是错误,忽略了 major 与 minor 的巨大代价差异。

面试题

Q1. 缺页中断主要由哪个硬件组件检测并触发?

单选题 · easy

  • A.DMA控制器
  • B.内存管理单元← 正确答案
  • C.中断控制器
  • D.系统读写总线

答案:B。MMU 负责虚拟地址到物理地址的翻译,当页表项无效或权限不足时,它无法完成翻译,于是触发缺页异常。DMA 负责数据搬运,中断控制器负责转发中断,总线只是传输通道,都不是缺页的检测者。

Q2. 缺页发生后,进入内核的典型总入口函数是哪一个?

单选题 · easy

  • A.进程调度函数
  • B.缺页处理函数← 正确答案
  • C.内存释放函数
  • D.文件读取函数

答案:B。Linux 内核缺页处理的核心入口是 do_page_fault,它负责校验地址、判断 VMA 类型并分派给匿名页、文件页或 COW 分支。调度、释放内存和读文件都不是缺页处理的总入口。

Q3. 下列哪类缺页一定需要磁盘 I/O?

单选题 · easy

  • A.次要缺页
  • B.匿名页缺页
  • C.主要缺页← 正确答案
  • D.写时复制缺页

答案:C。major fault 的定义就是从 swap 或文件读入页面,一定包含磁盘 I/O。minor fault 包括匿名页分配零页、页缓存命中、COW 复制等,都不需要访问磁盘。

Q4. 匿名页首次被访问时,内核通常执行什么操作?

单选题 · mid

  • A.从swap区换入
  • B.分配全零页← 正确答案
  • C.从文件读入
  • D.发起网络请求

答案:B。匿名页没有文件背景,首次访问时内核从伙伴系统分配一个物理页并清零,建立映射后返回。这属于 minor fault,不需要磁盘 I/O。

Q5. fork 之后,父子进程共享物理页,页表项被标记为什么?

单选题 · mid

  • A.标记为可写
  • B.标记为只读← 正确答案
  • C.标记为不存在
  • D.标记为大页

答案:B。写时复制要求父子进程先共享物理页,并把页表项设为只读。写操作触发保护缺页后,内核才复制物理页并把对应页表项改为可写。

Q6. 按需分页(lazy allocation)指的是下列哪种行为?

单选题 · mid

  • A.页表存在但物理页空
  • B.只登记不分配物理页← 正确答案
  • C.启动时预加载全部页
  • D.用swap分区扩展内存

答案:B。按需分页是 malloc 时只创建 VMA 记录,不填充页表项也不分配物理页;进程真正访问地址时才触发缺页并分配页。A 描述的是页表项已建立但物理页为空的错误状态。

Q7. 读放大问题主要出现在哪个机制中?

单选题 · hard

  • A.TLB替换
  • B.swap预读← 正确答案
  • C.写时复制
  • D.页表回收

答案:B。swap 预读和文件预读会一次性读入多个相邻页,如果其中很多页没被使用,就产生读放大。TLB 替换影响命中率,COW 和页表回收都不涉及多读磁盘页。

Q8. 下列哪些情况属于 minor fault?

多选题 · mid

  • A.页缓存命中← 正确答案
  • B.分配零页← 正确答案
  • C.从磁盘换入
  • D.复制被写页← 正确答案

答案:ABD。页缓存命中、匿名页分配零页、COW 复制被写页都不需要磁盘 I/O,因此是 minor fault。从磁盘换入必然产生磁盘 I/O,属于 major fault。

Q9. 关于缺页处理流程,下列哪些说法是正确的?

多选题 · hard

  • A.校验地址合法性← 正确答案
  • B.优先查询页缓存← 正确答案
  • C.匿名页定为主缺页
  • D.用户态更新页表

答案:AB。do_page_fault 会先校验地址是否落在 VMA 中;文件页缺页会先查页缓存,命中则免去磁盘 I/O。匿名页缺页通常是 minor fault,页表更新由内核完成而非用户态。

Q10. 当系统出现大量 major fault 时,可能引发哪些现象?

多选题 · extreme

  • A.有效访问时间增加← 正确答案
  • B.系统出现swap抖动← 正确答案
  • C.CPU使用率可能虚高← 正确答案
  • D.进程必然被OOM杀死

答案:ABC。major fault 将磁盘 I/O 纳入每次访问,EAT 明显上升;频繁换入换出会形成 swap 抖动;内核忙于处理缺页和等待 I/O,会让 CPU 使用率看起来很高。缺页本身不保证触发 OOM,因此 D 错误。