linux cfs调度算法_CFS:Linux中完全公平的流程调度

linux cfs调度算法

Linux采用模块化方法进行处理器调度,因为可以使用不同的算法来调度不同的进程类型。 调度类指定哪种调度策略适用于哪种类型的过程。 完全公平调度(CFS)在2007年成为Linux 2.6.23内核的一部分,它是常规(相对于实时)进程的调度类,因此命名为SCHED_NORMAL

CFS适用于桌面环境中典型的交互式应用程序,但可以将其配置为SCHED_BATCH以支持例如在大容量Web服务器上常见的批处理工作负载。 在任何情况下,CFS都将被称为“经典抢先式调度”的服务大打折扣。 同样,必须从技术角度看待“完全公平”的主张。 否则,该声明可能看起来很虚张声势。

让我们深入研究使CFS与其他流程调度程序区分开的细节。 让我们从快速回顾一些核心技术术语开始。

一些核心概念

Linux将进程的Unix视图继承为正在执行的程序。 因此,一个进程必须与其他进程争夺共享的系统资源:用于保存指令和数据的内存 ,至少一个用于执行指令的处理器以及与外部世界进行交互的I / O设备 。 进程调度是操作系统(OS)如何将任务(例如,处理一些数字,复制文件)分配给处理器的方法,然后由运行中的进程执行任务。 一个进程具有一个或多个执行线程 ,它们是机器级指令的序列。 调度进程就是在处理器上调度其线程之一。

N个线程,则需要N个调度操作来覆盖线程。 多线程进程中的线程保持关联,因为它们共享资源,例如内存地址空间。 Linux线程有时被称为轻量级进程, 轻量级强调了进程中线程之间的资源共享。

尽管一个过程可能处于各种状态,但是在调度中有两个特别重要。 被阻塞的进程正在等待某些事件(例如I / O事件)的完成。 仅在事件完成后,该过程才能恢复执行。 可运行的进程是当前未被阻止的进程。

如果一个进程主要消耗处理器而不是I / O资源,则该进程受处理器限制 (又称为compute-bound ),反之,则 I / O限制 。 因此,与处理器绑定的进程大部分是可运行的,而与I / O绑定的进程大部分是被阻止的。 例如,处理数字受处理器限制,而访问文件受I / O限制。 尽管整个过程的特征可能是处理器绑定或I / O绑定,但是给定进程在执行的不同阶段可能是一个或另一个。 交互式桌面应用程序,例如浏览器,往往受I / O约束。

一个好的流程调度程序必须平衡处理器绑定和I / O绑定任务的需求,尤其是在诸如Linux之类的操作系统上,它在众多硬件平台上蓬勃发展:台式机,嵌入式设备,移动设备,服务器集群,超级计算机, 和更多。

经典抢先式计划与CFS

Unix普及了经典的抢占式调度,后来又采用了其他操作系统,包括VAX / VMS,Windows NT和Linux。 该调度模型的中心是固定的时间片 ,即一个任务被允许持有处理器直到被抢占有利于其他任务的时间(例如50ms)。 如果抢占式进程尚未完成其工作,则必须重新计划该进程。 该模型的强大之处在于,即使在过去的单CPU机器上,它也通过处理器分时支持多任务处理(并发)。

经典模型通常包含多个调度队列,每个进程优先级一个:优先级较高的队列中的每个进程都在优先级较低的队列中的任何进程之前进行调度。 例如,VAX / VMS使用32个优先级队列进行调度。

