- 前言
- 一些节点的结论
- schedtune节点
- sysctl节点
- sched_boost
- sched_busy_hysteresis_enable_cpus
- sched_busy_hyst_ns
- sched_coloc_busy_hysteresis_enable_cpus
- sched_coloc_busy_hyst_ns
- sched_dynamic_ravg_window_enable
- sched_ravg_window_nr_ticks
- prefer_spread_on_idle
- sched_force_lb_enable
- sysctl_sched_user_hint
- sched_window_stats_policy
- sched_group_upmigrate
- sched_group_downmigrate
- sched_min_task_util_for_colocation
- sched_task_unfilter_period
- sched_min_task_util_for_boost
- 可Hack的点
- 总结
前言
几天前心血来潮,给内核关掉了WALT强行切换到了PELT负载追踪,但遗憾的是高通的魔改用力过猛,以至于直接切换回PELT(并修复编译)后并不能正常进行负载分配(负载迁移到大核之后难以摊开,EAS下负载全部堆在一个核心上,直到满频过载之后fall back to 传统的调度才会把负载摊开。。。)。当然,去谷歌Pixel的内核那边掏修复补丁是个办法(谷歌不走寻常路的把高通打磨了半天的WALT换回了PELT),但是,我觉得谷歌其实,emmmm也不是这么可信吧(其实是懒得解决移植的冲突,意外情况太多)。那就顺着逻辑,4.4内核上自带的是PELT,WALT是移植的,那就打磨PELT,现在WALT变成了原生支持,PELT反而需要一堆补丁来修复,那就打磨WALT吧。
其实还有一个点让我突然想好好研究一下WALT的,那就是我发现PELT下小核的负载比默认WALT下的正常的多,WALT下酷安滑屏小核负载基本都在15%-20%(PELT下在40%以上),而三个中核却有较高的负载(30%以上),这是不科学的,因为能量模型应该已经告诉了内核小核最高频的能效高于大核最低频,而且此时小核性能反而更强
,这种莫名的负载迁移确实让我很想调查一下。
一些节点的结论
懒得写过程了,摸鱼所得,毕竟这些调度算法也不是一时半会儿能看懂的,也都只知道了个大概
schedtune节点
sched_boost_no_override
见下方sched_boost
schedtune.colocate
指定一个cgroup的任务是不是相关线程组,这在负载迁移和选择核心时会起作用,下面再说
sysctl节点
位于/proc/sys/kernel
sched_boost
这个其实很久以前就研究过了,不过还是记录下吧
0 -> 小核优先负载 + 相关线程组负载如果超过sched_group_upmigrate
则该线程组优先中核 (详细见下)
1 -> 大核优先负载
2 -> 指定了sched_boost_no_override
的任务中核优先负载,其他任务小核优先(相关线程组会被忽略)
3 -> 0的内容 + 负载缩放使频率调度更激进?
sched_busy_hysteresis_enable_cpus
sched_busy_hyst_ns
sched_coloc_busy_hysteresis_enable_cpus
sched_coloc_busy_hyst_ns
这几个是涉及cpuidle的控制,其中hysteresis_enable_cpus
是指定CPU的bitmask,hyst_ns
是时间,这里的时间大概是指任务执行后暂停cpuidle的时间?默认情况下powerhal会在触摸屏幕时对sched_busy_hysteresis_enable_cpus
指定所有核心,对sched_busy_hyst_ns
指定一个忘了是多少的时间,手指离开屏幕时复原,这个造成的效果是在中核/大核优先负载时产生明显的jitter下降,小核优先负载时效果不明显。
有没有coloc的区别是在于是否针对相关线程组,指针对所有任务还是仅在相关线程组活跃时在指定的核心上进行。
sched_dynamic_ravg_window_enable
sched_ravg_window_nr_ticks
这两个玩意儿提供了神奇的动态控制WALT window size的功能,当内核的HZ被设为250时会默认启用。
注意:当HZ不为250时,就算你打开这个功能它也不会工作,所以这节点完全多余。。
它默认为90Hz刷新率使用12ms的时间窗口
为120Hz刷新率使用8ms时间窗口
(display那边在刷新率改变时主动调用)
你也可以通过sched_ravg_window_nr_ticks
设置更小的时间窗口,窗口的大小算法是1000/HZ得到最小窗口大小,最小窗口大小 * sched_ravg_window_nr_ticks
得到指定窗口大小
注意:你只能设置更小的时间窗口,更大的时间窗口(比刷新率指定的要大)会被直接忽略(里面有个min的比较)(所以我觉得这两个节点完全没有意义)
更小的时间窗口意味着更快的负载响应速度,但是也意味着轻负载更容易被判断为重负载导致不必要的任务迁移
prefer_spread_on_idle
决定了是否要主动把负载迁移到空闲的核心上,迁移到空闲核心意味着从(idle状态唤醒这些核心) | (阻止刚刚结束任务的核心进入idle)状态,这可能导致更高的耗电,在某些情况下能够提升性能
0 -> 否
1 -> 小核 是
2 -> 全核 是
3 -> 小核 仅在刚刚idle时是 (意味着不刻意唤醒深度idle的核心)
4 -> 全核 仅在刚刚idle时是 (意味着不刻意唤醒深度idle的核心)
sched_force_lb_enable
这个东西本身不属于WALT,既然看过了就写一下
决定是否要在小核出现需要向上迁移的重负载时,对非小核进行负载平衡
sysctl_sched_user_hint
临时性的对负载计算进行一点点干预(对负载进行放缩)?
最大可写入1000执行强制负载向上转移?
这个节点的值会在请求被处理后马上还原为0
sched_window_stats_policy
牵扯到window内计算负载的几种方式
#define WINDOW_STATS_RECENT 0
#define WINDOW_STATS_MAX 1
#define WINDOW_STATS_MAX_RECENT_AVG 2
#define WINDOW_STATS_AVG 3
默认是2 想知道到底有啥区别可以理解一下下面的代码,我觉得默认的2就已经挺合理的了
/* Push new 'runtime' value onto stack */
widx = sched_ravg_hist_size - 1;
ridx = widx - samples;
for (; ridx >= 0; --widx, --ridx) {
hist[widx] = hist[ridx];
sum += hist[widx];
if (hist[widx] > max)
max = hist[widx];
}
for (widx = 0; widx < samples && widx < sched_ravg_hist_size; widx++) {
hist[widx] = runtime;
sum += hist[widx];
if (hist[widx] > max)
max = hist[widx];
}
p->ravg.sum = 0;
if (sysctl_sched_window_stats_policy == WINDOW_STATS_RECENT) {
demand = runtime;//0
} else if (sysctl_sched_window_stats_policy == WINDOW_STATS_MAX) {
demand = max;//1
} else {
avg = div64_u64(sum, sched_ravg_hist_size);
if (sysctl_sched_window_stats_policy == WINDOW_STATS_AVG)
demand = avg;//3
else
demand = max(avg, runtime);//2
}
sched_group_upmigrate
sched_group_downmigrate
这两个玩意儿决定了相关线程组任务的集体向上、向下迁移阈值
当负载大于sched_group_upmigrate
时,这个组会被设为skip_min
,而此时组内任务会中核优先负载(同一组的新任务也是中核优先负载)(不过还是有条件可以让某个组内任务负载在小核上,下面再说)
当负载小于sched_group_upmigrate
时,skip_min
会消失,整个组的任务小核优先负载
丢在哪个核心上决定了要通过哪个核心知道任务的实际性能需求(丢上去之前是不知道的),而这也决定了任务在因为性能不足/过剩而迁移前的性能
sched_min_task_util_for_colocation
sched_task_unfilter_period
这就是上一条所说的“不过还是有条件可以让某个组内任务负载在小核上”
如果相关线程组内的某个任务,负载小于sched_min_task_util_for_colocation
的时间大于sched_task_unfilter_period
,那么它就无法和线程组内的其他任务呆在一起,而被迫被丢回小核了
sched_min_task_util_for_boost
类似上一条,决定了在sched_boost=2时要把哪些boost在中/大核上的任务丢回小核
剩下的节点基本就是字面意思了,另外有一两个节点暂且还没搞清楚作用,那就忽略吧~
可Hack的点
永远小核优先负载
kernel/sched/fair.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index be82b1c345ce..4fbf0b027abf 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6920,6 +6920,7 @@ static inline bool get_rtg_status(struct task_struct *p)
static inline bool task_skip_min_cpu(struct task_struct *p)
{
return sched_boost() != CONSERVATIVE_BOOST &&
+ sched_boost() != NO_BOOST &&
get_rtg_status(p) && p->unfilter;
}
作用:在sched_boost = 0时屏蔽schedtune.colocate(相关线程组)的作用,所有任务在小核上测试负载水平,然后决定是否迁移到中/大核,当然,可能有亿点卡,可以通过调window size 或 HZ 来缓解,直接解决了前言中所说的小核负载不高的问题
禁用hysteresis特性
一行干掉上面的四个相关节点(
kernel/sched/sched_avg.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/sched/sched_avg.c b/kernel/sched/sched_avg.c
index 1e7bb64e3c2d..9981b2488bb2 100644
--- a/kernel/sched/sched_avg.c
+++ b/kernel/sched/sched_avg.c
@@ -221,6 +221,8 @@ u64 sched_lpm_disallowed_time(int cpu)
u64 now = sched_clock();
u64 bias_end_time = atomic64_read(&per_cpu(busy_hyst_end_time, cpu));
+ return 0;
+
if (now < bias_end_time)
return bias_end_time - now;
总结
只是简单的摸了一下kernel/sched的相关代码,调速器的具体工作细节还没有摸清,也没有办法解释调度器中一些参数的作用(所以有个pl相关的节点还是没法解释),有生之年会再看看的(笑
分析的很准确啊,基本都对,高通有对应的文档
可以分享一下相关文档吗?我觉得对此感兴趣的人应该有很多 :)
这个文档是高通给客户开的,都带水印的,我不敢发出来 0.0
pl(predict load),是一种负载预测feature