Skip to main content
 首页 » 操作系统

Linux SchedTune学习笔记

2022年07月19日55oomusou

本文仅是对kernel中的document进行翻译,sched-tune内核文档路径:kernel/Documentation/scheduler/sched-tune.txt

1. 为何引入schedtune?

schedutil是一个基于利用率驱动的cpu频率governor。它允许调度器为了cpu上运行的task选出最优的工作频率点(DVFS operating point: OPP)。

但是,有时候我们需要故意进行boost,来满足特定场景下的性能要求,尽管这样会产生更大的功耗。比如,为了缩短task的响应时间,我们希望task运行在一个比实际cpu带宽要求更高的OPP。

还有一个重要原因是我们想用schedutil governor来替代当前所有的CPU Freq pollicy。schedutil这个governor是基于event的,而当前governor是基于采样的,所以schedutil对task选择最优OPP的更加迅速。但是仅仅跟踪实际的task使用率可能不足以表达当前的性能。比如,它不能做到类似“performance”、“interactive” CPUFreq governor的相关行为。

于是,就引入了schedtune。它是一套处于governor架构上层的、可调节的工具,扩展了对task performance boosting的支持。

performance boosting的意思:缩短task启动的时间。例如,一个task从唤醒到其再次休眠或者退出的时间;或有一个周期执行的task,在合适的OPP下,每20s执行5s,当boost之后,执行时间都会缩短至5s以下。


2. 什么是SchedTune

schedtune提供了一套用户接口的工具,用于功耗-性能调节。schedtune是cgroup的一个子系统。所以在cgroup的mount节点下,stune分别为每个group,都提供了2个调节开关:

/<stune cgroup mount point>/schedtune.prefer_idle 
/<stune cgroup mount point>/schedtune.boost

在android平台下,目录为:

htc_imedugl:/ # ls /dev/stune 
background            notify_on_release  schedtune.prefer_idle          
cgroup.clone_children release_agent      schedtune.sched_boost_enabled  
cgroup.procs          rt                 schedtune.sched_boost_no_override  
cgroup.sane_behavior  schedtune.boost    tasks                          
foreground            schedtune.colocate top-app  

user-space可以通过stune提供的接口随意改变相关相关属性,来适配当前的task运行的环境。比如,background、interactive、low-priority。

2.1 Boosting

boost的值用int型表示,范围为[0, 100]。

boost默认值为0,代表CFS调度器会工作在能耗最低的状态。这也意味着schedutil使task跑在最低的OPP。

boost值100,则表示调度器为工作在性能最高的状态,同时OPP也处在最大。

0-100的范围可以根据其他场景来进行适当调节。比如,优化交互的响应、电池电量变化等。

总体的SchedTune模块是架构在PELT(Per-Entity Load Tracking)和会影响OPP的schedutil两者之上的

每次task在cpu上申请,就有机会针对工作负载需要而调节该cpu的cpufreq。实际使用的cpufreq会被task所在cgroup的boost值影响

在frameworks存在的这种影响能够在尽量少修改调度器的前提下,实现仅仅通过一个简单的开关来达到多种不同动作。

在EAS调度器中,我们使用经过boost之后的task util和cpu util来计算energy,以及用于energy-aware的task分配。


2.2 prefer_idle

这是一个控制调度器节省功耗优先,还是性能优先的flag。

默认值0,会让CFS调度器根据energy-aware wakeup策略来分配在group中的task。(功耗优先)

当值设为1,会让CFS调度器分配task时,有最小的wakeup延迟。(性能优先)

android平台下使用这个flag用来表示正在和用户交互的应用。

设为1的节点:

dev/stune/foreground/schedtune.prefer_idle 
dev/stune/top-app/schedtune.prefer_idle

设为0节点:

dev/stune/background/schedtune.prefer_idle 
dev/stune/rt/schedtune.prefer_idle

3. Signal Boosting策略

整个PELT工作是基于一系列对cpu带宽需求、cpu capacity的负载跟踪signal而构建的。而SchedTune背后就是通过变大其中一些负载跟踪的signal,来使task或者runqueue看上去需要比实际的情况更多的性能。而具体是哪个信号,取决于特定的“consumer”。但不管怎么样,为“boosting signal”定义一个简单有效的固定方法是很重要的。

boosting策略如下定义了user-space控制的sched_cfs_boost值是如何翻译成内部“margin”参数值,并加到其需要影响的信号上的:

margin         := boosting_strategy(sched_cfs_boost, signal) 
boosted_signal := signal + margin

在SchedTune中实现的boosting strategy叫做:'Signal Proportional Compensation' (信号比例补偿,简称SPC)。################# 有了SPC,sched_cfs_boost就会被成比例地补偿到原signal值,最后赋给margin。如果计算之后signal到达了最大值,那么sched_cfs_boost就等于当前signal的实际值与最大值的差值。

