Linux(内核剖析):08---进程调度之Linux调度算法(调度器类公平调度(CFS))

【Linux(内核剖析):08---进程调度之Linux调度算法(调度器类公平调度(CFS))】一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述Linux(内核剖析):08---进程调度之Linux调度算法(调度器类公平调度(CFS))相关的知识,希望能为你提供帮助。

  • 前面一篇文章(javascript:void(0))抽象的讨论了进程调度原理,在已有的调度原理基础上,本文进一步探讨具有Linux特色的进程调度程序
一、调度器类
  • Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性地选择调度算法
  • 这种模块化结构被称为调度器类(scheduler classes),它允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程
  • 每个调度器都有一个优先级,基础的调度器代码定义在kernel/sched.c文件中,它会按照优先级顺序遍历调度类,拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那一个程序
  • 完全公平调度(CFS)是一个针对普通进程的调度类,在Linux中称为SCHED_NORMAL (在POSIX中称为SCHED_OTHER),CFS算法实现定义在文件kernel/sched_fair.c中
  • 本文下面的内容将重点讨论CFS算法——该内容对于所有2.6.23以后的内核版本意义非凡。另外,我们将在下面讨论实时进程的调度类
二、传统Unix系统中的进程调度
  • 在讨论公平调度算法前,我们必须首先认识一下传统Unix系统的调度过程
  • 正如前面所述,现代进程调度器有两个通用的概念:进程优先级和时间片
    • 时间片是指进程运行多少时间, 进程一旦启动就会有一个默认时间片。具有更高优先级的进程将运行得更频繁,而且(在多数系统上)也会被赋予更多的时间片
    • 在Unix系统上,优先级以nice值形式输出给用户空间。这点听起来简单,但是在现实中,却会导许多反常的问题,我们下面具体讨论
第一个问题
  • 第一个问题,若要将nice值映射到时间片,就必然需要将nice单位值对应到处理器的绝对时间。但这样做将导致进程切换无法最优化进行
  • 举例说明:
    • 假定我们将默认nice值(0)分配给一个进程——对应的是一个100ms的时间片;同时再分配一个最高nice(+20,最低的优先级)给另一个进程——对应的时间片是5ms
    • 我们接着假定上述两个进程都处于可运行状态。那 么默认优先级的进程将获得20/21(105ms中的100ms)的处理器时间,而低优先级的进程会获得1/21(105ms中的5ms)的处理器时间。我们本可以选择任意数值用于本例子中,但这个分配值正好是最具说服力的,所以我们选择它。
    • 现在,我们看看如果运行两个同等低优先级的进程情况将如何。我们是希望它们能各自获得一半的处理器时间,事实上也确实如此。但是任何一个进程每次仅仅只能获得5ms的处理器时 (10ms中各占一半)。也就是说,相比刚才例子中105ms内进行一次上下文切换,现在则需要在10ms内继续进行两次上下文切换
  • 类推,如果是两个具有普通优先级的进程,它们同样会每个获得50%处理器时间,但是是在100ms内各获得一半。 显然,我们看到这些时间片的分配方式并不很理想:它们是给nice值到时间片映射与进程运行优先级混合的共同作用结果。事实上,给定高nice(低优先级)的进程往往是后台进程,且多是计算密集型;而普通优先级的进程则更多是前台用户任务。所以这种时间片分配方式显然是和初衷背道而驰的
第二个问题
  • 第二个问题涉及相对nice值,同时和前面的nice值到时间片映射关系也脱不了干系
  • 举例说明:
    • 假设我们有两个进程,分别具有不同的优先级
    • 第一个假设nice值只是0,第二个假设是1。它们将被分别映射到时间片100ms和95ms(O(1)调度算法确实这么干了)。它们的时间片几乎一样,其差別微乎其微
    • 但是如果我们的进程分别赋予18和19的nice值,那么它们则分别被映射为10ms和5ms的时间片。如果这样,前者相比后者获得了两倍的处理器时间!不过nice值通常都使用相对(nice系统调用是在原值上增加或减少,而不是在绝对值上操作),也就是说:“把进程的nice值减小1”所带来的效果极大地取决于其nice的初始值
第三个问题
  • 第三个问题,如果执行nice值到时间片的映射,我们需要能分配一个绝对时间片,而且这个绝对时间片必须能在内核的测试范围内
  • 在多数操作系统中,上述要求意味着时间片必须是定时器节拍的整数 。但这么做必然会引发了几个问题:
    • 首先,最小时间片必然是定时器节拍的整数倍,也就是10ms或者1ms的倍数
    • 其次,系统定时器限制了两个时间片的差异:连续的nice值映射到时间片,其差別范围多至10ms或者少则1ms
    • 最后,时间片还会随着定时器节拍改变
  • 备注:如果这里所讨论的定时器节会在后面定时器和时间管理的文章中介绍。因为这点正是引入CFS的唯一原因