CFS不需要固定的时间片和明确的优先级。 处理器上给定任务的时间量是随着调度上下文在系统生命周期中的变化而动态计算的。 以下是激励性想法和技术细节的示意图:

  • 想象一个处理器P,它理想化的原因是它可以同时执行多个任务。 例如,任务T1和T2可以同时在P上执行,每个任务都接收P的50%的神奇处理能力。 这种理想化描述了完美的多任务处理 ,与理想化的处理器相比,CFS努力在实际中实现。 CFS旨在逼近完美的多任务处理。

  • CFS调度程序具有目标等待时间 ,这是每个可运行任务至少需要打开一次处理器所需的最短时间(理想化为无限小的持续时间)。 如果这样的持续时间可以无限小,那么每个可运行任务都会在任意给定的时间跨度内打开处理器,但是时间很短(例如10ms,5ns等)。 当然,在现实世界中,理想的无限小持续时间必须是近似值,默认近似值是20ms。 然后,每个可运行任务都会获得目标等待时间的1 / N分片,其中N是任务数。 例如,如果目标等待时间为20ms,并且有四个竞争任务,则每个任务的时间片为5ms。 顺便说一句,如果在调度事件中只有一个任务,则此幸运任务将获得整个目标延迟作为其分片。 在CFS 公平就浮出水面在给予每个任务争夺一个处理器的1 / N的片。

  • 1 / N条带确实是一个时间片,但不是固定的条带,因为这样的条带取决于N ,即当前竞争处理器的任务数。 系统随时间变化。 一些进程终止,并产生新的进程。 可运行的进程被阻塞,被阻塞的进程变为可运行。 N的值是动态的,因此,它是为竞争处理器的每个可运行任务计算的1 / N时间片。 传统的nice值用于对1 / N slice进行加权:低优先级nice值意味着仅将1 / N slice的一部分分配给任务,而高优先级nice值则意味着比例更大的分数将1 / N条带中的一个赋予任务。 总而言之, 好的值不会确定切片,而只会修改表示竞争任务之间公平性的1 / N切片。

  • 每当发生上下文切换时,操作系统都会产生开销。 就是说,当一个过程被抢占有利于另一个过程时。 为避免此开销过大,在抢占之前,任何计划的进程都必须运行最短的时间(典型设置为1ms到4ms)。 此最小值称为最小粒度 。 如果许多任务(例如,20)被争用处理器,则该最小粒度(假定为4ms)可能多于 1 / N片(在这种情况下,1ms的)。 如果最小粒度大于1 / N切片,则系统将过载,因为有太多任务要争夺处理器,因此公平性就荡然无存了。

  • 抢占何时发生? 考虑到它们的开销,CFS尝试最小化上下文切换:花费在上下文切换上的时间是其他任务所没有的时间。 因此,一旦任务获得处理器,它将在优先处理其他任务之前先运行其整个加权的1 / N条带 。 假设任务T1已针对其加权的1 / N片运行,并且可运行任务T2当前在争夺处理器的任务中具有最低的虚拟运行时 (vruntime)。 vruntime以纳秒为单位记录任务在处理器上运行了多长时间。 在这种情况下,T1将优先于T2。

  • 调度程序跟踪所有可运行和已阻止任务的vruntime。 任务的vruntime越低,该任务在处理器上的时间就越值得。 因此,CFS将低运行时任务移到计划行的前端。 由于该是作为树而不是列表来实现的,因此即将提供详细信息。

  • CFS调度程序应多久重新调度一次? 有一种简单的方法可以确定计划周期 。 假设目标等待时间(TL)为20毫秒,最小粒度(MG)为4毫秒:

    TL / MG = (20 / 4) = 5 ## five or fewer tasks are ok

    在这种情况下,五个或更少的任务将允许每个任务在目标延迟期间打开处理器。 例如,如果任务号为5,则每个可运行任务的1 / N切片为4ms,这恰好等于最小粒度;例如, 如果任务号为3,则每个任务将获得一个约7ms的1 / N切片。 无论哪种情况,调度程序都会在20ms(目标等待时间的持续时间)内重新调度。

    如果任务数(例如10个)超过TL / MG,则会发生故障,因为现在每个任务必须获得4ms的最短时间,而不是所计算的1 / N切片的2ms。 在这种情况下,调度程序将在40ms内重新调度:

    (number of tasks) * MG = (10 * 4) = 40ms ## period = 40ms

在CFS之前的Linux调度程序使用启发式方法来促进交互式任务在调度方面的公平对待。 CFS通过让vruntime事实主要为自己说话,采取了一种截然不同的方法,这恰恰支持了睡眠者公平性 。 就其本质而言,交互式任务在等待用户输入并因此成为I / O绑定的意义上倾向于睡眠很多。 因此,这样的任务往往具有相对较低的运行时间,这倾向于将任务移向调度行的前端。

特殊功能

CFS支持对称多处理(SMP),其中任何进程(无论是内核还是用户)都可以在任何处理器上执行。 然而,可配置的调度域可用于对处理器进行分组,以实现负载平衡甚至隔离。 如果多个处理器共享相同的调度策略,则可以选择它们之间的负载平衡; 如果特定处理器的调度策略与其他处理器不同,则该处理器将在调度方面与其他处理器隔离。