因为调节用的signal使用宏 SCHED_CAPACITY_SCALE (1024)作为最大值,所以margin就为:

margin := sched_cfs_boost * (SCHED_CAPACITY_SCALE - signal)

使用这种boosting strategy:

100% sched_cfs_boost代表着signal放大到最大值。

每一个在sched_cfs_boost内的值,都会放大signal的值

假如使用SPC的boosting策略来选择OPP的话,那么:

-   0% boosting: run the task at the minimum OPP required by its workload 
- 100% boosting: run the task at the maximum OPP available for the CPU 
-  50% boosting: run at the half-way OPP between minimum and maximum

这说明在50% boosting的时候,task会在理论最大性能一半的情况下运行。

下图表示了SPC boost之后的信号图(来自sched-tune.txt):

 a) "-" represents the original signal 
 b) "b" represents a  50% boosted signal 
 c) "p" represents a 100% boosted signal 
 
 
   ^ 
   |  SCHED_LOAD_SCALE 
   +-----------------------------------------------------------------+ 
   |pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp 
   | 
   |                                             boosted_signal 
   |                                          bbbbbbbbbbbbbbbbbbbbbbbb 
   | 
   |                                            original signal 
   |                  bbbbbbbbbbbbbbbbbbbbbbbb+----------------------+ 
   |                                          | 
   |bbbbbbbbbbbbbbbbbb                        | 
   |                                          | 
   |                                          | 
   |                                          | 
   |                  +-----------------------+ 
   |                  | 
   |                  | 
   |                  | 
   |------------------+ 
   | 
   | 
   +----------------------------------------------------------------------->

从图中可以看到,50% boost的情况下,boost后的信号都会提升当前signal与最大值之间差值的一半。100% boost的情况下,boost后的signal一直处于最高 。

schedutil的调频的计算方法:

         1.25 * cpuinfo.max_freq 
f = ———————————————————————————————— * boosted_util 
            cpu_max_capacity 
 
         1.25 * cpuinfo.max_freq 
  = ———————————————————————————————— * [util + (1024 - util) * boost_percent],//boost_percent是schedtune配置的boost值 
            cpu_max_capacity 
 
 
其中util计算公式如下: 
 
            prev_runnable_sum * (100 + sched_load_boost) 
util = ———————————————————————————————————————————————————————, //prev_runnable_sum是WALT统计的值,window_size默认20ms 
                    window_size >> 10 * 100

4. 使用boost的CPU util的OPP选择

我们看到boost的实现并没有使用新的负载signal,而是用一个api来调节现有的signal。调节是基于需求,并且仅在调度器的相关代码路径中。这个新的api根据sched_cfs_boost的值,决定了最终要么return原先的signal,要么return boost后的signal。这对现有代码修改非常简洁。

根据之前提到的SPC策略,signal代表了一个boost后的cpu util。对schedutil来说,这让一个cpu(比如CFS run queue)出现比原先更多的使用。

因此在sched_cfs_boost有效的情况下,我们使用下面的函数来获取当前cpu的util:

/* 
* cpu_util()返回CFS任务使用的CPU的容量。 返回值的单位必须是容量之一,因此我们可以将利用率与CFS任务可用 
的CPU容量(即cpu_capacity)进行比较。 
 
* cfs_rq.avg.util_avg是可运行任务的运行时间加上CPU上当前不可运行任务的最新利用率的总和。它表示[0..capacity_orig] 
范围内的CPU利用率,其中Capacity_orig是最高频率下可用的cpu_capacity(arch_scale_freq_capacity() I: 最大的容量是1024, 
和利用率相比的时候容量也使用的是百分比)。 
 
* CPU的利用率收敛到等于或小于CPU当前容量(capacity_curr <= Capacity_orig)的总和,因为它是该CPU上的运行时间, 
由Capacity_curr缩放。 
 
*但是,由于cfs.avg.util_avg中不幸的圆整舍入,或者恰好在迁移任务和新任务唤醒之后,直到平均值随着新的运行时间稳定下 
来之后,cfs_rq.avg.util_avg可能高于Capacity_curr,甚至高于Capacity_orig。 我们需要检查利用率是否保持在[0..capacity_orig] 
范围内,并在必要时对其进行限制。 如果没有使用率上限,则一个组可能会被视为过载(CPU0利用率为121%+ CPU1利用率为80%), 
而CPU1具有20%的可用容量。 我们允许利用率超调Capacity_curr(而不是capacity_orig),因为它可用于预测任务迁移(计划程序驱动的DVFS)之后所需的容量。 
*/ 
cpu_util() //kernel/sched/sched.h 
boosted_cpu_util() //kernel/sched/fair.c

其中boosted_cpu_util() 会return被sched_cfs_boost作用之后的cpu util。这个函数被CFS调度器的代码中用于决定cpu OPP。例如,将boost设为100%,cpu就是泡在最高的OPP。

