diff --git a/study/kernel/00-DESCRIPTION/SCHEDULER.md b/study/kernel/00-DESCRIPTION/SCHEDULER.md
index f6225ae..f52f617 100755
--- a/study/kernel/00-DESCRIPTION/SCHEDULER.md
+++ b/study/kernel/00-DESCRIPTION/SCHEDULER.md
@@ -536,7 +536,7 @@ NUMA 机器一个重要特性就是不同 node 之间的内存访问速度有差
| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
|:----:|:----:|:---:|:------:|:---:|
| 2019/10/18 | [Reconcile NUMA balancing decisions with the load balancer](https://linuxplumbersconf.org/event/4/contributions/480) | 重构 load balance | v4 ☑ | [LWN](https://lwn.net/Articles/793427), [PatchWork](https://lore.kernel.org/patchwork/cover/1199507), [lkml](https://lkml.org/lkml/2019/10/18/676) |
-| 2020/02/11 | [sched/numa: replace runnable_load_avg by load_avg](https://lore.kernel.org/patchwork/patch/1192634) | 优化补丁 | | |
+| 2020/2/21 | Vincent Guittot | [remove runnable_load_avg and improve group_classify](https://lore.kernel.org/patchwork/cover/1198654) | 重构组调度的 PELT 跟踪, 在每次更新平均负载的时候, 更新整个 CFS_RQ 的平均负载| V10 ☑ 5.7 | [PatchWork](https://lore.kernel.org/patchwork/cover/1198654), [lkml](https://lkml.org/lkml/2020/2/21/1386) |
| | [Accumulated fixes for Load/NUMA Balancing reconcilation series](https://lore.kernel.org/patchwork/cover/1203922) | fix 补丁 | | |
diff --git a/study/kernel/01-process/05-schedule/07-cfs/09-pelt/README.md b/study/kernel/01-process/05-schedule/07-cfs/09-pelt/README.md
new file mode 100755
index 0000000..e4e0a2b
--- /dev/null
+++ b/study/kernel/01-process/05-schedule/07-cfs/09-pelt/README.md
@@ -0,0 +1,776 @@
+
+
+# 1 PELT 3.8@2012 per-entity load-tracking
+-------
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2012/8/23 | [per-entity load-tracking](https://lore.kernel.org/patchwork/cover/322242) | per-task 的负载跟踪首次引入内核 | V1 ☑3.8 | [LWN](https://lwn.net/Articles/531853), [PatchWork](https://lore.kernel.org/patchwork/cover/322242), [lkml](https://lkml.org/lkml/2012/8/23/267) |
+
+第一个版本的 PELT 在 3.8 的时候合入主线.
+
+
+```cpp
+e9c84cb8d5f1 sched: Describe CFS load-balancer
+f4e26b120b9d sched: Introduce temporary FAIR_GROUP_SCHED dependency for load-tracking
+5b51f2f80b3b sched: Make __update_entity_runnable_avg() fast
+f269ae0469fc sched: Update_cfs_shares at period edge
+48a1675323fa sched: Refactor update_shares_cpu() -> update_blocked_avgs()
+82958366cfea sched: Replace update_shares weight distribution with per-entity computation
+f1b17280efbd sched: Maintain runnable averages across throttled periods
+bb17f65571e9 sched: Normalize tg load contributions against runnable time
+8165e145ceb6 sched: Compute load contribution by a group entity
+c566e8e9e44b sched: Aggregate total task_group load
+aff3e4988444 sched: Account for blocked load waking back up
+0a74bef8bed1 sched: Add an rq migration call-back to sched_class
+9ee474f55664 sched: Maintain the load contribution of blocked entities
+2dac754e10a5 sched: Aggregate load contributed by task entities on parenting cfs_rq
+18bf2805d9b3 sched: Maintain per-rq runnable averages
+9d85f21c94f7 sched: Track the runnable average on a per-task entity basis
+```
+当时整个 sched_avg 结构体如下所示:
+
+```cpp
+struct sched_avg {
+ /*
+ * These sums represent an infinite geometric series and so are bound
+ * above by 1024/(1-y). Thus we only need a u32 to store them for for all
+ * choices of y < 1-2^(-32)*1024.
+ */
+ u32 runnable_avg_sum, runnable_avg_period;
+ u64 last_runnable_update;
+ unsigned long load_avg_contrib;
+};
+```
+
+| 字段 | 描述 |
+|:-------:|:------:|
+| runnable_avg_sum | 调度实体 sched_entity 在就绪队列上(on_rq) 累计负载 |
+| runnable_avg_period | 调度实体 sched_entity 自创建至今如果持续运行, 所应该达到的累计负载 |
+| last_runnable_update | 上次更新 sched_avg 的时间 |
+| load_avg_contrib | 进程的负载贡献, running_avg_sum * load_weight / avg_period, 参见 [\_\_update_entity_load_avg_contrib](https://elixir.bootlin.com/linux/v3.8/source/kernel/sched/fair.c#L1402) |
+
+
+```cpp
+# https://elixir.bootlin.com/linux/v3.8/source/kernel/sched/fair.c#L1402
+static inline void __update_task_entity_contrib(struct sched_entity *se)
+{
+ u32 contrib;
+
+ /* avoid overflowing a 32-bit type w/ SCHED_LOAD_SCALE */
+ contrib = se->avg.runnable_avg_sum * scale_load_down(se->load.weight);
+ contrib /= (se->avg.runnable_avg_period + 1);
+ se->avg.load_avg_contrib = scale_load(contrib);
+}
+```
+
+cfs_rq 上记录的负载信息, 如下所示:
+
+```cpp
+# https://elixir.bootlin.com/linux/v3.8/source/kernel/sched/sched.h#L227
+#ifdef CONFIG_SMP
+/*
+ * Load-tracking only depends on SMP, FAIR_GROUP_SCHED dependency below may be
+ * removed when useful for applications beyond shares distribution (e.g.
+ * load-balance).
+ */
+#ifdef CONFIG_FAIR_GROUP_SCHED
+ /*
+ * CFS Load tracking
+ * Under CFS, load is tracked on a per-entity basis and aggregated up.
+ * This allows for the description of both thread and group usage (in
+ * the FAIR_GROUP_SCHED case).
+ */
+ u64 runnable_load_avg, blocked_load_avg;
+ atomic64_t decay_counter, removed_load;
+ u64 last_decay;
+#endif /* CONFIG_FAIR_GROUP_SCHED */
+/* These always depend on CONFIG_FAIR_GROUP_SCHED */
+#ifdef CONFIG_FAIR_GROUP_SCHED
+ u32 tg_runnable_contrib;
+ u64 tg_load_contrib;
+#endif /* CONFIG_FAIR_GROUP_SCHED */
+
+ /*
+ * h_load = weight * f(tg)
+ *
+ * Where f(tg) is the recursive weight fraction assigned to
+ * this group.
+ */
+ unsigned long h_load;
+#endif /* CONFIG_SMP */
+```
+
+# 2 PELT 4.1@2015 consolidation of CPU capacity and usage
+-------
+
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2012/8/23 | [consolidation of CPU capacity and usage](https://lore.kernel.org/patchwork/cover/322242/) | CPU 调频会导致 capacity 的变化, 在支持 DVFS 的系统还用最大 capacity 计算负载是不合理的, 因此 PELT 感知 capacity 的变化 | V1 ☑ 4.1 | [PatchWork](https://lore.kernel.org/patchwork/cover/545867), [lkml](https://lkml.org/lkml/2015/2/27/309) |
+
+
+这组补丁为 PELT 引入了 `frequency scale invariance` 的特性, 当前的系统都是支持 DVFS 的, CPU 处于不同的频率对应的 capacity 肯定是不同的, 特别的对于 big.LITTLE 等架构, 不同 cluster 的能效也是不同的. 因此如果两个相同的进程在不同的频率运行相同的时间, 那么其利用率理论上应该是不一样的.
+
+
+```cpp
+dfbca41f3479 sched: Optimize freq invariant accounting
+1aaf90a4b88a sched: Move CFS tasks to CPUs with higher capacity
+caff37ef96ea sched: Add SD_PREFER_SIBLING for SMT level
+dc7ff76eadb4 sched: Remove unused struct sched_group_capacity::capacity_orig
+ea67821b9a3e sched: Replace capacity_factor by usage
+8bb5b00c2f90 sched: Calculate CPU's usage statistic and put it into struct sg_lb_stats::group_usage
+ca6d75e6908e sched: Add struct rq::cpu_capacity_orig
+b5b4860d1d61 sched: Make scale_rt invariant with frequency
+0c1dc6b27dac sched: Make sched entity usage tracking scale-invariant
+a8faa8f55d48 sched: Remove frequency scaling from cpu_capacity
+21f4486630b0 sched: Track group sched_entity usage contributions
+36ee28e45df5 sched: Add sched_avg::utilization_avg_contrib
+```
+
+
+
+| 字段 | 描述 |
+|:-------:|:------:|
+| runnable_avg_sum | 调度实体 sched_entity 在就绪队列上(on_rq) 累计负载 |
+| running_avg_sum | 调度实体 sched_entity 实际在 CPU 上运行(on_cpu) 的累计负载 |
+| avg_period | 调度实体 sched_entity 自创建至今如果持续运行, 所应该达到的累计负载, 等同于原来的 runnable_avg_period, 只是由于这个负载其实跟进程是不是 runnable 没关系, 因此改名 |
+| last_runnable_update | 上次更新 sched_avg 的时间 |
+| decay_count | 用于计算当前 SE blocked 状态时的待衰减周期, 每次调度实体出队时, 保存当前 cfs_rq 的 decay_counter, 下次入队更新时, 两个的差值就是已经经历的周期|
+| load_avg_contrib | 进程的负载贡献, running_avg_sum * load_weight / avg_period, 参见 [\_\_update_entity_load_avg_contrib](https://elixir.bootlin.com/linux/v4.1/source/kernel/sched/fair.c#L2718) |
+| utilization_avg_contrib | 进程的利用率, running_avg_sum * SCHED_LOAD_SCALE / avg_period, 参见 [\_\_update_task_entity_utilization](https://elixir.bootlin.com/linux/v4.1/source/kernel/sched/fair.c#L2744) |
+
+
+这组补丁为了支持了 `frequency scale invariance`, 因此引入了利用率 utilization 的概念. 它表示进程真正占用 CPU 的比率, 这是一个跟当时运行时 CPU 频率(影响 capacity) 有关的累计平均值. 之前计算的 runnable_avg_sum 以及 load_avg_contrib 都同时包含了调度实体 runnable 和 running 的负载信息. 并不能很好的体现利用率. 利用率更在意的是它真正的运行. 因此 sched_avg 中引入了 running_avg_sum 和 utilization_avg_contrib, 分别表示其占用 CPU 的累计负载和利用率.
+
+在不支持 `frequency scale invariance` 之前, 那么每个 1MS(1024us) 窗口, 其如果处于 R 状态, 那么当前窗口贡献的负载值就是 1024. 感知了频率变化之后, `__update_entity_runnable_avg` 每次在更新负载的时候, delta 会根据当前频率对应的 capacity 进行一个缩放.
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.1/source/kernel/sched/fair.c#L2587
+# https://elixir.bootlin.com/linux/v4.1/source/kernel/sched/fair.c#L2597
+static __always_inline int __update_entity_runnable_avg(u64 now, int cpu,
+ struct sched_avg *sa,
+ int runnable,
+ int running)
+{
+ // ......
+ if (delta + delta_w >= 1024) {
+ // ......
+
+ /* Efficiently calculate \sum (1..n_period) 1024*y^i */
+ runnable_contrib = __compute_runnable_contrib(periods);
+ if (runnable)
+ sa->runnable_avg_sum += runnable_contrib;
+ if (running)
+ sa->running_avg_sum += runnable_contrib * scale_freq
+ >> SCHED_CAPACITY_SHIFT;
+ sa->avg_period += runnable_contrib;
+ }
+
+ /* Remainder of delta accrued against u_0` */
+ if (runnable)
+ sa->runnable_avg_sum += delta;
+ if (running)
+ sa->running_avg_sum += delta * scale_freq
+ >> SCHED_CAPACITY_SHIFT;
+ sa->avg_period += delta;
+
+ return decayed;
+}
+```
+
+这组补丁的理念中 load 是一个与频率无关的概念, 但是 utilization 利用率从并不是. 如果我们在两个有不同计算能力的 CPU 上运行两个 nice 0 的死循环程序, 那么他们的负载应该是相差不多的, 因为他们的所期望的计算需求是一样的. 因此在计算的时候只有 running_avg_sum 按照 `DELTA * scale_freq / 1024` 的方式缩放了, 而 runnable_avg_sum 并没有. 这样 [\_\_update_task_entity_utilization](https://elixir.bootlin.com/linux/v4.1/source/kernel/sched/fair.c#L2744) 在根据 running_avg_sum 计算 utilization_avg_contrib 的时候, 就会感知到每次 scale_freq 的变化.
+
+# 3 PELT 4.3@2015 Rewrite runnable load and utilization average tracking
+-------
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2012/8/23 | [Rewrite runnable load and utilization average tracking](https://lore.kernel.org/patchwork/cover/579066) | 重构组调度的 PELT 跟踪, 在每次更新平均负载的时候, 更新整个 CFS_RQ 的平均负载| v10 ☑4.3 | [LWN](https://lwn.net/Articles/579066), [PatchWork](https://lore.kernel.org/patchwork/cover/590249), [lkml](https://lkml.org/lkml/2015/7/15/159) |
+
+
+在之前的 PELT 实现中, 一次更新只跟新一个调度实体的负载变化, 而没有更新 CFS_RQ 上所有调度实体的负载, 这就导致整个就绪队列上 runnable_load_avg 是失真的. 这组补丁对此做了优化. 在每次更新平均负载的时候, 会更新整个 CFS_RQ 的平均负载.
+
+```cpp
+7ea241afbf49 sched/fair: Clean up load average references
+139622343ef3 sched/fair: Provide runnable_load_avg back to cfs_rq
+1269557889b4 sched/fair: Remove task and group entity load when they are dead
+540247fb5ddf sched/fair: Init cfs_rq's sched_entity load average
+6c1d47c08273 sched/fair: Implement update_blocked_averages() for CONFIG_FAIR_GROUP_SCHED=n
+9d89c257dfb9 sched/fair: Rewrite runnable load and utilization average tracking
+cd126afe838d sched/fair: Remove rq's runnable avg
+```
+
+该补丁合入之后, sched_avg 结构体又发生了重大的变化.
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.3/source/include/linux/sched.h#L1204
+/*
+ * The load_avg/util_avg accumulates an infinite geometric series.
+ * 1) load_avg factors the amount of time that a sched_entity is
+ * runnable on a rq into its weight. For cfs_rq, it is the aggregated
+ * such weights of all runnable and blocked sched_entities.
+ * 2) util_avg factors frequency scaling into the amount of time
+ * that a sched_entity is running on a CPU, in the range [0..SCHED_LOAD_SCALE].
+ * For cfs_rq, it is the aggregated such times of all runnable and
+ * blocked sched_entities.
+ * The 64 bit load_sum can:
+ * 1) for cfs_rq, afford 4353082796 (=2^64/47742/88761) entities with
+ * the highest weight (=88761) always runnable, we should not overflow
+ * 2) for entity, support any load.weight always runnable
+ */
+struct sched_avg {
+ u64 last_update_time, load_sum;
+ u32 util_sum, period_contrib;
+ unsigned long load_avg, util_avg;
+};
+```
+
+| 字段 | 描述 |
+|:-------:|:------:|
+| last_update_time | 代替原来的 last_runnable_update, 记录上次更新 sched_avg 的时间 |
+| load_sum | 接替原来的 runnable_avg_sum, 调度实体 sched_entity 在就绪队列上(on_rq) 累计负载之和, 带权重加权 |
+| util_sum | 接替原来的 running_avg_sum, 调度实体 sched_entity 实际在 CPU 上运行(on_cpu) 的累计负载之和 |
+| load_avg | 接替原来的 load_avg_contrib, 作为进程的负载贡献 sa->load_sum / LOAD_AVG_MAX, 参见 [`\_\_update_load_avg`](https://elixir.bootlin.com/linux/v4.3/source/kernel/sched/fair.c#L2518) |
+| util_avg| 接替 utilization_avg_contrib, 成为进程的利用率 sa->util_sum << SCHED_LOAD_SHIFT) / LOAD_AVG_MAX; |
+| period_contrib | 记录了当前进程最后运行的未满一个窗口(1024us) 的剩余时间 |
+| ~~avg_period~~ | ~~调度实体 sched_entity 自创建至今如果持续运行, 所应该达到的累计负载,曾用名 runnable_avg_period~~, 用 LOAD_AVG_MAX 替代 |
+| ~~decay_count~~ | ~~之前用于计算当前 SE blocked 状态时的待衰减周期, 实现了 update_blocked_average 之后~~, 新的算法不需要此结构 |
+
+cfs_rq 中新增了 struct sched_avg 字段, 用来记录的负载信息如下所示:
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.3/source/kernel/sched/sched.h#L367
+# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9d89c257dfb9c51a532d69397f6eed75e5168c35
+struct cfs_rq {
+......
+#ifdef CONFIG_SMP
+ /*
+ * CFS load tracking
+ */
+ struct sched_avg avg;
+ u64 runnable_load_sum;
+ unsigned long runnable_load_avg;
+#ifdef CONFIG_FAIR_GROUP_SCHED
+ unsigned long tg_load_avg_contrib;
+#endif
+ atomic_long_t removed_load_avg, removed_util_avg;
+#ifndef CONFIG_64BIT
+ u64 load_last_update_time_copy;
+#endif
+};
+```
+
+# 4 PELT 4.4@2015 Compute capacity invariant load/utilization tracking
+-------
+
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2015/8/14 | [Compute capacity invariant load/utilization tracking](https://lore.kernel.org/patchwork/cover/590249) | PELT 支持 Capacity Invariant, 对之前, 对 frequency scale invariance 的进一步优化 | v1 ☑4.4 | [LWN](https://lwn.net/Articles/531853), [PatchWork](https://lore.kernel.org/patchwork/cover/590249), [lkml](https://lkml.org/lkml/2015/8/14/296) |
+
+这组补丁 Morten Rasmussen 自打 2014 年就开始推. 当前, 每个实体的负载跟踪仅对利用率跟踪的频率缩放进行补偿. 这个补丁集也扩展了这种补偿, 并增加了计算能力(不同的微架构和/或最大频率/P-state)的不变性. 前者防止在 cpu 以不同频率运行时做出次优负载平衡决策, 而后者确保可以跨 cpu 比较利用率(sched_avg.util_avg), 并且可以直接将利用率与 cpu 容量进行比较, 以确定cpu是否过载.
+
+
+```cpp
+98d8fd812667 sched/fair: Initialize task load and utilization before placing task on rq
+231678b768da sched/fair: Get rid of scaling utilization by capacity_orig
+9e91d61d9b0c sched/fair: Name utilization related data and functions consistently
+e3279a2e6d69 sched/fair: Make utilization tracking CPU scale-invariant
+8cd5601c5060 sched/fair: Convert arch_scale_cpu_capacity() from weak function to #define
+e0f5f3afd2cf sched/fair: Make load tracking frequency scale-invariant
+```
+
+这组补丁之前只有 util_{sum|avg} 按照 CPU freq 进行了缩放. load_{sum|avg} 还保持着原有的速率. 因此当 CPU 频率降低, 运行速度变慢的时候, 进程的负载会变大. 在负载均衡的算法决策中, 使用的都是 load_avg 的信息, 这就导致了负载均衡决策失误. 因此这组补丁将 load_{sum|avg} 也按照 CPU freq 做了缩放. 参见 [sched/fair: Make load tracking frequency scale-invariant](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e0f5f3afd2cf).
+
+# 5 PELT 4.12@ 2017 Optimize `__update_sched_avg`
+-------
+
+
+| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:---:|:------:|:---:|
+| 2017/10/19 | Yuyang Du | [sched/fair: Add documentation and optimize `__update_sched_avg()`](https://lore.kernel.org/patchwork/cover/759850) | 异构系统(比如 big.LITTLE) 上通过 DTS 获取 cpu capacity 等信息 | v7 ☑ 4.15 | [PatchWork](https://lore.kernel.org/patchwork/cover/759850) |
+
+
+```cpp
+83e2ed3990c sched/fair: Move the PELT constants into a generated header
+bb0bd044e65c sched/fair: Increase PELT accuracy for small tasks
+3841cdc31099 sched/fair: Fix comments
+05296e7535d6 sched/fair: Fix corner case in __accumulate_sum()
+
+
+76d034edcf65 sched/Documentation: Add 'sched-pelt' tool
+a481db34b9be sched/fair: Optimize ___update_sched_avg()
+```
+
+Yuyang Du 在 Document 下新增了一个 `sched-pelt.c` 的文件, 用来生成 PELT 需要缓存的数据. 同时 PELT 计算和更新负载的路径 `___update_load_avg` 进行了优化,
+
+```cpp
++/*
++ * Accumulate the three separate parts of the sum; d1 the remainder
++ * of the last (incomplete) period, d2 the span of full periods and d3
++ * the remainder of the (incomplete) current period.
++ *
++ * d1 d2 d3
++ * ^ ^ ^
++ * | | |
++ * |<->|<----------------->|<--->|
++ * ... |---x---|------| ... |------|-----x (now)
++ *
++ * p
++ * u' = (u + d1) y^(p+1) + 1024 \Sum y^n + d3 y^0
++ * n=1
++ *
++ * = u y^(p+1) + (Step 1)
++ *
++ * p
++ * d1 y^(p+1) + 1024 \Sum y^n + d3 y^0 (Step 2)
++ * n=1
++ */
+```
+
+
+
+# 5 PELT 4.15@ 2017 FIE and CIE support for ARM/ARM64
+-------
+
+
+| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:---:|:------:|:---:|
+| 2016/10/19 | Dietmar Eggemann | [CPUs capacity information for heterogeneous systems](https://lore.kernel.org/patchwork/cover/726135) | 异构系统(比如 big.LITTLE) 上通过 DTS 获取 cpu capacity 等信息 | v7 ☑ 4.15 | [PatchWork](https://lore.kernel.org/patchwork/cover/726135) |
+| 2017/08/26 | Dietmar Eggemann | [arm, arm64, cpufreq: frequency- and cpu-invariant accounting support for task scheduler](https://lore.kernel.org/patchwork/cover/834799) | 为 ARM/ARM64 提供 FIE/CIE 功能 | v5 ☑ 4.15 | [PatchWork](https://lore.kernel.org/patchwork/cover/834799) |
+
+
+得益于 ARM big.LITTLE 架构在 ANDROID 的广泛使用, ARM64 是最早支持 FIE 和 CIE 的架构.
+关于 FIE 和 CIE 的具体知识, 大家可以参照本博文最后的背景知识介绍 [FIE 和 CIE].
+
+* FIE 是系统感知同一架构的 CPU 在不同的频点下, 所能提供的计算能力 capacity 是不同的.
+
+* CIE 则使系统感知, 不同架构(比如 big.LITTLE 的小核和大核虽然都使用 ARM 架构, 但是是性能异构的) 的 CPU 即使处于相同频点下, 所能提供的计算能力 capacity 也是不同的.
+
+# 5 PELT 4.15@2017 A bit of a cgroup/PELT overhaul
+-------
+
+Peter 后来进行了一些优化
+
+| 时间 | 作者 |特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2016/06/17 | Peter Zijlstra | [sched/fair: Fix PELT wobblies](https://lore.kernel.org/patchwork/cover/690025) | | v1 ☑ 4.8-rc1 | [PatchWork](ttps://lore.kernel.org/patchwok/cover/690025) |
+| 2017/05/04 | Tejun Heo | [sched/fair: Propagate runnable_load_avg independently from load_avg](https://lore.kernel.org/patchwork/patch/785393) | | v1 ☑ 4.15 | [PatchWork](https://lore.kernel.org/patchwork/patch/782955)
*-*-*-*-*-*-*-*
[PatchWork](https://lore.kernel.org/patchwork/patch/785395) |
+| 2017/09/01 | Peter Zijlstra | [ A bit of a cgroup/PELT overhaul](https://lore.kernel.org/patchwork/cover/827575) | | v2 ☑ 4.15 | 2017/05/12 [PatchWork RFC](https://lore.kernel.org/patchwork/cover/787364/)
*-*-*-*-*-*-*-*
2017/09/01 [PatchWork v1](https://lore.kernel.org/patchwork/cover/827575), [LKML](https://lkml.org/lkml/2017/9/1/331) |
+
+
+```cpp
+17de4ee04ca9 sched/fair: Update calc_group_*() comments
+2c8e4dce7963 sched/fair: Calculate runnable_weight slightly differently
+9a2dd585b2c4 sched/fair: Implement more accurate async detach
+f207934fb79d sched/fair: Align PELT windows between cfs_rq and its se
+144d8487bc6e sched/fair: Implement synchonous PELT detach on load-balance migrate
+1ea6c46a23f1 sched/fair: Propagate an effective runnable_load_avg
+0e2d2aaaae52 sched/fair: Rewrite PELT migration propagation
+2a2f5d4e44ed sched/fair: Rewrite cfs_rq->removed_*avg
+9059393e4ec1 sched/fair: Use reweight_entity() for set_user_nice()
+840c5abca499 sched/fair: More accurate reweight_entity()
+8d5b9025f9b4 sched/fair: Introduce {en,de}queue_load_avg()
+b5b3e35f4149 sched/fair: Rename {en,de}queue_entity_load_avg()
+b382a531b9fe sched/fair: Move enqueue migrate handling
+88c0616ee729 sched/fair: Change update_load_avg() arguments
+c7b50216818e sched/fair: Remove se->load.weight from se->avg.load_sum
+3d4b60d3e3dd sched/fair: Cure calc_cfs_shares() vs. reweight_entity()
+cef27403cbe9 sched/fair: Add comment to calc_cfs_shares()
+7c80cfc99b7b sched/fair: Clean up calc_cfs_shares()
+```
+
+这个版本 sched_avg 结构体变更如下
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.15/source/include/linux/sched.h#L278
+struct sched_avg {
+ u64 last_update_time;
+ u64 load_sum;
+ u64 runnable_load_sum;
+ u32 util_sum;
+ u32 period_contrib;
+ unsigned long load_avg;
+ unsigned long runnable_load_avg;
+ unsigned long util_avg;
+};
+```
+
+| 字段 | 描述 |
+|:-------:|:------:|
+| last_update_time | 代替原来的 last_runnable_update, 记录上次更新 sched_avg 的时间 |
+| load_sum | 接替原来的 runnable_load_sum, 调度实体 sched_entity 在就绪队列上(on_rq) 累计负载 |
+| util_sum | 接替原来的 running_avg_sum, 调度实体 sched_entity 实际在 CPU 上运行(on_cpu) 的累计负载 |
+| load_avg | 接替原来的 load_avg_contrib, 作为进程的负载贡献 sa->load_sum / LOAD_AVG_MAX, 参见 [`\_\_update_load_avg`](https://elixir.bootlin.com/linux/v4.3/source/kernel/sched/fair.c#L2518) |
+| util_avg| 接替 utilization_avg_contrib, 成为进程的利用率 sa->util_sum << SCHED_LOAD_SHIFT) / LOAD_AVG_MAX; |
+| runnable_load_sum | 新增字段, 对于进程或者进程组记录了所有调度实体(on_rq) 的累计负载, 对于 CFS_RQ, 记录了当前就绪队列上所有可运行的任务的负载累计之和 |
+| runnable_load_avg | 新增字段, 调度时期在就绪队列上的平均累计负载 |
+| period_contrib | 记录了当前进程最后运行的未满一个窗口(1024us) 的剩余时间 |
+
+
+**首先这组补丁**修改了负载和平均负载的计算方式, 参照 [commit c7b50216818e sched/fair: Remove se->load.weight from se->avg.load_sum](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c7b50216818e) 中的修改.
+
+```cpp
++/*
++ * sched_entity:
++ *
++ * load_sum := runnable_sum
++ * load_avg = se_weight(se) * runnable_avg
++ *
++ * cfq_rs:
++ *
++ * load_sum = \Sum se_weight(se) * se->avg.load_sum
++ * load_avg = \Sum se->avg.load_avg
++ */
+```
+
+通过这个补丁, 将原来更新负载的 `___update_load_avg` 函数拆成了 `___update_load_sum` 和 `___update_load_avg` 两部分, 计算方法也略有不同.
+
+之前的版本中, 所有的负载都直接通过 `___update_load_avg` 中更新,
+
+而该补丁合入后, 负载更新如下:
+
+* 对于调度实体来说, load_sum/runnable_load_sum 等都不再附带有进程的权重 weight 信息, 而在意他是否处于 running or runnable, 直接在 `___update_load_sum` 中更新. 但是最终 load_avg 则需要附带进程的权重信息, 因此将在 `___update_load_avg` 中更新, 计算中将乘以 se_weight(se).
+
+* 对于 CFS_RQ 来说, 我们经常需要使用 runnable_load_avg 负载值比较不同 CPU 之间的负载信息, 他其实是 RQ 上所有调度实体的 load_sum 之和, 因此需要附带 weight 信息的, 因此在 `___update_load_sum` 更新 CFS_RQ 负载的时候, 直接附带了 weight 信息, 而 `___update_load_avg` 中更新 avg 的时候, 则不应该再附带.
+
+**其次归一了 CFS_RQ 上 runnable_load_avg 的计算, 并处理了组调度情形下, runnable_load_avg 的计算**, load_balance 比较 CPU 的负载时, 为了体现出等待的压力, 之前一直是使用 runnable_load_avg 的负载, 因此早期版本通过 cfs_rq 上增加了一个 runnable_load_avg 字段来完成. 参见 [139622343ef3 sched/fair: Provide runnable_load_avg back to cfs_rq](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=139622343ef3).
+
+对于没开组调度的情况
+
+* runnable_load_avg 是该运行队列上所有可运行任务的直接和.
+
+* 与 load_avg 相反, load_avg 是运行队列上所有任务(task_cpu == this_cpu)的总和, 其中包那些被阻塞的任务
+
+```cpp
+runnable_load_avg = \Sum se->avg.load_avg ; where se->on_rq
+```
+
+然而, 在开启了 cgroup 的情况下, 这就要分开了, 因为 group 实体总是可运行的, 即使它的大部分调度实体都被阻塞.
+
+因此, [commit sched/fair: Propagate an effective runnable_load_avg](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1ea6c46a23f1213d1972bfae220db5c165e27bba) 引入 runnable_weight, 它对于任务实体与常规权重相同, 但对于组实体是实体权重的一部分, 并表示组运行队列的可运行部分.
+
+具体信息可以通过这个 commit 中注释部分的修改窥测一二.
+
+
+```cpp
+ /*
+ * sched_entity:
+ *
++ * task:
++ * se_runnable() == se_weight()
++ *
++ * group: [ see update_cfs_group() ]
++ * se_weight() = tg->weight * grq->load_avg / tg->load_avg
++ * se_runnable() = se_weight(se) * grq->runnable_load_avg / grq->load_avg
++ *
+ * load_sum := runnable_sum
+ * load_avg = se_weight(se) * runnable_avg
+ *
++ * runnable_load_sum := runnable_sum
++ * runnable_load_avg = se_runnable(se) * runnable_avg
++ *
++ * XXX collapse load_sum and runnable_load_sum
++ *
+ * cfq_rs:
+ *
+ * load_sum = \Sum se_weight(se) * se->avg.load_sum
+ * load_avg = \Sum se->avg.load_avg
++ *
++ * runnable_load_sum = \Sum se_runnable(se) * se->avg.runnable_load_sum
++ * runnable_load_avg = \Sum se->avg.runable_load_avg
+ */
+```
+
+具体实现上, 仍然沿用了上面刚提到的 cfs->runnable_load_{sum|avg} 的更新, 这个补丁做了归一, 将 RQ 的负载统计放置到了 sched_avg 上.
+
+然后将此负载 runnable_load_sum 通过 PELT 层次结构传播, 以达到有效的可运行负载平均值
+我们不应该将其与规范的可运行负载平均值混淆.
+
+
+# 5 PELT 4.17@2018 sched/fair: add util_est on top of PELT
+-------
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2020/2/21 | [sched/fair: add util_est on top of PELT](https://lore.kernel.org/patchwork/patch/889504) | 重构组调度的 PELT 跟踪, 在每次更新平均负载的时候, 更新整个 CFS_RQ 的平均负载| V10 ☑ 5.7 | [PatchWork](https://lore.kernel.org/patchwork/cover/1198654), [lkml](https://lkml.org/lkml/2020/2/21/1386) |
+
+
+由于 `PELT` 衰减的特质, 它并不十分适合终端等场景. 对于 `big.LITTLE` 架构, 一个重量级的任务可能在长时间的睡眠唤醒之后, 进程的利用率 `util` 几乎衰减到微乎其微. 那么它很有可能被唤醒到 `LITTLE` 核上, 对性能造成影响. 其次任务的 `util` 每个周期(1024us) 更新一次, 因此对于一个一直运行的任务, 他的 util 是一个不断变化的值, 那么一个正在运行的任务其瞬时的 `PELT` 利用率直接参与调度器的决策不太合理.
+
+因此内核需要一个平滑的估计值, 一个更稳定的利用率估计能更好的标记 CFS 和 RQ 的负载.
+
+
+```cpp
+d519329f72a6 sched/fair: Update util_est only on util_avg updates
+a07630b8b2c1 sched/cpufreq/schedutil: Use util_est for OPP selection
+f9be3e5961c5 sched/fair: Use util_est in LB and WU paths
+7f65ea42eb00 sched/fair: Add util_est on top of PELT
+```
+
+该补丁合入之后, 在 `sched_avg` 结构中新增了一个 `util_est`, 用来标记估计的利用率信息.
+
+```cpp
+# https://elixir.bootlin.com/linux/v5.7/source/include/linux/sched.h#L278
+# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7f65ea42eb00bc902f1c37a71e984e4f4064cfa9
++/**
++ * struct util_est - Estimation utilization of FAIR tasks
++ * @enqueued: instantaneous estimated utilization of a task/cpu
++ * @ewma: the Exponential Weighted Moving Average (EWMA)
++ * utilization of a task
++ *
++ * Support data structure to track an Exponential Weighted Moving Average
++ * (EWMA) of a FAIR task's utilization. New samples are added to the moving
++ * average each time a task completes an activation. Sample's weight is chosen
++ * so that the EWMA will be relatively insensitive to transient changes to the
++ * task's workload.
++ *
++ * The enqueued attribute has a slightly different meaning for tasks and cpus:
++ * - task: the task's util_avg at last task dequeue time
++ * - cfs_rq: the sum of util_est.enqueued for each RUNNABLE task on that CPU
++ * Thus, the util_est.enqueued of a task represents the contribution on the
++ * estimated utilization of the CPU where that task is currently enqueued.
++ *
++ * Only for tasks we track a moving average of the past instantaneous
++ * estimated utilization. This allows to absorb sporadic drops in utilization
++ * of an otherwise almost periodic task.
++ */
++struct util_est {
++ unsigned int enqueued;
++ unsigned int ewma;
++#define UTIL_EST_WEIGHT_SHIFT 2
++};
++
+ /*
+ * The load_avg/util_avg accumulates an infinite geometric series
+ * (see __update_load_avg() in kernel/sched/fair.c).
+@@ -335,6 +363,7 @@ struct sched_avg {
+ unsigned long load_avg;
+ unsigned long runnable_load_avg;
+ unsigned long util_avg;
++ struct util_est util_est;
+ };
+```
+
+# PELT 4.19@2018 track CPU utilization
+-------
+
+| 时间 | 作者 |特性 | 描述 | 是否合入主线 | 链接 |
+|:-----:|:---:|:--:|:----:|:----------:|:---:|
+| 2018/06/08 | Vincent Guittot | [track CPU utilization](https://lore.kernel.org/patchwork/cover/948961) | 增加了 RT_RQ/DL_RQ 以及 IRQ 的 PELT load tracking | v6 4.19-rc1 | [v4 sched/rt: track rt rq utilization](https://lore.kernel.org/patchwork/patch/934368)
*-*-*-*-*-*-*-*
[PatchWork v5](https://lore.kernel.org/patchwork/cover/948961) |
+
+linux 内核中调度器提供了多种调度类, 但是到目前为止, PELT 都只有支持 CFS 和 CFS_RQ 的负载均衡. 因此这组补丁为 RT_RQ/DL_RQ 以及 IRQ 增加了 PELT 跟踪.
+
+
+
+# 6 PELT 5.1@2019 update scale invariance of PELT
+-------
+
+| 时间 | 作者 |特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:----:|:---:|:------:|:---:|
+| 2019/01/16 | Vincent Guittot | [sched/fair: update scale invariance of PELT](https://lore.kernel.org/patchwork/cover/1034952) | 将 Frequency Invariance 的计算和更新放到 RQ 上, 而不是按照每个调度实体和 RQ 去完成. | v9 5.1-rc1 | [v3](https://lore.kernel.org/patchwork/patch/784059)
*-*-*-*-*-*-*-*
[v9](https://lore.kernel.org/patchwork/cover/1034952) |
+
+
+Frequency Invariance 通过按照 CPU 频率和实际 capacity 对进程运行的时间 delta 进行缩放, 以反映在运行时间内已经完成的实际工作量.
+
+但是之前 scale_delta 的计算和更新都是按每个实体和 rt/dl/cfs_rq 完成, 现在更新了这种算法, 将计算统一放到 RQ 上去进行, 这样只需要在每个 tick 中更新一次, 因此我们不再需要维护每个 rt_rq/dl_rq/cfs_rq 的 sched_avg 和 stolen_idle_time. 确保了所有的 PELT 计算将始终在同一个 RQ 级别进行. 参见[sched/fair: Update scale invariance of PELT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=23127296889fe84b0762b191b5d041e8ba6f2599).
+
+```cpp
+10a35e6812aa sched/pelt: Skip updating util_est when utilization is higher than CPU's capacity
+23127296889f sched/fair: Update scale invariance of PELT
+62478d9911fa sched/fair: Move the rq_of() helper function
+```
+
+
+# 7 PELT 5.7@2020 Support frequency invariance for X86
+-------
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2020/1/22 | [Add support for frequency invariance for (some) x86](https://lore.kernel.org/patchwork/cover/1183773) | 支持 x86 FI(frequency invariance) | v5 5.7-rc1 | [Add support for frequency invariance for (some) x86](https://lore.kernel.org/patchwork/cover/1183773) |
+
+得益于 ARM big.LITTLE 架构和 DynamicIQ 架构在安卓的广泛使用, FIE 和 CIE 的支持, ARM64 走在了其他架构的最前面, 但是 X86_64 服务器虽然更看重吞吐量, 但是对功耗的追求也是永恒的话题, 更何况 X86_64 也有一些低端嵌入式芯片, 因此这组补丁补齐了 X86 架构下 FIE 的支持.
+
+
+# 6 PELT 5.7@2020 remove runnable_load_avg and improve group_classify
+-------
+
+| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:---:|:----:|:---:|:---:|:----------:|:----:|
+| 2020/2/21 | Vincent Guittot | [remove runnable_load_avg and improve group_classify](https://lore.kernel.org/patchwork/cover/1198654) | 重构组调度的 PELT 跟踪, 在每次更新平均负载的时候, 更新整个 CFS_RQ 的平均负载| V10 ☑ 5.7 | [PatchWork](https://lore.kernel.org/patchwork/cover/1198654), [lkml](https://lkml.org/lkml/2020/2/21/1386) |
+
+
+```cpp
+070f5e860ee2 sched/fair: Take into account runnable_avg to classify group
+9f68395333ad sched/pelt: Add a new runnable average signal
+0dacee1bfa70 sched/pelt: Remove unused runnable load average
+6499b1b2dd1b sched/numa: Replace runnable_load_avg by load_avg
+6d4d22468dae sched/fair: Reorder enqueue/dequeue_task_fair path
+```
+
+之前的负载均衡流程中, 较多的使用了 runnable_load_avg, 但是这也引入了不少问题.
+
+后来 Vincent Guittot 对整个 load_balance 和 numa_balancing 进行了重构和优化.
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2019/10/18 | Vincent Guittot | [sched/fair: rework the CFS load balance](https://linuxplumbersconf.org/event/4/contributions/480) | 重构 load balance | v4 ☑ | [LWN](https://lwn.net/Articles/793427), [PatchWork](https://lore.kernel.org/patchwork/patch/1141687), [lkml](https://lkml.org/lkml/2019/10/18/676) |
+| 2020/2/21 | Vincent Guittot | [remove runnable_load_avg and improve group_classify](https://lore.kernel.org/patchwork/cover/1198654) | 重构组调度的 PELT 跟踪, 在每次更新平均负载的时候, 更新整个 CFS_RQ 的平均负载| V10 ☑ 5.7 | [PatchWork](https://lore.kernel.org/patchwork/cover/1198654), [lkml](https://lkml.org/lkml/2020/2/21/1386) |
+| 2019/2/24 | Mel Gorman | [Reconcile NUMA balancing decisions with the load balancer](https://linuxplumbersconf.org/event/4/contributions/480) | 重构 load balance | v4 ☑ | [LWN](https://lwn.net/Articles/793427), [PatchWork](https://lore.kernel.org/patchwork/cover/1199507), [lkml](https://lkml.org/lkml/2019/10/18/676) |
+| | [Accumulated fixes for Load/NUMA Balancing reconcilation series](https://lore.kernel.org/patchwork/cover/1203922) | fix 补丁 | | |
+
+其中 [rework the CFS load balance](https://lore.kernel.org/patchwork/patch/1141687) 中
+
+[commit b0fb1eb4f04a sched/fair: Use load instead of runnable load in load_balance()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b0fb1eb4f04a) 将 load_balance 路径下原来使用 RQ runnable_load_avg 的地方都修改成了 load_avg.
+[commit c63be7be59de sched/fair: Use utilization to select misfit task](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c63be7be59de) 修改了 misfit task 的路径.
+[commit 11f10e5420f6 sched/fair: Use load instead of runnable load in wakeup path](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=11f10e5420f6) 修改了唤醒路径.
+
+接着的 [remove runnable_load_avg and improve group_classify](https://lore.kernel.org/patchwork/cover/1198654), Vincent Guittot 进一步修正了 numa_balance 下使用 runnable_load_avg 的路径. 这组补丁包含了几个 RFC 的补丁, 最后的 Mel Gorman 继续完善了 Vincent Guittot 在 numa balance 上的工作. [Reconcile NUMA balancing decisions with the load balancer v6](https://lore.kernel.org/patchwork/cover/1199507)
+
+[6499b1b2dd1b sched/numa: Replace runnable_load_avg by load_avg](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6499b1b2dd1b) 将 numa_balancing 路径下之前使用 cfs_rq_runnable_load_avg(RQ runnable_load_avg) 的地方全改成了 cfs_rq_load_avg(RQ load_avg).
+
+[0dacee1bfa70 sched/pelt: Remove unused runnable load average](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0dacee1bfa70) 前面使用 cfs_rq_runnable_load_avg 的路径都修正以后, 内核已经没有使用 cfs_rq_runnable_load_avg 的地方了, 因此删除了 sched_avg 等结构体中的 runnable_load_{sum|avg} 字段.
+
+[9f68395333ad sched/pelt: Add a new runnable average signal](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9f68395333ad) 前脚把 runnable_load_{sum|avg} 删掉了, 这个补丁找了一种新的 runnable 计算方式来(runnable_{sum|avg})替代旧的方式. 之前 runnable_load_{sum|avg} 的计算包含了太多作者认为不合适的信息(比如 se 和 group 的 runnable 负载 包含了太多的权重 load_weight 信息), 而作者**更希望 runnable 能体现出等待进程的数量, 这个才能突出反应示 CFS_RQ 上的可运行压力**. 新的 runnable 负载计算方式只在意有多少进程在等待, 而不关心他们的 load_weight, 因此可以理解为该信号跟踪 RQ 上任务的等待压力, 有助于更好地定义 RQ 的状态. 这种计算方式与 load_{sum|avg} 的计算方式是类似的, 这样的好处是, 我们**可以直接将 runnable 的负载和 running 的负载进行比较**. 当任务竞争同一个 RQ时, 它们的可运行平均负载将高于 util_avg, 因为它将包含等待时间(不再包含之前的 load_weight 信息), 我们可以使用这个信号更好地对 CFS_RQ 进行分类.
+
+
+# 10 背景知识
+-------
+
+## 10.1 进程的最大运行负载
+-------
+
+进程投入运行至今, 如果一直运行那么能达到的负载最大值是多少呢?
+
+最早的 PELT 3.8 版本是在 sched_avg 中存储了一个字段 runnable_avg_period, 用来表示进程自投入运行至今, 假设一直运行, 所能达到的最大负载, 每次 `__update_entity_runnable_avg` 都会更新, 参见 [9d85f21c94f7 sched: Track the runnable average on a per-task entity basis](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9d85f21c94f7f7a84d0ba686c58aa6d9da58fdbb). 此时 runnable_avg_period 的计算公式如下所示:
+
+```cpp
+runnable_avg_period(t) = \Sum 1024 * y^i
+
+load_avg_contrib = runnable_load_sum * load.weight / (runnable_avg_period + 1);
+
+```
+
+接着的 PELT 4.1 版本, 引入了利用率, utilization_avg_contrib 的计算跟 load_avg_contrib 类似, 只不过只计算了 running 状态的负载. 这里由于 runnable_avg_period 不再只是跟 runnable 的负载有关系, 因此改名为 avg_period. 参见 [36ee28e45df5 sched: Add sched_avg::utilization_avg_contrib](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=36ee28e45df50c2c8624b978335516e42d84ae1f).
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.1/source/include/sched/sched.h#L2718
+load_avg_contrib = runnable_avg_sum * load.weight / (avg_period + 1);
+
+# https://elixir.bootlin.com/linux/v4.1/source/include/sched/sched.h#L2744
+utilization_avg_contrib = running_avg_sum * SCHED_LOAD_SCALE / (avg_period + 1);
+```
+
+后来 PELT 4.3 重构代码的时候, 删除了 avg_period, 转而使用 LOAD_AVG_MAX 替代. 参见 [9d89c257dfb9 sched/fair: Rewrite runnable load and utilization average tracking](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9d89c257dfb9).
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.1/source/include/sched/sched.h#L12632
+load_avg = load_sum / LOAD_AVG_MAX;
+
+runnable_load_avg = runnable_load_sum / LOAD_AVG_MAX;
+
+util_avg = util_sum * SCHED_LOAD_SCALE / LOAD_AVG_MAX;
+```
+
+
+使用 LOAD_AVG_MAX 相当于假定最后窗口的 period_contrib 时间已经耗尽, 并且被认为是空闲的. 从而导致 CPU util 的负载永远漏掉了一个窗口的值, 对于一个负载非常重的 util, 它的值本应该保持在 1023 左右, 但是却一直维持在 [1002..1024] 的区间内. 因此在 PELT 4.13, 考虑使用 LOAD_AVG_MAX 直接作为进程的最大负载值是不合理的. 因此补丁 [625ed2bf049d sched/cfs: Make util/load_avg more stable](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=625ed2bf049d5a352c1bcca962d6e133454eaaff) 中考虑了一种最简易的计算方法.
+
+```cpp
+LOAD_AVG_MAX * y + 1024(us) = LOAD_AVG_MAX
+max_value = LOAD_AVG_MAX * y + sa->period_contrib
+```
+
+那么进程如果打创建开始就一直投入运行, 那么能达到的负载最大值为:
+
+```cpp
+LOAD_AVG_MAX - 1024 + sa->period_contrib = LOAD_AVG_MAX - (1024 - sa->period_contrib)
+```
+
+语义上可以理解为, 最后一个的窗口只运行了 `sa->period_contrib`, 这个窗口不需要衰减.
+
+
+## 10.2 FIE 和 CIE
+-------
+
+内核当前 PELT 在计算负载的时候, 考虑了两个跟 CPU 性能和频率相关的变量(scale_freq 和 scale_cpu
+). 如下所示:
+
+```cpp
+# https://elixir.bootlin.com/linux/v4.4/source/kernel/sched/fair.c#L2587
+
+static __always_inline int
+__update_load_avg(u64 now, int cpu, struct sched_avg *sa,
+ unsigned long weight, int running, struct cfs_rq *cfs_rq)
+{
+ // ......
+ scale_freq = arch_scale_freq_capacity(NULL, cpu);
+ scale_cpu = arch_scale_cpu_capacity(NULL, cpu);
+ // ......
+}
+```
+
+这些是 EAS 调度器提出的两个概念, 分别对应了 FIE(Frequency Invarient Engine) 和 CIE(CPU Invariant Engine).
+
+1. FIE 在计算 CPU 负载时要考虑 CPU 频率的变化.
+
+2. CIE 是考虑不同 CPU 架构(比如 ARM big.LITTLE 架构, 小核使用 A5x, 大核可能是 A7x), 即使在同样的频点下, 不同架构的 CPU 所能提供的计算能力也是不同的.
+
+
+* 现在的 CPU 都是支持 DVFS 调频的, CPU 在不同的频率下能提供的计算能力是不同的, 为了量化这个值, 内核使用了 scale_freq.
+
+
+`scale_freq` 表示的是当前 CPU 当前频点下的计算能力按照 1024 的归一, 就是将 CPU当前频率和最高频率的比值, 按照 1024(SCHED_CAPACITY_SCALE) 为最大值进行 scale.
+
+假设最大频率 max_freq 下计算能力为 1024, 那么 curr_freq 下的计算能力是多少呢?
+
+其计算方式如下所示:
+
+```cpp
+ curr_freq
+scale_freq = ---------- * SCHED_CAPACITY_SCALE
+ max_freq
+```
+
+举例来说, CPU0 最大频率为 2GHZ, 当前频率为 1.5GHZ, 那么 scale_freq = 1024 * 1.5 / 2 = 768;
+
+[sched/fair: Make load tracking frequency scale-invariant](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e0f5f3afd2cffa96291cd852056d83ff4e2e99c7)
+
+
+* 对于 big.LITTLE 等性能异构的系统, 不同 cluster 的 CPU 最大计算能力是不同的. scale_cpu 就是用来表示这个 CPU 实际的最大计算能力.
+DTS 中可以通过 "capacity-dmips-hmz" 字段配置这个值. 参见内核启动初始化流程 [`topology_parse_cpu_capacity`](https://elixir.bootlin.com/linux/v5.10/source/drivers/base/arch_topology.c#L166).
+
+比如我们拿到的一台手机产品, 4 个 A53 小核(最大频率 1.5G), scale_cpu = 488, 4 个 A72 大核(最大频率 2.0G), scale_cpu = 1024.
+
+ARM 的 Dietmar Eggemann 实现了 CIE 的支持, 参见 [`sched/fair: Make utilization tracking CPU scale-invariant`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e3279a2e6d697e00e74f905851ee7cf532f72b2d).
+
+```cpp
+scale_delta = scale(delta, scale_freq) = delta * scale_freq / SCHED_CAPACITY_SCALE;
+
+ scale(scaled_delta, scale_cpu)
+= scaled_delta * scale_cpu / SCHED_CAPACITY_SCALE
+= delta * scale_freq * scale_cpu / SCHED_CAPACITY_SCALE^2
+```
+
+真正计算 util 的时候, 同时考虑了 FIE 和 CIE.
+
+## 10.3 FI Support
+-------
+
+| 时间 | 特性 | 描述 | 是否合入主线 | 链接 |
+|:----:|:----:|:---:|:------:|:---:|
+| 2012/8/23 | [consolidation of CPU capacity and usage](https://lore.kernel.org/patchwork/cover/322242/) | CPU 调频会导致 capacity 的变化, 在支持 DVFS 的系统还用最大 capacity 计算负载是不合理的, 因此 PELT 感知 capacity 的变化 | v1 ☑ 4.1 | [PatchWork](https://lore.kernel.org/patchwork/cover/545867), [lkml](https://lkml.org/lkml/2015/2/27/309) |
+| 2015/8/14 | [Compute capacity invariant load/utilization tracking](https://lore.kernel.org/patchwork/cover/590249) | PELT 支持 Capacity Invariant, 对之前, 对 frequency scale invariance 的进一步优化 | V1 ☑4.4 | [LWN](https://lwn.net/Articles/531853), [PatchWork](https://lore.kernel.org/patchwork/cover/590249), [lkml](https://lkml.org/lkml/2015/8/14/296) |
+| 2019/01/16 | [sched/fair: update scale invariance of PELT](https://lore.kernel.org/patchwork/cover/1034952) | v9 ☑ 5.1-rc1 | [v3](https://lore.kernel.org/patchwork/patch/784059)
*-*-*-*-*-*-*-*
[v9](https://lore.kernel.org/patchwork/cover/1034952) |
+
+
+
+
+
+# 7 参考资料
+-------
+
+[task 的 load_avg_contrib 的更新参考](https://www.codenong.com/cs106477101)
+
+[CFS调度器:负载跟踪与更新](https://zhuanlan.zhihu.com/p/158185705)
+