CFS(Completely Fair Schduler), 首次出现于Linux2.6.23。
CFS区别于其他调度算法的最突出一点是CFS保证每个任务分配到公平的运行时间。
经典抢占式调度通常包括多个调度队列,每个进程优先级一个:高优先级队列中的每个进程在低优先级队列中的任何进程之前被调度。例如,VAX/VMS 使用 32 个优先级队列进行调度。CFS 省去了固定的时间片和明确的优先级。处理器上给定任务的时间量是随着调度上下文在系统生命周期中的变化而动态计算的。
CFS的一些概念
目标延迟(target latency)
目标延迟是每个可执行任务至少打开处理器一次所需的最短时间(理想化为无限小的持续时间)
目标延迟是所有待运行的任务至少运行一小会(理想化为无限小的持续时间)。
然后每个可运行任务会获得目标延迟的1/N的时间去让处理器执行它,N为竞争处理器的任务总数。这里的均分就体现了CFS完全公平调度的思想。
1/N确实是一个时间片但不固定,因为进程总数会不停的改变,一些进程终止并产生新的进程,可运行的进程被阻塞,被阻塞的进程变得可运行。
传统的优先级用于加权1/N
每个可运行任务的实际运行时间不是单纯的1/N,也受进程优先级的影响,低优先级的进程的实际运行时间会低于1/N,高优先级则高于1/N。
虚拟运行时间(vruntime)
虚拟运行时间与进程的优先级无关,调度程序会跟踪所有任务的vruntime,任务的vruntime越低,任务在处理器上的时间就越多。CFS相应的将vruntime任务移到调度线(红黑树实现)的前面。
最小粒度(minimum granularity)
每当发生上下文切换时,OS都会产生开销。为了防止开销过大,任何调度进程在被抢占之前必须运行一段最短时间(典型设置为1ms到4ms),这个最短时间就称为最小粒度。
调度周期
假设TL为20ms,MG为4ms
TL/MG = 20/4 = 5
说明五个或更少的任务被允许在目标延迟(TL)期间被处理器处理,然后经过20ms调度程序重新调度目标延迟的持续时间。
但如果超过了TL/MG,就会出现每个任务所分得1/N的运行时间小于最小粒度(MG),在这种情况下,调度程序将在40ms内重新调度:
任务数量 * MG = 10 * 4 = 40ms
CFS的实现
runqueue
CFS实现了一个基于使用时间(vruntime)排序的红黑树的数据结构runqueue,表示计划任务的时间线。
在CFS下,每个处理器都有一个特定的runqueue,rb树的每个结点代表一个调度任务或任务组。因此(在整个数或任何子树)左侧的结点的vruntime低于右侧结点。
CFS 从这棵树中挑选“最左边”的任务并坚持下去。随着系统向前推进,执行的任务被越来越多地放到树的右边——缓慢但肯定地让每个任务都有机会成为“最左边的任务”,从而在确定的时间内进入 CPU。
task_struct
CFS 调度程序实现了一个结构体task_struct用于跟踪有关要调度的每个任务的详细信息。
小特点
CFS 支持对称多处理 (SMP),如果多个处理器共享相同的调度策略,那么它们之间的负载平衡是一种选择;如果特定处理器具有与其他处理器不同的调度策略,则该处理器将在调度方面与其他处理器隔离。
三种调度策略
SCHED_NORMAL(传统上称为SCHED_OTHER):用于常规任务的调度策略。
SCHED_BATCH:不像常规任务那样经常抢占,从而允许任务运行更长时间并更好地利用缓存,但以交互性为代价。这非常适合批处理作业。
SCHED_IDLE:这比 nice 19 更弱,但它不是一个真正的空闲计时器调度程序,以避免陷入会使机器死锁的优先级反转问题。
SCHED_FIFO/_RR 在 sched/rt.c 中实现,并由 POSIX 指定。
详情更多参考
https://opensource.com/article/19/2/fair-scheduling-linux
https://www.kernel.org/doc/html/latest/scheduler/sched-design-CFS.html
SMP:https://www.tutorialspoint.com/Symmetric-Multiprocessing