第四个问题
  • 第四个问题也是最后一个是关于基于优先级的调度器为了优化交互任务而唤醒相关进程的问题
  • 这种系统中,你可能为了进程能更快地投入运行,而去对新要唤醒的进程提升优先级,即便它们的时间片已经用尽了。虽然上述方法确实能提升不少交互性能,但是一些例外倩况也有可能发生,因为它同时也给某些特殊的睡眠/唤醒用例一个玩弄调度器的后门,使得给定进程打破公平原则,获得更多处理器时间,损害系统中其他进程的利益
  • 总结:
    • 上述问题中的绝大多数都可以通过对传统Unix调度器进行改造解决,虽然这种改造修改不小,但也并非是结构性调整
    • 比如,将nice值呈几何增加而非算数增加的方式解决第二个问题
    • 采用一个新的度量机制将从nice值到时间片的映射与定时器节拍分离开来,以此解决第三个问题
    • 但是这些解决方案都回避了实质问题——即分配绝对的时间片引发的固定的切换频率,给公平性造成了很大变数
    • CFS采用的方法是对时间片分配方式进行根本性的重新设计(进程调度器而言):完全摒弃时间片而是分配给进程一个处理器使用比重。通过这种方式,CFS确保了进程调度中能有恒定的公平性,而将切换颊率置于不断变动中
三、Linux的公平调度(CFS)
CFS概述
  • CFS的出发点基于一个简单的理念:进程调度的效果应如同系统具备一个理想中的完美多任务处理器
  • 在这种系统中,每个进程将获得1/n的处理器时间——n是指可运行进程的数量。 同时,我们可以调度给它们无限小的时间周期,所以在任何可测量周期内,我们给予n个进程中每个进程同样多的运行时间
  • 举例来说:
    • 假如我们有两个运行进程,在标准Unix调度模型中,我们们先运行其中一个5ms,然后再运行另一个 5ms。但它们任何一个运行时都将占有100%的处理器
    • 在理想情况下,完美的多任务处理器模型应该这样的:我们能在10ms内同时运行两 进程,它们各自使用处理器一半的能力
CFS深度剖析
  • 当然,上述理想模型并非现实,因为我们无法在一个处理器上真的同时运行多个进程。而且如果每个进程运行无限小的时间周期也是不高效的——因为调度时进程抢占会带来一定的代价: 将一个进程换出,另一个换入本身有消耗,同时还会影响到缓存的效率
  • 因此虽然我们希望所有进程能只运行一个非常短的周期,但是CFS充分考虑了这将带来的额外消耗。实现中首先要确保系统性能不受损失:
    • CFS的做法是允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,而不再采用分配给每个进程时间片的做法了,CFS在所有可运行进程总数基础上计算出一个进程应该运行多久,而不是依靠nice值来计算时间片
    • nice值在CFS被作为进程获得的处理器运行比的权重:越高的nice值(越低的优先级)进程获得更低的处理器使用权重,这是相对默认nice值进程的进程而言的;相反,更低的nice值(越高的优先级)的进程获得更高的处理器使用权重
最小粒度
  • 每个进程都按其权重在全部可运行进程中所占比例的“时间片”来运行,为了计算准确的时间片,CFS为完美多任务中的无限小凋度周期的近似值设立了一个目标。而这个目标称作“目标延迟”,越小的调度周期将带来越好的交互性,同时也更接近完美的多任务。但是你必须承受更高的切换代价和更差的系统总吞吐能力
    • 例如:让我们假定目标延迟值是20ms,我们有两个同样优先级的可运行任务(无论这些任务的优先级是多少)。每个任务在被其他任务抢占前运行10ms,如果我们有4个这样的任务,则每个只能运行5ms。进一步设想,如果有20个这样的任务,那么每个仅仅只能获得1ms的运行时间
  • 你一定注意到了,当可运行任务数量趋于无限时,它们各自所获得的处理器使用比和时间片将趋于0。这样无疑造成了不可接受的切换消耗。CFS为此引入每个进程获得的时间片底线,这个底线称为最小粒度。默认情况下这个值是1ms。如此一来,即便是可运行进程数量趋于无穷,每个最少也能获1ms的运行时间,确保切换消耗被限制在一定范围内
  • 你可能会注意到假如在进程数量变得非常多的情况下,CFS并非一个完美的公平调度,因为这时处理器时间片再小也无法突破最小粒度。 的确如此,尽管修改过的公平队列方法确实能提髙这方面的公平 性 ,但是CFS的算法本身其实已经决定在这方面做出折中了。但还好,因为通常情况下系统中只会有几百个可运行进程,无疑,这时CFS是相当公平的
演示案例
  • 现在,让我们来看看具有不同nice值的两个可运行进程的运行情况——比如一个具有默认nice(0),另一个具有的nice值是5。这些不同的nice值对应不同的权重,所以上述两个进程将获得不同的处理器使用比
  • 在这个例子中,nice值是5的进程的权重将是默认nice进程的1/3。如果我们的目标延迟是20ms,那么这两个进程将分別获得15ms和5ms的处理器时间。再比如我们的两个可运行进程的nice值分别是10和15,它们分配的时间片将是多少呢?还是15和5ms!可见,绝对的nice值不再影响调度决策:只有相对值才会影响处理器时间的分配比例
  • 总结:
    • 总结一下,任何进程所获得的处理器时间是由它自己和其他所有可运行进程nice值的相对差值决定的
    • nice值对时间片的作用不再是算数加权,而是几何加权。任何nice值对应的绝对时间不再是一个绝对值,而是处理器的使用比
    • CFS称为公平调度器是因为它确保给每个进程公平的处理器使用比。正如我们知道的,CFS不是完美的公平,它只是近乎完美的多任务。但是它确实在多进程环境下,降低了调度延迟带来的不公平性
    • CFS的实现,参见下一篇文章:javascript:void(0)

    推荐阅读