第八章 多级反馈队列(MLFQ)

在操作系统中,调度器的主要目标是决定哪个进程应该在何时运行。然而,我们通常不知道进程的具体行为,比如它会运行多久,或者它是否会频繁地与用户交互(I/O)。

  • 问题一:不知道工作长度。 SJF/STCF 等算法能优化周转时间,但它们需要知道工作长度,而我们通常不知道。
  • 问题二:响应时间和周转时间的权衡。 轮转法能降低响应时间(交互体验好),但周转时间可能很差。SJF/STCF 能优化周转时间,但可能牺牲了交互性。
  • 核心挑战:如何在信息不完备的情况下,设计一个既能优化响应时间,又能优化周转时间的调度器?

MLFQ 的核心思想:从历史中学习,预测未来。

MLFQ 并不是一成不变的,它会根据进程在运行过程中的 表现(反馈)来 动态调整 进程的 优先级。进程表现出什么样的行为,就会被赋予相应的优先级,从而影响其未来的调度。

基础知识梳理:

  1. 多级队列(Multi-level Queues):

    • MLFQ 拥有 多个独立的队列,每个队列拥有一个 不同的优先级
    • 一个进程在任何时刻 只能存在于一个队列 中。
    • 高优先级队列中的进程永远优先于低优先级队列中的进程。
  2. 优先级(Priority):

    • MLFQ 的核心是 动态优先级,而不是固定优先级。
    • 进程的优先级会随着其行为而改变。
  3. 调度规则(核心):

    • 规则 1:优先级决定运行权。 如果进程 A 的优先级高于进程 B,那么优先运行 A。
    • 规则 2:同优先级下的轮转。 如果进程 A 和 B 优先级相同,则对它们进行轮转调度(Round Robin)。
  4. 进程行为与优先级关联:

    • 交互型进程(Interactive Process): 经常需要等待用户输入或其他 I/O 操作。它们通常会 在时间片用完之前主动释放 CPU。MLFQ 希望这类进程有好的响应时间,所以会 保持其高优先级
    • CPU 密集型进程(CPU-bound Process): 运行时间长,长时间占用 CPU。MLFQ 会 降低这类进程的优先级

知识重点整理:

MLFQ 的发展过程(通过规则的演进):

第一阶段:基本规则与优先级调整(尝试 1)

  • 规则 3:新进程进入。 当一个新进程进入系统时,默认给予 最高优先级(放在最上层队列)。

    • 目的: 假设所有新进程都可能是短作业,从而近似 SJF/STCF,以优化周转时间。
  • 规则 4a:用完时间片。 如果一个进程 用完了它在当前队列分配的时间片(time slice),那么 降低其优先级(移到下一个较低优先级的队列)。

    • 目的: 认为长时间占用 CPU 的进程是 CPU 密集型,降低其优先级。
  • 规则 4b:主动释放 CPU。 如果一个进程 在其时间片用完之前主动释放了 CPU(例如,等待 I/O),则 保持其优先级不变

    • 目的: 认为主动释放 CPU 的是交互型进程,不应受惩罚,保持其高优先级以提供好的响应时间。

示例分析(此阶段):

  • 长工作: 会从高优先级队列逐渐下降到低优先级队列。
  • 短工作: 如果在用完时间片前完成,其优先级不会降低,如果确实很快完成,就能近似 SJF。
  • 有 I/O 的工作: 由于会主动释放 CPU,优先级保持不变,从而得到更好的响应。

MLFQ 在此阶段存在的问题:

  1. 饥饿(Starvation): 如果系统中有大量的交互型进程,它们会不断占用 CPU,导致 CPU 密集型进程(即使是优先级较低的)永远得不到运行机会。
  2. 被“愚弄”(Gaming the Scheduler): 聪明的用户可以编写程序,通过在时间片用完前 伪造 I/O 操作(例如访问一个无用的文件)来欺骗规则 4b,从而 保持高优先级,几乎独占 CPU
  3. 进程行为变化问题: 一个进程在不同时间段表现可能不同。如果一个计算密集型进程突然变得像交互型进程,但其优先级已经很低,MLFQ 无法及时识别并提升其优先级。

第二阶段:解决饥饿和进程行为变化(尝试 2)

  • 规则 5:优先级提升(Priority Boost)。 周期性地(经过一段时间 S),将 系统中所有进程的优先级都提升到最高级别
    • 目的:
      • 解决饥饿: 即使是低优先级的进程,也能周期性地获得高优先级,从而得到运行机会。
      • 适应行为变化: 如果一个 CPU 密集型进程变得像交互型进程,在优先级提升时,它会得到正确的对待。

此阶段的问题:

  • “巫毒常量”(Voodoo Constant): 时间间隔 S 的设置非常困难。
    • S 太大:长工作仍然可能饥饿。
    • S 太小:交互型工作得不到足够的 CPU 时间比例。

第三阶段:解决“愚弄”问题(尝试 3)

  • 重写规则 4(更好的计时方式): 调度程序 记录一个进程在某一层队列中消耗的总时间(配额)。只要进程用完了这个配额(无论它中间主动放弃 CPU 了多少次),就 将其降到低一级队列
    • 目的: 阻止了通过伪造 I/O 来“愚弄”调度程序的行为。无论进程如何释放 CPU,只要用完配额就会降级。

MLFQ 调优及其他问题(MLFQ 调优及其他问题):

  • 配置参数: MLFQ 的配置非常复杂,没有固定的“最优解”,需要根据实际工作负载进行调优。

    • 队列数量: 有多少级队列?
    • 时间片长度: 每个队列的时间片有多长?(通常高优先级队列时间片短,低优先级队列时间片长)
    • 优先级提升间隔 S: 多久提升一次所有进程的优先级?
  • 避免“巫毒常量”(Ousterhout 定律): 难以找到完美的 S 值。系统管理员通常通过配置文件设置默认值,希望这些默认值是合理的。

  • MLFQ 的变种:

    • 不同队列不同时间片: 高优先级队列时间片短,低优先级队列时间片长。
    • 固定优先级留给 OS: 有些系统将最高优先级留给操作系统自身。
    • 用户建议(Advice): 允许用户通过 nice 命令等工具给出优先级建议,系统可以参考但不强制执行。
    • 数学公式: 一些系统(如 FreeBSD)使用数学公式根据进程使用 CPU 的情况计算优先级,并结合使用量衰减(decay-usage)来达到优先级提升的效果。

MLFQ 小结:

  • MLFQ 的核心是通过 多级队列反馈信息 来动态调整进程的 优先级
  • 它根据进程的 一贯表现 来区别对待。
  • MLFQ 不需要先验知识,通过观察来学习。
  • 它可以同时满足 短交互型工作(近似 SJF/STCF,响应时间好)和 长 CPU 密集型工作(公平、稳步前进)。
  • 许多现代操作系统(如 BSD、Solaris、Windows NT)都采用了某种形式的 MLFQ。

总结核心思想:

MLFQ 就像一个“观察者”,它给新来的进程一个机会(最高优先级),如果进程证明自己是短作业(很快完成),就很好;如果证明自己是长作业(用完时间片),就降低其优先级。如果进程表现出交互性(频繁释放 CPU),就保持其高优先级。通过周期性的“大赦”(优先级提升),确保所有进程都有机会得到运行,同时防止被“愚弄”。

posted on 2025-11-21 11:22  Leo_Yide  阅读(15)  评论(0)    收藏  举报