162306a36Sopenharmony_ci.. SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci.. include:: ../disclaimer-zh_CN.rst
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci:Original: Documentation/scheduler/sched-design-CFS.rst
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci:翻译:
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci===============
1162306a36Sopenharmony_ci完全公平调度器
1262306a36Sopenharmony_ci===============
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci1. 概述
1662306a36Sopenharmony_ci=======
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ciCFS表示“完全公平调度器”,它是为桌面新设计的进程调度器,由Ingo Molnar实现并合入Linux
1962306a36Sopenharmony_ci2.6.23。它替代了之前原始调度器中SCHED_OTHER策略的交互式代码。
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciCFS 80%的设计可以总结为一句话:CFS在真实硬件上建模了一个“理想的,精确的多任务CPU”。
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci“理想的多任务CPU”是一种(不存在的 :-))具有100%物理算力的CPU,它能让每个任务精确地以
2462306a36Sopenharmony_ci相同的速度并行运行,速度均为1/nr_running。举例来说,如果有两个任务正在运行,那么每个
2562306a36Sopenharmony_ci任务获得50%物理算力。 --- 也就是说,真正的并行。
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci在真实的硬件上,一次只能运行一个任务,所以我们需要介绍“虚拟运行时间”的概念。任务的虚拟
2862306a36Sopenharmony_ci运行时间表明,它的下一个时间片将在上文描述的理想多任务CPU上开始执行。在实践中,任务的
2962306a36Sopenharmony_ci虚拟运行时间由它的真实运行时间相较正在运行的任务总数归一化计算得到。
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci2. 一些实现细节
3462306a36Sopenharmony_ci===============
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci在CFS中,虚拟运行时间由每个任务的p->se.vruntime(单位为纳秒)的值表达和跟踪。因此,
3762306a36Sopenharmony_ci精确地计时和测量一个任务应得的“预期的CPU时间”是可能的。
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci  一些细节:在“理想的”硬件上,所有的任务在任何时刻都应该具有一样的p->se.vruntime值,
4062306a36Sopenharmony_ci  --- 也就是说,任务应当同时执行,没有任务会在“理想的”CPU分时中变得“不平衡”。
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ciCFS的任务选择逻辑基于p->se.vruntime的值,因此非常简单:总是试图选择p->se.vruntime4362306a36Sopenharmony_ci最小的任务运行(也就是说,至今执行时间最少的任务)。CFS总是尽可能尝试按“理想多任务硬件”
4462306a36Sopenharmony_ci那样将CPU时间在可运行任务中均分。
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ciCFS剩下的其它设计,一般脱离了这个简单的概念,附加的设计包括nice级别,多处理,以及各种
4762306a36Sopenharmony_ci用来识别已睡眠任务的算法变体。
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci3. 红黑树
5262306a36Sopenharmony_ci=========
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciCFS的设计非常激进:它不使用运行队列的旧数据结构,而是使用按时间排序的红黑树,构建出
5562306a36Sopenharmony_ci任务未来执行的“时间线”。因此没有任何“数组切换”的旧包袱(之前的原始调度器和RSDL/SD5662306a36Sopenharmony_ci被它影响)。
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ciCFS同样维护了rq->cfs.min_vruntime值,它是单调递增的,跟踪运行队列中的所有任务的最小
5962306a36Sopenharmony_ci虚拟运行时间值。系统做的全部工作是:使用min_vruntime跟踪,然后用它的值将新激活的调度
6062306a36Sopenharmony_ci实体尽可能地放在红黑树的左侧。
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci运行队列中正在运行的任务的总数由rq->cfs.load计数,它是运行队列中的任务的权值之和。
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ciCFS维护了一个按时间排序的红黑树,所有可运行任务以p->se.vruntime为键值排序。CFS从这颗
6562306a36Sopenharmony_ci树上选择“最左侧”的任务并运行。系统继续运行,被执行过的任务越来越被放到树的右侧 --- 缓慢,
6662306a36Sopenharmony_ci但很明确每个任务都有成为“最左侧任务”的机会,因此任务将确定性地获得一定量CPU时间。
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci总结一下,CFS工作方式像这样:它运行一个任务一会儿,当任务发生调度(或者调度器时钟滴答
6962306a36Sopenharmony_citick产生),就会考虑任务的CPU使用率:任务刚刚花在物理CPU上的(少量)时间被加到
7062306a36Sopenharmony_cip->se.vruntime。一旦p->se.vruntime变得足够大,其它的任务将成为按时间排序的红黑树的
7162306a36Sopenharmony_ci“最左侧任务”(相较最左侧的任务,还要加上一个很小的“粒度”量,使得我们不会对任务过度调度,
7262306a36Sopenharmony_ci导致缓存颠簸),然后新的最左侧任务将被选中,当前任务被抢占。
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci4. CFS的一些特征
7862306a36Sopenharmony_ci================
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ciCFS使用纳秒粒度的计时,不依赖于任何jiffies或HZ的细节。因此CFS并不像之前的调度器那样
8162306a36Sopenharmony_ci有“时间片”的概念,也没有任何启发式的设计。唯一可调的参数(你需要打开CONFIG_SCHED_DEBUG)是:
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci   /sys/kernel/debug/sched/min_granularity_ns
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci它可以用来将调度器从“桌面”模式(也就是低时延)调节为“服务器”(也就是高批处理)模式。
8662306a36Sopenharmony_ci它的默认设置是适合桌面的工作负载。SCHED_BATCH也被CFS调度器模块处理。
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ciCFS的设计不易受到当前存在的任何针对stock调度器的“攻击”的影响,包括fiftyp.cthud.c8962306a36Sopenharmony_cichew.cring-test.cmassive_intr.c,它们都能很好地运行,不会影响交互性,将产生
9062306a36Sopenharmony_ci符合预期的行为。
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ciCFS调度器处理nice级别和SCHED_BATCH的能力比之前的原始调度器更强:两种类型的工作负载
9362306a36Sopenharmony_ci都被更激进地隔离了。
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ciSMP负载均衡被重做/清理过:遍历运行队列的假设已经从负载均衡的代码中移除,使用调度模块
9662306a36Sopenharmony_ci的迭代器。结果是,负载均衡代码变得简单不少。
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci5. 调度策略
10162306a36Sopenharmony_ci===========
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ciCFS实现了三种调度策略:
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci  - SCHED_NORMAL:(传统被称为SCHED_OTHER):该调度策略用于普通任务。
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci  - SCHED_BATCH:抢占不像普通任务那样频繁,因此允许任务运行更长时间,更好地利用缓存,
10862306a36Sopenharmony_ci    不过要以交互性为代价。它很适合批处理工作。
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci  - SCHED_IDLE:它比nice 19更弱,不过它不是真正的idle定时器调度器,因为要避免给机器
11162306a36Sopenharmony_ci    带来死锁的优先级反转问题。
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ciSCHED_FIFO/_RR被实现在sched/rt.c中,它们由POSIX具体说明。
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ciutil-linux-ng 2.13.1.1中的chrt命令可以设置以上所有策略,除了SCHED_IDLE。
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci6. 调度类
12062306a36Sopenharmony_ci=========
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci新的CFS调度器被设计成支持“调度类”,一种调度模块的可扩展层次结构。这些模块封装了调度策略
12362306a36Sopenharmony_ci细节,由调度器核心代码处理,且无需对它们做太多假设。
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cisched/fair.c 实现了上文描述的CFS调度器。
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cisched/rt.c 实现了SCHED_FIFO和SCHED_RR语义,且比之前的原始调度器更简洁。它使用了100个
12862306a36Sopenharmony_ci运行队列(总共100个实时优先级,替代了之前调度器的140个),且不需要过期数组(expired
12962306a36Sopenharmony_ciarray)。
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci调度类由sched_class结构体实现,它包括一些函数钩子,当感兴趣的事件发生时,钩子被调用。
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci这是(部分)钩子的列表:
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci - enqueue_task(...)
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci   当任务进入可运行状态时,被调用。它将调度实体(任务)放到红黑树中,增加nr_running变量
13862306a36Sopenharmony_ci   的值。
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci - dequeue_task(...)
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci   当任务不再可运行时,这个函数被调用,对应的调度实体被移出红黑树。它减少nr_running变量
14362306a36Sopenharmony_ci   的值。
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci - yield_task(...)
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci   这个函数的行为基本上是出队,紧接着入队,除非compat_yield sysctl被开启。在那种情况下,
14862306a36Sopenharmony_ci   它将调度实体放在红黑树的最右端。
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci - check_preempt_curr(...)
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci   这个函数检查进入可运行状态的任务能否抢占当前正在运行的任务。
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci - pick_next_task(...)
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci   这个函数选择接下来最适合运行的任务。
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci - set_curr_task(...)
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci   这个函数在任务改变调度类或改变任务组时被调用。
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci - task_tick(...)
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci   这个函数最常被时间滴答函数调用,它可能导致进程切换。这驱动了运行时抢占。
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci7. CFS的组调度扩展
17062306a36Sopenharmony_ci==================
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci通常,调度器操作粒度为任务,努力为每个任务提供公平的CPU时间。有时可能希望将任务编组,
17362306a36Sopenharmony_ci并为每个组提供公平的CPU时间。举例来说,可能首先希望为系统中的每个用户提供公平的CPU
17462306a36Sopenharmony_ci时间,接下来才是某个用户的每个任务。
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ciCONFIG_CGROUP_SCHED 力求实现它。它将任务编组,并为这些组公平地分配CPU时间。
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ciCONFIG_RT_GROUP_SCHED 允许将实时(也就是说,SCHED_FIFO和SCHED_RR)任务编组。
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ciCONFIG_FAIR_GROUP_SCHED 允许将CFS(也就是说,SCHED_NORMAL和SCHED_BATCH)任务编组。
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci   这些编译选项要求CONFIG_CGROUPS被定义,然后管理员能使用cgroup伪文件系统任意创建任务组。
18362306a36Sopenharmony_ci   关于该文件系统的更多信息,参见Documentation/admin-guide/cgroup-v1/cgroups.rst
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci当CONFIG_FAIR_GROUP_SCHED被定义后,通过伪文件系统,每个组被创建一个“cpu.shares”文件。
18662306a36Sopenharmony_ci参见下面的例子来创建任务组,并通过“cgroup”伪文件系统修改它们的CPU份额::
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	# mount -t tmpfs cgroup_root /sys/fs/cgroup
18962306a36Sopenharmony_ci	# mkdir /sys/fs/cgroup/cpu
19062306a36Sopenharmony_ci	# mount -t cgroup -ocpu none /sys/fs/cgroup/cpu
19162306a36Sopenharmony_ci	# cd /sys/fs/cgroup/cpu
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	# mkdir multimedia	# 创建 "multimedia" 任务组
19462306a36Sopenharmony_ci	# mkdir browser		# 创建 "browser" 任务组
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	# #配置multimedia组,令其获得browser组两倍CPU带宽
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	# echo 2048 > multimedia/cpu.shares
19962306a36Sopenharmony_ci	# echo 1024 > browser/cpu.shares
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	# firefox &	# 启动firefox并把它移到 "browser" 组
20262306a36Sopenharmony_ci	# echo <firefox_pid> > browser/tasks
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	# #启动gmplayer(或者你最喜欢的电影播放器)
20562306a36Sopenharmony_ci	# echo <movie_player_pid> > multimedia/tasks
206