可配置的调度组是CFS的另一个功能。 例如,考虑在我的台式机上运行的Nginx Web服务器。 在启动时,该服务器具有一个主进程和四个工作进程,它们充当HTTP请求处理程序。 对于任何HTTP请求,处理该请求的特定工作程序都是无关紧要的。 仅重要的是及时处理请求,因此这四个工作人员一起提供了一个池,可以根据请求的来从中提取任务处理程序。因此,将这四个Nginx工作人员视为一个组而不是将其视为一个组似乎很公平。作为个人进行调度,可以使用调度组来完成此任务。 可以将四个Nginx工作器配置为在其中具有单个vruntime,而不是单个vruntime。 配置是通过文件以传统的Linux方式完成的。 对于vruntime共享,将创建一个名为cpu .shares的文件,其详细信息通过熟悉的shell命令给出。

如前所述,Linux支持调度类,以便不同的调度策略及其实现算法可以共存于同一平台上。 调度类在C中作为代码模块实现。到目前为止,调度类CSCHSCHED_NORMAL 。 还有专门针对实时任务的调度类,即SCHED_FIFO (先进先出)和SCHED_RR (循环)。 在SCHED_FIFO下 ,任务运行完毕; 在SCHED_RR下,任务一直运行到耗尽固定的时间片并被抢占为止。

CFS实施

CFS需要高效的数据结构来跟踪任务信息,并需要高性能代码来生成计划。 让我们从调度中的中心术语runqueue开始 。 这是一个数据结构,代表计划任务的时间轴。 尽管有名称,运行队列也不需要以传统方式实现,作为FIFO列表。 CFS通过使用按时间顺序排列的红黑树作为运行队列来打破传统。 数据结构非常适合这项工作,因为它是一个自平衡二进制搜索树,具有在O(log N)时间内执行的高效插入删除操作,其中N是树中的节点数。 而且,树是一种出色的数据结构,可用于根据特定属性(在本例中为vruntime)将实体组织为层次结构。

在CFS中,树的内部节点表示要调度的任务,而树与任何运行队列一样,整体上表示任务执行的时间轴。 红黑树被广泛用于调度之外。 例如,Java使用此数据结构实现其TreeMap

在CFS下,每个处理器都有一个特定的任务运行队列,并且在多个运行队列中没有一个任务同时发生。 每个运行队列都是一棵红黑树。 树的内部节点代表任务或任务组,并且这些节点通过其vruntime值进行索引,因此(在树中或在任何子树中)左侧的内部节点的vruntime值比右侧的内部节点低:


   
   
    25     ## 25 is a task vruntime
    /\
  17  29   ## 17 roots the left subtree, 29 the right one
  /\  ...
 5  19     ## and so on
...  \
     nil   ## leaf nodes are nil

总而言之,具有最低vruntime的任务(因此也最需要处理器)驻留在左侧子树中的某个位置。 vruntime相对较高的任务聚集在正确的子树中。 抢先的任务将进入右子树,从而使其他任务有机会在树中向左移动。 vruntime最小的任务在树的最左侧(内部)节点中结束,因此该节点位于运行队列的前面。

CFS调度程序具有一个实例C task_struct ,以跟踪有关要调度的每个任务的详细信息。 该结构嵌入了sched_entity结构,该结构又具有特定于调度的信息,尤其是每个任务或任务组的vruntime:


   
   
struct task_struct {       /** info on a task **/
  ...
  struct sched_entity se;  /** vruntime, etc. **/
  ...
};

红黑树以熟悉的C方式实现,并在指针方面提高了效率。 一个cfs_rq结构实例嵌入了一个名为task_timelinerb_root字段,该字段指向一棵红黑树的根。 树的每个内部节点都有指向父节点和两个子节点的指针。 叶节点的值为零

CFS展示了如何以低耗但高效的方式实现一个简单的想法(为每个任务分配处理器资源的合理份额)。 值得重复的是,CFS无需传统的工件(例如固定的时间片和明确的任务优先级)即可实现公平有效的调度。 当然,对更好的调度程序的追求还在继续。 但是,目前,CFS与通用处理器调度一样好。

翻译自: https://opensource.com/article/19/2/fair-scheduling-linux

linux cfs调度算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值