1. 项目概述:为什么我们需要“页面置换算法”?
如果你写过稍微复杂一点的程序,或者用过内存不那么宽裕的老旧设备,大概率遇到过“卡死”或者“程序崩溃”的情况。很多时候,这背后的问题根源,就是内存不够用了。但我们的程序明明只需要运行一部分代码和数据,为什么不能把暂时不用的部分先挪出去,等需要时再换回来呢?这个“挪出去”和“换回来”的核心策略,就是页面置换算法要解决的事情。
简单来说,页面置换算法是操作系统内存管理中的核心调度策略。它决定了当物理内存(RAM)空间不足,而程序又需要加载新的数据或代码页时,应该把当前内存中的哪一页“淘汰”出去,以便为新来的页面腾出位置。这个决策过程直接影响了系统的整体性能,一个糟糕的置换策略可能导致系统频繁地在内存和硬盘(交换区)之间来回倒腾数据,这种现象被称为“抖动”,会让系统响应变得极其缓慢。
这次我们不谈空洞的理论,就从一个最直观的“步骤化”视角,把几个经典的页面置换算法——FIFO、OPT、LRU——掰开揉碎了讲清楚。我会假设你手头有一个模拟环境,或者干脆就拿张纸画一画,跟着我的步骤一步步推演,你就能彻底明白它们是怎么工作的,各自的优缺点在哪里,以及在实际场景中我们该如何选择和权衡。无论是准备面试,还是想深入理解系统底层,这篇文章都能给你一套清晰的“操作手册”。
2. 核心概念与前置知识:建立统一的“实验架”
在开始推演步骤之前,我们必须先搭好一个统一的“实验架”,明确几个关键概念和约定。这就好比做物理实验前,得先校准仪器、定义好测量单位一样。
2.1 什么是“页”与“缺页”?
现代操作系统普遍采用“虚拟内存”技术。程序看到的是一个连续的、巨大的地址空间(虚拟地址),而物理内存是有限的。操作系统把这个虚拟地址空间切割成一个个固定大小的块,称为“页面”;同样地,物理内存也被切割成同等大小的块,称为“页框”。一个页面可以被加载到任何一个空闲的页框中。
当程序试图访问一个虚拟地址时,操作系统会先检查该地址所在的页面是否已经加载在物理内存的某个页框中。如果在,称为“命中”,访问会直接进行,速度极快。如果不在,则称为“缺页”,此时就会触发一个“缺页中断”。操作系统需要从硬盘(交换文件或交换分区)中找到这个页面,并将其载入到一个物理页框中。如果此时物理内存已满,没有空闲页框,就必须先执行“页面置换算法”,选出一个现有的页面淘汰出去,腾出位置。
注意:我们讨论的“置换”,都发生在“缺页”且“内存无空闲页框”的情况下。这是算法被激活的唯一场景。
2.2 我们的模拟实验设定
为了清晰地演示算法步骤,我们设定以下实验条件:
- 物理页框数:假设系统只有3个物理页框(编号为0, 1, 2)。数量少是为了方便演示,现实中可能是几百上千个。
- 页面访问序列:我们用一个序列来模拟程序运行时对页面的请求顺序。这是算法的输入。例如:
7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1。这个序列是随机构造的,但包含了重复访问,能很好地测试算法。 - 初始状态:开始时,3个页框都是空的。
- 衡量指标:缺页率。即
缺页次数 / 总访问次数。这是评价置换算法优劣的核心指标,缺页率越低,性能通常越好(因为减少了耗时的硬盘I/O)。
接下来,我们就用这个统一的“实验架”,分别运行三种经典算法,并一步步记录下它们的决策过程和最终结果。
3. 算法一:先进先出(FIFO)—— 简单粗暴的队列管理者
FIFO算法是最直观、实现最简单的置换算法。它的核心思想是:把最先进入内存的页面最先淘汰出去。你可以把它想象成一个队列,新页面从队尾进入,需要淘汰时,总是淘汰队头的页面。
3.1 FIFO算法步骤详解
我们严格按照访问序列,一步步推演。
访问序列:7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1物理页框(3个):初始为空。维护一个队列:记录页面进入内存的顺序。
| 访问页面 | 物理页框状态 (队头 -> ... -> 队尾) | 是否缺页? | 淘汰页面(若发生) | 队列变化说明 |
|---|---|---|---|---|
| 7 | [空, 空, 空] | 是 | - | 页框空,直接装入7。队列:[7] |
| 0 | [7, 空, 空] | 是 | - | 页框未满,装入0。队列:[7, 0] |
| 1 | [7, 0, 空] | 是 | - | 页框未满,装入1。队列:[7, 0, 1] |
| 2 | [7, 0, 1] | 是 | 7 | 内存已满!队头是7,淘汰7。装入2。队列变为:[0, 1, 2] |
| 0 | [0, 1, 2] | 否 | - | 页面0已在内存中,命中!队列顺序不变。 |
| 3 | [0, 1, 2] | 是 | 0 | 缺页,内存满。淘汰队头0。装入3。队列:[1, 2, 3] |
| 0 | [1, 2, 3] | 是 | 1 | 缺页,内存满。淘汰队头1。装入0。队列:[2, 3, 0] |
| 4 | [2, 3, 0] | 是 | 2 | 缺页,内存满。淘汰队头2。装入4。队列:[3, 0, 4] |
| 2 | [3, 0, 4] | 是 | 3 | 缺页,内存满。淘汰队头3。装入2。队列:[0, 4, 2] |
| 3 | [0, 4, 2] | 是 | 0 | 缺页,内存满。淘汰队头0。装入3。队列:[4, 2, 3] |
| 0 | [4, 2, 3] | 是 | 4 | 缺页,内存满。淘汰队头4。装入0。队列:[2, 3, 0] |
| 3 | [2, 3, 0] | 否 | - | 命中。队列不变。 |
| 2 | [2, 3, 0] | 否 | - | 命中。队列不变。 |
| 1 | [2, 3, 0] | 是 | 2 | 缺页,内存满。淘汰队头2。装入1。队列:[3, 0, 1] |
| 2 | [3, 0, 1] | 是 | 3 | 缺页,内存满。淘汰队头3。装入2。队列:[0, 1, 2] |
| 0 | [0, 1, 2] | 否 | - | 命中。队列不变。 |
| 1 | [0, 1, 2] | 否 | - | 命中。队列不变。 |
| 7 | [0, 1, 2] | 是 | 0 | 缺页,内存满。淘汰队头0。装入7。队列:[1, 2, 7] |
| 0 | [1, 2, 7] | 是 | 1 | 缺页,内存满。淘汰队头1。装入0。队列:[2, 7, 0] |
| 1 | [2, 7, 0] | 是 | 2 | 缺页,内存满。淘汰队头2。装入1。队列:[7, 0, 1] |
统计:
- 总访问次数:20次
- 缺页次数:15次
- 缺页率:15 / 20 =75%
3.2 FIFO的陷阱:Belady异常
FIFO算法虽然简单,但有一个著名的反直觉现象——Belady异常。即:在某些页面访问序列下,增加物理页框的数量,反而可能导致缺页率上升。这违背了“资源越多性能越好”的常识。
举个例子:假设访问序列是 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5。
- 当有3个页框时,缺页次数为9次。
- 当有4个页框时,你按照FIFO步骤推导,会发现缺页次数变成了10次。
原因剖析:FIFO只记录进入时间,完全不考虑页面的使用频率。增加页框后,可能会把一些未来很快会被再次访问的页面(但因为是早期进入的)保留了下来,同时却把一些虽然进入晚但未来更久不会被用到的页面提前淘汰了,打乱了原本“恰好”的置换节奏。这说明FIFO未能很好地反映程序的“局部性”原理(程序倾向于在短时间内集中访问某些特定的页面)。
实操心得:FIFO算法在硬件实现上非常容易(一个简单的环形缓冲区即可),所以在一些对性能要求不高或资源极其受限的嵌入式系统中仍有应用。但在通用操作系统中,它通常作为对比的基准,实际很少直接使用,就是因为其性能不稳定且可能存在Belady异常。
4. 算法二:最佳置换(OPT)—— 理想中的“预言家”
OPT算法是一种理论上最优的算法。它的核心思想是:淘汰那些在未来最长时间内不再被访问的页面。这就像有一个预言家,能准确知道程序未来所有页面的访问顺序。
4.1 OPT算法步骤详解
显然,这是无法在实际中实现的(操作系统无法预知未来),但它为其他算法提供了一个性能上限的衡量标杆。
我们使用同一个访问序列进行推演。关键步骤在于:每次需要置换时,我们向后查看访问序列,找出当前在内存中的那些页面,谁下一次出现的位置最远(或者再也不出现),就淘汰谁。
访问序列:7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1物理页框(3个):初始为空。
| 访问页面 | 物理页框状态 | 是否缺页? | 淘汰页面(若发生) | 决策逻辑(向后看序列) |
|---|---|---|---|---|
| 7 | [空, 空, 空] | 是 | - | 直接装入。 |
| 0 | [7, 空, 空] | 是 | - | 装入。 |
| 1 | [7, 0, 空] | 是 | - | 装入。内存满。 |
| 2 | [7, 0, 1] | 是 | 7 | 内存已满!看未来:7下次出现在第18位,0下次在第5位,1下次在第14位。7最远,淘汰7。 |
| 0 | [0, 1, 2] | 否 | - | 命中。 |
| 3 | [0, 1, 2] | 是 | 1 | 缺页,内存满。看未来:0下次在第7位,1下次在第14位,2下次在第9位。1最远(14>9>7),淘汰1。 |
| 0 | [0, 2, 3] | 否 | - | 命中。 |
| 4 | [0, 2, 3] | 是 | 3 | 缺页,内存满。看未来:0下次在第11位,2下次在第9位,3下次在第10位。比较:0(11), 2(9), 3(10)。2和3都比0近,2(9)最远?等等,仔细看:我们需要淘汰“最远”的。0在第11位出现,2在第9位,3在第10位。所以最近的是2(9),最远的是0(11)。我们应该淘汰未来最久不被用的,即0吗?错!规则是“淘汰未来最长时间内不再被访问的”,即下一次访问距离现在最长的。0在11位,距离当前位置(访问4是第8次访问)是3步。2在9位,距离是1步。3在10位,距离是2步。因此,0是未来最晚被访问的(距离最长),所以应该淘汰0。但让我们再严格检查序列:当前是访问页面4(序列第8个)。内存中有0,2,3。向后找:0下一次出现在第11个位置(值0),距离3。2下一次出现在第9个位置(值2),距离1。3下一次出现在第10个位置(值3),距离2。所以,0的距离3最大,淘汰0。装入4。 |
| 2 | [2, 3, 4] | 否 | - | 命中。 |
| 3 | [2, 3, 4] | 否 | - | 命中。 |
| 0 | [2, 3, 4] | 是 | 4 | 缺页,内存满。看未来:2下次在第13位,3下次在第12位,4在剩余序列中不再出现!对于不再出现的页面,可以认为其下一次访问在“无穷远”。所以淘汰4。 |
| 3 | [2, 3, 0] | 否 | - | 命中。 |
| 2 | [2, 3, 0] | 否 | - | 命中。 |
| 1 | [2, 3, 0] | 是 | 2或3 | 缺页,内存满。看未来:2下次在第15位,3不再出现,0下次在第16位。3不再出现,是“无穷远”,所以淘汰3。装入1。 |
| 2 | [2, 0, 1] | 否 | - | 命中。 |
| 0 | [2, 0, 1] | 否 | - | 命中。 |
| 1 | [2, 0, 1] | 否 | - | 命中。 |
| 7 | [2, 0, 1] | 是 | 2 | 缺页,内存满。看未来:2不再出现,0下次在第19位,1下次在第20位。2和0、1比较,2不再出现,是“无穷远”,所以淘汰2。装入7。 |
| 0 | [0, 1, 7] | 否 | - | 命中。 |
| 1 | [0, 1, 7] | 否 | - | 命中。 |
统计:
- 总访问次数:20次
- 缺页次数:9次
- 缺页率:9 / 20 =45%
4.2 OPT算法的启示与局限性
OPT算法的缺页率(45%)远低于FIFO(75%),这展示了理想情况下的性能潜力。它总是做出全局最优的置换决策。
局限性:正如前文所述,OPT是“不可实现”的,因为它要求预知未来的全部访问序列。这在动态运行的程序中是不可能的。
实操价值:OPT的主要价值在于作为评估基准。当设计或测试一个新的置换算法时,可以将其缺页率与OPT进行比较,从而知道该算法距离理论最优还有多大差距。例如,一个算法的缺页率如果能接近OPT,那么它就是非常优秀的。
注意:在模拟OPT算法时,判断“不再出现”的页面是关键。在编程实现模拟器时,通常可以向后遍历序列,找到每个内存页面下一次出现的索引,取最大值(对于不再出现的,可以赋予一个极大的数,如序列长度+1)。这需要O(n*k)的复杂度(n为序列长,k为页框数),这也是它不实用的原因之一。
5. 算法三:最近最久未使用(LRU)—— 对过去行为的合理推测
LRU算法是对OPT算法的一种实用且有效的近似。既然无法预知未来,那就回顾过去。它的核心思想是:淘汰那些最近一段时间内最久没有被访问的页面。这基于“局部性原理”:如果一个页面最近被用过,那么它很可能在不久的将来还会被用到;反之,如果很久没用,未来被用的可能性也较低。
5.1 LRU算法步骤详解
实现LRU的关键是如何记录和更新每个页面的“最近使用时间”。我们这里用两种直观的方法来演示步骤:一是“计数器/时间戳”法,为每个页框维护一个逻辑时钟;二是“栈”法,将访问过的页面按最近访问时间排序。
我们用**“计数器法”**来推演:每次访问(无论是否缺页)都更新被访问页面的计数器为当前最大时间值。需要淘汰时,选择计数器值最小的页面(即最久未使用的)。
假设有一个全局递增的逻辑时钟C,初始为0。
访问序列:7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1物理页框(3个):初始为空。记录每个页框内页面的最后访问时间。
| 访问页面 | 物理页框状态 (页面:时间) | 是否缺页? | 淘汰页面(若发生) | 逻辑时钟与操作说明 |
|---|---|---|---|---|
| 7 | [空, 空, 空] | 是 | - | C=0。装入7,时间戳=0。状态:7:0 |
| 0 | [7:0, 空, 空] | 是 | - | C=1。装入0,时间戳=1。状态:7:0, 0:1 |
| 1 | [7:0, 0:1, 空] | 是 | - | C=2。装入1,时间戳=2。状态:7:0, 0:1, 1:2 |
| 2 | [7:0, 0:1, 1:2] | 是 | 7 | C=3。内存已满!找出时间戳最小的页面:7:0 (最久未用)。淘汰7。装入2,时间戳=3。状态变为:2:3, 0:1, 1:2 |
| 0 | [2:3, 0:1, 1:2] | 否 | - | C=4。页面0命中,更新时间戳为4。状态:2:3, 0:4, 1:2 |
| 3 | [2:3, 0:4, 1:2] | 是 | 1 | C=5。缺页,内存满。找时间戳最小的:1:2。淘汰1。装入3,时间戳=5。状态:2:3, 0:4, 3:5 |
| 0 | [2:3, 0:4, 3:5] | 否 | - | C=6。页面0命中,更新时间戳为6。状态:2:3, 0:6, 3:5 |
| 4 | [2:3, 0:6, 3:5] | 是 | 2 | C=7。缺页,内存满。找时间戳最小的:2:3。淘汰2。装入4,时间戳=7。状态:4:7, 0:6, 3:5 |
| 2 | [4:7, 0:6, 3:5] | 是 | 3 | C=8。缺页,内存满。找时间戳最小的:3:5。淘汰3。装入2,时间戳=8。状态:4:7, 0:6, 2:8 |
| 3 | [4:7, 0:6, 2:8] | 是 | 4 | C=9。缺页,内存满。找时间戳最小的:4:7。淘汰4。装入3,时间戳=9。状态:3:9, 0:6, 2:8 |
| 0 | [3:9, 0:6, 2:8] | 否 | - | C=10。页面0命中,更新时间戳为10。状态:3:9, 0:10, 2:8 |
| 3 | [3:9, 0:10, 2:8] | 否 | - | C=11。页面3命中,更新时间戳为11。状态:3:11, 0:10, 2:8 |
| 2 | [3:11, 0:10, 2:8] | 否 | - | C=12。页面2命中,更新时间戳为12。状态:3:11, 0:10, 2:12 |
| 1 | [3:11, 0:10, 2:12] | 是 | 0 | C=13。缺页,内存满。找时间戳最小的:0:10。淘汰0。装入1,时间戳=13。状态:3:11, 1:13, 2:12 |
| 2 | [3:11, 1:13, 2:12] | 否 | - | C=14。页面2命中,更新时间戳为14。状态:3:11, 1:13, 2:14 |
| 0 | [3:11, 1:13, 2:14] | 是 | 3 | C=15。缺页,内存满。找时间戳最小的:3:11。淘汰3。装入0,时间戳=15。状态:0:15, 1:13, 2:14 |
| 1 | [0:15, 1:13, 2:14] | 否 | - | C=16。页面1命中,更新时间戳为16。状态:0:15, 1:16, 2:14 |
| 7 | [0:15, 1:16, 2:14] | 是 | 2 | C=17。缺页,内存满。找时间戳最小的:2:14。淘汰2。装入7,时间戳=17。状态:0:15, 1:16, 7:17 |
| 0 | [0:15, 1:16, 7:17] | 否 | - | C=18。页面0命中,更新时间戳为18。状态:0:18, 1:16, 7:17 |
| 1 | [0:18, 1:16, 7:17] | 否 | - | C=19。页面1命中,更新时间戳为19。状态:0:18, 1:19, 7:17 |
统计:
- 总访问次数:20次
- 缺页次数:12次
- 缺页率:12 / 20 =60%
5.2 LRU的实现挑战与近似算法
从结果看,LRU(60%)的性能介于FIFO(75%)和OPT(45%)之间,更接近OPT,这证明了其有效性。但真正的挑战在于如何高效实现它。
“计数器/时间戳”法的问题:每次内存访问(不仅仅是缺页中断)都需要更新对应页面的时间戳,这要求硬件支持(如一个全局时钟和每个页表项中的时间戳字段),并且每次淘汰时需要遍历所有页面找最小值,开销较大。
“栈”法:维护一个页面栈,最近访问的页面移到栈顶,栈底就是LRU页面。但每次访问都需要在栈中移动页面,同样需要硬件支持以保证速度。
因此,实际的操作系统(如Linux)使用的是LRU的近似算法,它们开销小且易于硬件实现。最常见的是:
- 二次机会算法(时钟算法):为每个页面设置一个“访问位”。当需要置换时,像时钟指针一样扫描页面。如果页面的访问位是0,就淘汰它;如果是1,则将其置为0,给该页面第二次机会,指针继续移动。这相当于把页面粗略地分成了“最近被用过”和“最近没用过”两类。
- 老化算法:使用一个多位(如8位)的移位寄存器来模拟页面历史。定期(如每个时钟周期)将访问位右移进寄存器,并清零访问位。需要淘汰时,淘汰寄存器值最小的页面。这实现了对“最近一段时间”使用频率的粗略统计。
实操心得:理解LRU的关键在于理解“局部性原理”和其对过去行为的推断。在面试或实际系统调优中,当遇到缓存性能问题时,思考LRU及其变种是否适用是首要方向。例如,在数据库的Buffer Pool、CPU缓存、甚至Web浏览器缓存中,都能看到LRU思想的身影。在代码层面,如果要自己实现一个缓存,LinkedHashMap(设置访问顺序)是快速实现LRU的经典选择。
6. 算法对比与场景选择指南
经过一步步的推演,我们对三种算法有了直观的认识。现在我们来系统性地对比一下,并讨论如何在实际中做出选择。
6.1 性能与特性对比表
| 特性 | FIFO (先进先出) | OPT (最佳置换) | LRU (最近最久未使用) |
|---|---|---|---|
| 核心思想 | 淘汰最早进入的页面 | 淘汰未来最久不被访问的页面 | 淘汰最近最久未被访问的页面 |
| 实现复杂度 | 极低(队列) | 不可能实现(需预知未来) | 较高(需硬件支持精确时间戳) |
| 开销 | 低 | - | 中到高 |
| 是否考虑程序行为 | 否 | 是(完美未来) | 是(过去历史) |
| 是否存在Belady异常 | 是 | 否 | 否 |
| 缺页率(我们的实验) | 75% (最高) | 45% (最低,理论最优) | 60% (居中,接近OPT) |
| 实际应用 | 简单嵌入式系统,作为基准 | 仅用于理论分析与性能评估 | 广泛用于缓存系统(CPU缓存、数据库缓冲池、页面缓存),操作系统多使用其近似算法 |
6.2 如何根据场景选择置换策略?
选择页面置换算法不是一个纯理论问题,需要权衡性能需求、实现开销和硬件支持。
追求极致简单与确定性:如果你的系统内存充足,或者应用场景简单,缺页不是主要矛盾,FIFO是一个可以接受的选择。它的行为完全可预测,在资源受限的微控制器或实时操作系统中可能有其一席之地。
通用计算环境(如Linux/Windows):现代通用操作系统无一例外地使用LRU的近似算法,如时钟算法或其变种。它在性能(接近LRU)和开销(实现相对简单)之间取得了最佳平衡。Linux内核的页面置换核心就是基于LRU思想的“双向链表+活动/非活动列表”机制。
缓存系统设计:在设计应用层缓存(如Redis、Memcached的键淘汰策略,数据库缓冲池)时,LRU是最常被考虑的算法。因为缓存数据的访问模式通常符合局部性原理。许多缓存库都提供了LRU或类LRU的实现。
特殊访问模式:如果程序的访问模式是顺序扫描大型数组(如科学计算),那么任何算法表现都会很差,因为几乎每次访问都会缺页。此时FIFO和LRU区别不大。这种情况下,优化重点可能在于采用更大的页面或改进算法本身(如预取)。
避坑技巧:在面试或技术讨论中,当被问到LRU时,一定要能说出其近似算法(如时钟算法)。这证明你不仅了解理论,还知道工程上的折中。可以这样表达:“理论上LRU是最佳实践之一,但精确实现开销大。因此,像Linux这样的实际系统采用了时钟算法这种LRU近似实现,它通过一个访问位和环形扫描来低成本地模拟LRU行为。”
7. 进阶思考与扩展算法
除了上述三种经典算法,了解它们的变种和扩展能让你对内存管理的理解更深入。
7.1 LRU的家族:LFU与MRU
- LFU(最不经常使用):淘汰访问次数最少的页面。它关注的是频率而非新鲜度。适用于某些访问频率非常稳定的场景,但缺点是新调入的页面可能因为计数低而被快速淘汰,且需要维护计数器并应对“老化”问题(一个过去频繁访问但现在不再用的页面会长期占着内存)。
- MRU(最近最多使用):与LRU相反,淘汰最近被使用过的页面。这听起来反直觉,但在某些特殊场景下有效,例如数据库的“嵌套循环连接”中,顺序扫描大表时,刚刚被访问的页面在下次循环中很可能不再需要。
7.2 工作集模型与抖动预防
操作系统不会盲目地运行置换算法。它通过“工作集模型”来动态评估一个进程当前正在活跃使用的页面集合。如果分配给进程的物理页框数小于其工作集大小,那么无论采用多好的置换算法,都会发生剧烈的“抖动”。因此,一个更全局的调度策略是:当系统检测到抖动时,可能会挂起某些进程,将其内存整体换出,以释放资源给其他进程,从而平抑抖动。这涉及到进程调度与内存管理的协同。
7.3 实操中的混合策略与参数调优
在实际的Linux系统中,页面置换不是单一算法。例如:
- 它区分了文件缓存和匿名内存(堆、栈等),两者的回收优先级和策略不同。
- 使用了水位线机制:当空闲内存低于“低水位线”时,内核线程
kswapd开始异步回收页面;低于“最低水位线”时,分配内存的进程可能被阻塞,直接参与同步回收。 - 回收时,页面根据其活跃程度在“活动链表”和“非活动链表”之间移动,优先回收非活动链表中的页面,这本质上是LRU思想的一种实现。
对于开发者而言,虽然无法直接修改内核置换算法,但可以通过调整/proc/sys/vm/下的参数(如swappiness,控制换出匿名内存的倾向)来影响系统的置换行为,以适应不同应用负载(如数据库服务器通常倾向于降低swappiness值,以减少对交换区的使用)。
跟着这七个章节一步步走下来,你应该已经对页面置换算法从“是什么”、“怎么做”到“为什么”以及“怎么选”有了一个立体而扎实的理解。记住,理解算法最好的方式就是像我们这样,拿一个具体的序列,用纸笔或编辑器一步步模拟出来。下次当你再遇到程序性能瓶颈,怀疑是内存交换惹的祸时,不妨用vmstat或sar命令看看系统的缺页和交换频率,你就能从更底层的视角理解那些性能数字背后的故事了。