5.Per task group boosting

在使用电池供电的设备上,会有很多后台service一直处于running状态,此时它们就需要节省功耗优先的调度策略。同时,一些交互性的app则对性能更敏感,需要放弃功耗,选择最高的性能。

为了能更好的应对上述情况,schedtune扩展了一个更广泛的boosting接口。它能让不同task group配置和使用不同的boosting值(注:上面的bost是per-CPU的,这个是per-group_task的)。 如果task需要特殊的性能要求,那么就把它分到另一个cgroup中。boost通过以下接口来设置:

schedtune.boost

这个boost值对应了该group中所有task会进行SPC boosting。

目前的schedtune控制有如下主要特性:

1)层级结构中只能创建一层

根节点定义了系统级的boost,默认应用到所有task。下一级子groups命名为“boost groups”,它们为特定的task定义boost值。再深一层的子groups是不允许创建的,因为对user-space来说没有意义。

2)定义的子group数量有限制

这个数量限制是在编译时确定的,默认是16。需要数量限制的原因有如下2点:

a)在真实系统中,我们不希望有太多特殊的场景。可能我们只要有以下几种就可以了:"background","interactive","performance"。

b)尽可能简化功能。特别是当有不同的boost配置的RUNNABLE tasks时,计算每个cpu boosting。

如此简单的设计就能在大多主要场景下,利用简洁的接口来管理所有或者部分task的功耗-性能。并且,这个接口可以简单地集成到user-space中,用于对不同类型的task,进行快速的task boosting。

6. 设置和使用

0. kernel config配置中:

CONFIG_SCHED_TUNE=y

1. 检查schedtune cgroup控制器是否available:

htc_imedugl:/ # cat /proc/cgroups                                               
#subsys_name    hierarchy    num_cgroups    enabled 
cpuset          4        12          1 
cpu            3        1          1 
cpuacct         1        232          1 
schedtune        2         5           1 //this 
freezer         0         1          1

2. 挂载cgroup文件系统(可选)

root@linaro-nano:~# sudo mount -t tmpfs cgroups /sys/fs/cgroup

3. 挂载schedtune文件系统:

root@linaro-nano:~# mkdir /sys/fs/cgroup/stune 
root@linaro-nano:~# sudo mount -t cgroup -o schedtune stune /sys/fs/cgroup/

android平台下,目录路径如下:

htc_imedugl:/ # ls /dev/stune 
background            notify_on_release  schedtune.prefer_idle          
cgroup.clone_children release_agent      schedtune.sched_boost_enabled  
cgroup.procs          rt                 schedtune.sched_boost_no_override  
cgroup.sane_behavior  schedtune.boost    tasks                          
foreground            schedtune.colocate top-app  

4. 创建task groups并配置特定的boost(可选)

举例将boost设置100%:

root@linaro-nano:~# mkdir /sys/fs/cgroup/stune/performance 
root@linaro-nano:~# echo 100 > /sys/fs/cgroup/stune/performance/schedtune.boost

android平台下,无法创建新的group。创建报错:

/dev/stune # mkdir perf  
mkdir: 'perf': No space left on device

5. 将task移到boost group

root@linaro-nano:~# echo TASKPID > /sys/fs/cgroup/stune/performance/cgroup.procs

android平台下:

echo TASKPID > /dev/stune/GROUPNAME/tasks

6. Per-task & wakeup任务分配策略

许多设备同时会有多个CFS tasks需要最小的wakeup延迟,而也有很多task不关注wakeup延迟。对于触摸控制的环境,缩短多余的wakeup延迟是非常重要的。

当你使用schedtune时,你可以改另一个参数,它可以让一个group标记为是否energy_aware placement bypass:

prefer_idle=0 (default - use energy-aware task placement if available) 即功耗优先 
prefer_idle=1 (never use energy-aware task placement for these tasks) 即性能优先

因为在CFS中,为了性能(缩短wakeup延迟), 一般的wakeup task placement逻辑对性能优先有一定倾向性。这个属性就可以对wakeup延迟有要求的task有效果的同时,也仍然允许energy-aware wakeup placement来迎合其他task,来节省功耗。

7. Q&A

1.如何管理具有不同提升值(boost)的多组任务?

当前的SchedTune实现跟踪在CPU上boost的RUNNABLE任务。 一旦sched-DVFS选择运行CPU的OPP,将以其runqueue中当前RUNNABLE任务的boost值的最大值来提升CPU利用率。这使sched-DVFS仅在有boost任务准备运行时才boost CPU,并在最后一个boost任务出队后立即切换回节能模式。

参考:

https://www.cnblogs.com/lingjiajun/p/12561177.html


本文参考链接:https://www.cnblogs.com/hellokitty2/p/13875073.html