六:操作系统虚拟内存之帧分配
内存管理:深入理解帧分配 (Frame Allocation)
在现代操作系统中,虚拟内存是一个核心概念。它允许进程使用比物理内存更大、更灵活的地址空间。然而,最终进程的数据和指令还是需要存储在物理内存中。物理内存被划分为固定大小的块,称为帧 (Frames) 或 页帧 (Page Frames)。进程的虚拟地址空间被划分为相同大小的块,称为页 (Pages)。
当一个进程运行时,它只需要将其当前正在使用的部分(即活跃的页)加载到物理内存的帧中。操作系统内存管理单元(MMU)负责将虚拟页映射到物理帧。
一个关键的问题是:操作系统应该如何将有限的物理帧分配给系统中同时运行的多个进程?这就是帧分配 (Frame Allocation) 要解决的问题。合理的帧分配策略直接影响着系统的整体性能、吞吐量和响应时间。
帧分配策略主要关注两个方面:
- 分配策略: 如何确定每个进程在任何给定时间应该拥有多少物理帧?
- 替换范围: 当一个进程发生缺页中断需要加载一个新页,而其分配的帧都已满时,要替换掉哪一个旧页?这个旧页是从该进程自己的帧中选择,还是可以从系统中所有进程的帧中选择?这涉及页面替换范围 (Replacement Scope)。
接下来,我们将详细探讨这些策略。
1. 帧分配策略 (Frame Allocation Strategies)
帧分配策略决定了每个进程能够占用的物理帧数量。主要有两种基本方法:固定分配和可变分配。
1.1 固定分配 (Fixed Allocation)
在固定分配策略中,一旦一个进程被加载到内存并开始运行,它就被分配一个固定数量的物理帧。这个数量在其整个生命周期(或至少在其常驻内存期间)保持不变。即使进程的内存需求发生变化,它仍然只能使用预先分配的那些帧。
确定为每个进程分配多少帧的方法有几种:
- 等分分配 (Equal Allocation): 这是最简单的方法。如果有
M
个物理帧可用于用户进程,并且有N
个用户进程正在运行,那么每个进程被分配M / N
个帧(取整)。- 例子: 系统有 1000 个物理帧,当前有 10 个活跃进程。每个进程被分配
1000 / 10 = 100
个帧。
- 例子: 系统有 1000 个物理帧,当前有 10 个活跃进程。每个进程被分配
- 比例分配 (Proportional Allocation): 这种方法试图根据进程的“大小”(通常是其虚拟地址空间的大小,即总页数)来分配帧。较大的进程可能需要更多的帧以减少缺页中断。如果进程
i
的大小是s_i
页,所有进程的总大小是S = Σ s_j
,总可用帧数是M
,那么进程i
被分配的帧数f_i
可以计算为:
f_i = (s_i / S) * M
(结果通常需要取整,并且所有进程分配的总帧数应等于 M)。- 例子: 系统有 1000 个物理帧,有两个进程 P1 (大小 500 页) 和 P2 (大小 1500 页) 正在运行。总大小 S = 500 + 1500 = 2000 页。
- P1 分配帧数:
(500 / 2000) * 1000 = (1/4) * 1000 = 250
帧。 - P2 分配帧数:
(1500 / 2000) * 1000 = (3/4) * 1000 = 750
帧。 - 总分配帧数:250 + 750 = 1000 帧。
- P1 分配帧数:
- 例子: 系统有 1000 个物理帧,有两个进程 P1 (大小 500 页) 和 P2 (大小 1500 页) 正在运行。总大小 S = 500 + 1500 = 2000 页。
固定分配的优点:
- 实现简单。
- 为每个进程提供了一个最低保障的内存量(如果分配得当)。
- 每个进程的内存使用相对独立,一个进程的行为不太会直接影响另一个进程的帧数。
固定分配的缺点:
- 缺乏灵活性: 进程的内存需求会随时间变化(局部性原理)。一个进程可能在某个阶段需要更多帧以容纳其工作集,在另一阶段却用不了那么多。固定分配无法适应这种变化。
- 可能导致内部碎片:如果一个进程分配了固定数量的帧,但实际活跃的工作集小于这个数量,那么分配给它的部分帧就会被浪费。
- 难以确定最佳的固定分配数量:过少会导致频繁的缺页中断,过多则浪费内存。
1.2 可变分配 (Variable Allocation)
在可变分配策略中,分配给进程的物理帧数量可以在其运行期间动态改变。操作系统会监控进程的行为(例如,通过追踪缺页中断率),并据此调整其分配的帧数。
- 如何调整? 可变分配通常基于进程的工作集 (Working Set) 模型或缺页中断频率 (Page Fault Frequency - PFF) 模型。
- 工作集模型: 进程的工作集是在最近一段时间窗口(Δ)内最活跃访问的页集合。操作系统尝试为每个进程分配足够多的帧来容纳其当前工作集。如果工作集变大,分配更多帧;工作集变小,收回一些帧。
- PFF 模型: 操作系统监控进程的缺页中断率。如果缺页率高于某个上限阈值,表明进程可能需要更多帧,于是增加其分配的帧数。如果缺页率低于某个下限阈值,表明进程可能分配了过多的帧,于是减少其分配的帧数。
- 例子 (PFF): 进程 A 的缺页中断率持续高于阈值
T_upper
。操作系统为其增加 5 个帧。一段时间后,进程 B 的缺页中断率持续低于阈值T_lower
。操作系统从进程 B 收回 3 个帧,并将这些帧分配给其他需要的进程(或加入空闲帧池)。
可变分配的优点:
- 灵活性高: 能更好地适应进程动态变化的内存需求,从而可能减少总体的缺页中断。
- 理论上能更有效地利用物理内存,因为它试图只为进程分配它们当前真正需要的帧。
可变分配的缺点:
- 实现复杂: 需要操作系统持续监控进程的内存使用和行为,并动态调整分配。
- 系统开销: 监控和调整过程本身会消耗 CPU 资源。
- 可能导致不稳定: 如果调整策略不够精细,可能会出现“振荡”现象,即分配的帧数频繁大幅波动。
- 一个进程的行为(例如,突然的工作集增长)可能直接影响其他进程可用的帧数。
2. 页面替换范围 (Replacement Scope)
当一个进程发生缺页中断,需要将所需的页从二级存储加载到物理内存中时,如果该进程分配的所有物理帧都已经满了,就必须从中选择一个页来替换掉(写回二级存储,然后加载新页)。页面替换范围定义了替换算法在选择牺牲页时考虑的帧的集合。
主要有两种页面替换范围:全局替换和局部替换。
2.1 全局替换 (Global Replacement)
在全局替换策略中,当一个进程发生缺页中断且其分配的帧已满时,替换算法可以从系统中的所有物理帧中选择一个牺牲页进行替换,无论这个帧当前属于哪个进程。
- 如何工作? 页面替换算法(如 LRU, FIFO, Optimal 等)维护一个关于系统中所有物理帧使用情况的信息(例如,它们的最近使用时间)。当需要替换时,算法根据其规则在所有帧中寻找最佳的牺牲页。这个牺牲页可能属于发生缺页中断的进程,也可能属于其他任何进程。
- 例子: 进程 A 发生缺页中断。进程 A 当前有 50 个帧。系统总共有 1000 个帧,被进程 A (50)、进程 B (40)、进程 C (30) 等进程以及操作系统内核使用。如果采用全局 LRU 替换,操作系统会检查所有 1000 个帧,找到其中最近最少使用的那个帧,然后替换掉它。这个帧可能恰好是进程 B 的一个帧。
- 典型搭配: 全局替换通常与可变分配策略一起使用。因为全局替换允许一个进程在需要时从其他进程“拿走”帧(如果那些帧是全局替换算法认为最合适的牺牲品),这使得分配给进程的帧数可以在运行时自然地增减。
全局替换的优点:
- 高内存利用率: 物理内存中不活跃的页更有可能被替换,无论它们属于哪个进程。这使得空闲帧可以被更需要的进程使用,从而提高了整体内存利用率。
- 可能带来更好的系统吞吐量: 如果系统中存在一些不活跃或等待I/O的进程占用了帧,这些帧可以被活跃进程使用,减少活跃进程的缺页次数。
全局替换的缺点:
- 进程间的干扰: 一个进程的缺页行为会影响其他进程拥有的帧数。一个“行为不佳”的进程(例如,访问模式散乱导致高缺页率)可能会从其他进程“窃取”帧,导致其他进程的缺页率也随之升高,最终可能导致系统范围内的颠簸 (Thrashing)。
- 难以预测或控制单个进程的性能: 无法保证一个进程总能维持一定数量的帧,其性能可能受到其他进程的影响。
2.2 局部替换 (Local Replacement)
在局部替换策略中,当一个进程发生缺页中断且其分配的帧已满时,替换算法只能从当前分配给该进程自身的物理帧中选择一个牺牲页进行替换。
- 如何工作? 每个进程维护一个关于其自身所拥有的物理帧的使用信息。当一个进程发生缺页时,替换算法仅检查该进程自己的帧列表,并根据规则从中选择一个牺牲页。其他进程的帧完全不受影响。
- 例子: 进程 A 发生缺页中断。进程 A 当前被分配了 50 个帧。系统总共有 1000 个帧。如果采用局部 LRU 替换,操作系统会检查仅属于进程 A 的那 50 个帧,找到其中最近最少使用的那个,然后替换掉它。进程 B、C 等的帧完全不会被考虑。
- 典型搭配: 局部替换通常与固定分配策略一起使用。因为固定分配为每个进程划定了明确的帧边界,局部替换正好在这个边界内操作,符合固定分配“互不干涉”的理念。
局部替换的优点:
- 进程间的隔离性好: 一个进程的缺页中断和替换行为不会直接影响其他进程的帧数。这有效地防止了系统范围的颠簸,因为一个进程无法通过“窃取”帧来拖垮整个系统。
- 更容易控制和预测单个进程的性能: 如果为进程分配了足够的帧(例如,包含其整个工作集),可以相对有效地减少其缺页次数。
局部替换的缺点:
- 可能导致内存利用率低下: 如果一个进程被固定分配了较多帧,但其当前工作集较小,导致一些帧不活跃,这些不活跃的帧即使系统中其他进程急需内存,也无法被使用。
- 灵活性差: 无法应对单个进程内存需求的突发增长,如果分配的帧数不足以容纳其当前工作集,即使系统中有大量空闲帧,该进程仍然会频繁缺页。
3. 分配与替换的关联
帧分配策略和页面替换范围是紧密相关的概念,它们共同决定了进程如何使用物理内存。
- 固定分配 + 局部替换: 这是最简单和最常见的组合之一。每个进程有固定的帧配额,并在自己的配额内进行页面替换。这种组合的优点是实现简单、进程间隔离性好,缺点是内存利用率可能不高,且无法适应进程动态需求。
- 可变分配 + 全局替换: 这种组合更为复杂,但理论上能提供更好的内存利用率和系统吞吐量。操作系统动态调整进程的帧数,替换算法则在整个物理内存池中寻找牺牲者。这种组合的优点是灵活性强、能适应动态需求,缺点是实现复杂、存在进程间干扰的风险,可能导致全局颠簸。
- 固定分配 + 全局替换: 这种组合不常见,且可能导致问题。如果进程的帧数固定,但替换可以在全局进行,那么一个进程可能会不断地“借用”其他进程的帧(通过全局替换机制),这违背了固定分配试图提供的隔离性。
- 可变分配 + 局部替换: 这种组合是可能的。操作系统动态调整进程的帧数(可变分配),但当进程发生缺页时,仅在其当前分配的帧集合内进行替换(局部替换)。这提供了动态分配的灵活性,同时又保留了局部替换的隔离性。然而,当一个进程需要更多帧时,操作系统必须显式地为其分配新的帧(可能是从空闲帧池中获取,或者通过某种策略从拥有多余帧的进程那里收回),而不是简单地依赖全局替换“自然”地获取帧。
现代操作系统通常会采用更为复杂的混合策略,例如,为每个进程设定一个最小和最大的帧数限制,在其间采用可变分配和全局替换,而在达到限制时可能切换策略。
总结
帧分配是虚拟内存管理中的一个重要环节。
- 固定分配 为每个进程提供固定的帧数,简单但缺乏灵活性。
- 可变分配 允许进程的帧数动态调整,更灵活但实现复杂且有潜在的系统不稳定风险。
页面替换范围定义了选择牺牲页的范围:
- 全局替换 在所有物理帧中选择,提高了内存利用率,但也可能导致进程间干扰和全局颠簸。
- 局部替换 只在发生缺页的进程自己的帧中选择,提供了更好的隔离性,但可能导致内存浪费。
通常,固定分配与局部替换搭配,可变分配与全局替换搭配,形成两种主要的内存管理流派。理解这些策略对于理解操作系统如何管理有限的物理内存资源,以及它们如何影响多任务环境下的系统性能至关重要。