操作系统导论第9章:比例份额调度

9.1 基本概念:票数代表份额

  • 每个进程持有一些彩票号,调度程序每一次调度时,进行抽奖,持有开奖号数的进程被调度
  • 利用了随机性,可以避免很多最差的情况

9.2 彩票机制

  • 彩票货币:每个用户可以有自己的货币,最后再转换成调度程序的货币
  • 彩票转让:进程可以临时将自己的彩票交给另一个进程
  • 彩票膨胀:一个进程可以临时提升或降低彩票数量。只有进程相互信任的环境才有意义。

9.3 步长调度

  • 每个工作都有步长,步长等于一个大数除以它的票数
  • 每个进程都有行程值,每次调度完一个进程后,行程值增加它的步长
  • 每次调度选择拥有最小行程值的进程
  • 与彩票机制调度不同的是,由于彩票机制使用了随机性,所以可能无法提供正确的比例,特别是在短时间内发生的调度行为,而步长调度则是能够确保这种比例
  • 而步长调度的缺点则是,在新进程加入时,它的行程值为0,会使得它长时间独占CPU
curr = remove_min(queue); // pick client with minimum pass
schedule(curr); // use resource for quantum
curr->pass += curr->stride; // compute next pass using stride
insert(queue, curr); // put back into the queue

9.4 问题

如何分配票数?这个问题非常难解决,因此比例份额调度只有在容易确定份额的场景下才适用。

9.5 Linux完全公平调度(CFS)

基本操作

尽管大多数调度程序都是基于固定时间片的概念,但CFS的操作有所不同。它的目标很简单:在所有竞争的进程之间平均分配CPU。它通过称为虚拟运行时vruntime)的基于计数的简单技术来实现。
在每个进程运行时,它将积累vruntime。在最基本的情况下,每个进程的虚拟运行时以相同的速率增长,与物理(实时)时间成比例。当发生调度决策时,CFS将选择运行vruntime最低的进程。
这就引出了一个问题:调度程序应该何时停止当前正在运行的进程,然后运行下一个进程?这里的压力很明显:如果CFS切换得太频繁,公平性就会增加,因为CFS将确保每个进程即使在很小的时间窗口内也能获得其CPU的份额,但是会以性能为代价(上下文切换过多);如果CFS切换的频率较低,则性能会提高(上下文切换减少),但会以近期的公平为代价。
CFS通过各种控制参数来管理这种压力。首先是调度延迟sched_latency)。 CFS使用此值来确定在考虑切换之前一个进程应运行多长时间(以动态方式有效地确定其时间段)。典型的调度延迟时间值为48(毫秒)。 CFS将该值除以CPU上正在运行的进程数(n)来确定一个进程的时间片,从而确保在这段时间内CFS将完全公平。
例如,如果正在运行 n = 4 n = 4 n=4个进程,则CFS将调度延迟时间的值除以n,得出每个进程的时间片为12 ms。然后,CFS调度第一个任务并运行它,直到它使用了12毫秒的(虚拟)运行时为止,然后检查是否有一个具有较低vruntime的任务要运行。在这种情况下,CFS将切换到其他三个任务之一,依此类推。下图显示了一个示例,其中四个任务(A,B,C,D)以这种方式分别运行两个时间片。然后,其中两个(C,D)完成,仅剩下两个,然后以循环方式分别运行24 ms。
在这里插入图片描述
但是,如果运行的进程“太多”怎么办?那会不会导致时间片太小,从而导致上下文切换过多?
为了解决此问题,CFS添加了另一个参数最小粒度,通常设置为6 ms之类的值。CFS永远不会将时间片设置小于该值,从而确保在调度开销方面不会花费太多时间。
例如,如果有十个进程在运行,我们的原始计算将调度的延迟除以十来确定时间片(结果:4.8毫秒)。但是,由于最小粒度,CFS会将每个进程的时间片设置为6ms。尽管CFS在48毫秒的目标调度延迟(调度延迟)上并不能(完全)公平,但在达到较高CPU效率的同时,它还是很接近的。
注意,CFS利用了周期性的计时中断,这意味着它只能以固定的时间间隔做出决定。该中断频繁关闭(例如,每1毫秒一次),使CFS有机会唤醒并确定当前任务是否已运行结束。如果任务的时间片不是计时中断间隔的完美倍数,则没问题; CFS精确跟踪vruntime,这意味着从长远来看,它将最终接近理想的CPU共享。

加权(精细度)

CFS还可以控制进程优先级,从而使用户或管理员可以为某些进程分配更高的CPU份额。它不使用票而是通过经典的UNIX机制(称为进程的nice级别)来完成的。可以将一个进程的nice参数设置为-20到+19之间的任意值,默认值为0。正的nice值表示优先级较低,而负的值表示优先级较高。
CFS将每个进程的nice值映射到权重,如下所示:

static const int prio_to_weight[40] = {
	/* -20 */ 88761, 71755, 56483, 46273, 36291,
	/* -15 */ 29154, 23254, 18705, 14949, 11916,
	/* -10 */ 9548, 7620, 6100, 4904, 3906,
	/* -5 */  3121, 2501, 1991, 1586, 1277,
	/* 0 */   1024, 820, 655, 526, 423,
	/* 5 */   335, 272, 215, 172, 137,
	/* 10 */  110, 87, 70, 56, 45,
	/* 15 */  36, 29, 23, 18, 15,
};

这些权重使我们能够计算每个进程的有效时间片(就像我们之前所做的一样),但是现在考虑了它们的优先级差异。这样做的公式如下:
t i m e _ s l i c e k = w e i g h t k ∑ i = 0 n − 1 w e i g h t i ∗ s c h e d _ l a t e n c y time\_slice_k=\frac{weight_k}{\sum\limits^{n-1}_{i=0}weight_i}*sched\_latency time_slicek=i=0n1weightiweightksched_latency

让我们看一个例子,看看它是如何工作的。假设有两个任务,A和B。A,因为它是我们最宝贵的工作,被赋予了更高的优先级,所以分配给它的nice值-5;B,因为我们讨厌它,所以它具有默认优先级(nice值等于0)。这意味着 w e i g h t A weight_A weightA为3121,而 w e i g h t B weight_B weightB为1024。然后,计算每个任务的时间片,A的时间片约为调度延迟的 3 4 \frac{3}{4} 43(因此为36毫秒),而B的时间片约为 1 4 \frac{1}{4} 41(因此为12毫秒)。
除了概括时间片计算之外,还必须调整CFS计算vruntime的方式。这是新的公式,它采用进程 i i i累计的实际运行时间( r u n t i m e i runtime_i runtimei),并按进程的权重进行反比例缩放。在我们的运行示例中,A的vruntime将以B的速度的三分之一累积。
v r u n t i m e i = v r u n t i m e i + w e i g h t 0 w e i g h t i ∗ r u n t i m e i vruntime_i=vruntime_i+\frac{weight_0}{weight_i}*runtime_i vruntimei=vruntimei+weightiweight0runtimei

上面的权重表的构造的一个明智的方面是,当nice值的差异恒定时,该表保留CPU比例比率。例如,如果进程A的nice值为5(不是-5),而进程B的nice值为10(不是0),则CFS将以与以前完全相同的方式调度它们。

使用红黑树

如上所述,CFS的一个主要重点是效率。对于调度程序而言,效率有很多方面,但是其中一个方面是如此简单:当调度程序必须找到要运行的下一个任务时,它应该尽快完成。像列表这样的简单数据结构无法扩展:现代系统有时由数千个进程组成,因此每隔几毫秒搜索一个长列表是很浪费的。
CFS通过将进程保存在红黑树中来解决此问题。红黑树是许多平衡树中的一种。与简单的二叉树(在最坏情况下的插入操作性能可能退化为类似链表的性能)相比,平衡树做了一些额外的工作来保持较低的深度,从而确保操作在时间上是对数级别的(而不是线性的)。
CFS不会将所有进程都保留在此结构中,而是仅将运行(或可运行)的进程保留在其中。如果某个进程进入睡眠状态(例如,等待I / O完成,或者等待网络数据包到达),则将其从树中删除并跟踪其他位置。
让我们看一个例子。假设有十个任务,并且它们具有以下vruntime值:1、5、9、10、14、18、17、21、22和24。如果我们将这些任务保留在有序列表中,查找下一个要运行的任务很简单:只需删除第一个元素。但是,当将该任务放回列表中时(按顺序),我们要扫描列表,寻找正确的位置将其插入,这是一个O(n)的操作。任何查找的效率也很低,平均也要花费线性时间。
在红黑树中保持相同的值可以使大多数操作更高效,如图所示。进程按vruntime在树中排序,并且大多数操作(例如插入和删除)在时间上都是对数的,即O(log n)。当n上千时,对数的效率明显高于线性的效率。
在这里插入图片描述

处理I / O和睡眠进程

选择最低vruntime的进程来运行导致的一个问题是长期处于休眠状态的任务。想象一下两个进程,A和B,其中一个(A)连续运行,另一个(B)长时间睡眠(例如10秒)。当B醒来时,其vruntime将比A的运行时间晚10秒,因此(如果我们不小心的话),B现在会在接下来的10秒钟内独占CPU,使A饥饿。
CFS通过在任务唤醒时更改其vruntime来处理这种情况。具体来说,CFS将该任务的vruntime设置为在树中找到的最小值(请记住,该树仅包含正在运行的任务)。这样,CFS避免了饥饿,但并非没有代价:短时间内频繁睡眠的任务经常无法获得应有的CPU份额。

CFS其他有趣的功能

CFS具有许多其他功能,它包括多种启发式方法,可以提高缓存性能,具有有效处理多CPU的策略,可以跨大组进程进行调度(而不是将每个进程视为一个独立的实体),以及许多其他有趣的功能。

9.6 总结

我们介绍了比例分配调度的概念,并简要讨论了三种方法:彩票调度,步长调度和Linux的完全公平调度(CFS)。彩票调度巧妙地利用随机性来实现比例分配。步长调度非常具有确定性。 CFS是本章中讨论的唯一“真正的”调度程序,有点像带有动态时间片的加权循环调度,但是可以在负载下扩展和运行良好。据说,它是当今最广泛使用的公平份额调度程序。
没有调度程序是万能的灵丹妙药,公平份额的调度程序也有很多问题。
一个问题是这种方法不能与I / O很好地融合在一起;如上所述,偶尔执行I / O的任务可能无法公平得到CPU。另一个问题是,它们没有解决票或优先级分配的难题,即如何知道应该分配浏览器程序多少票,或者如何设置文本编辑器的nice值?其他通用调度程序(例如前面讨论的MLFQ和其他类似的Linux调度程序)会自动处理这些问题,因此可能更易于部署。
好消息是,在许多领域中,这些问题并不是主要问题,并且比例份额调度程序可发挥巨大作用。例如,在虚拟数据中心(或云)中,你可能希望将四分之一的CPU周期分配给Windows VM,其余的分配给基本的Linux安装,比例份额可以变得简单而有效。这个想法也可以扩展到其他资源。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值