CPU调频器
框架层:提供框架支持以及调频策略;
驱动层:提供驱动能力支持;
CPU调频器:Governor其实我们平时所说的DCVS,当然DVFS包含了硬件的调频能力与软件的算法。此处集中在软件算法部分。核心层提供了API cpufreq_register_governor来进行注册。通过查看scaling_available_governor可以看到所有已经注册的调频器,通过设置scaling_governor可以进行各种governor之间的切换。
图示:DCVS与governor
框架层通过cpufreq_policy设置了上层约束,governor在这个约束的范围内通过采集CPU的使用情况来决定是否需要调频,调到多少的频率合适。
图示:governor的输入与输出
图示:CPU使用率
下面介绍几种常用的governor:
interactive
基于linux模块化的设计,早期的governor基本都是对CPU的使用率进行定时采样检查,然后根据使用率的情况调整CPU频率。Interactive governor作为早期android的调频器,也属于此类。它除了周期性采样CPU的使用率之外,还加入了一些优化项,来满足交互式终端的性能体验需求。
图示:interactive governor工作原理(tips:不要把任务都放在同一个进程里面,尽量分散到多个线程上)
Interactive governor针对android这类交互终端做了一些改进如下:
1) 引入hi_speed的概念,当CPU使用率超过一定阈值(go_hispeed_load)的时候,会直接跳到预先设定的hi_speed的CPU频率,来满足交互的性能。避免governor周期采样调频的时间延迟。
2) 引入target_loads的概念,一般governor都默认采用80%作为调频的阈值,即CPU使用率超过80%,向上提高频率;低于80%,向下降低频率。但是具体的CPU的个体上存在差异,比如高频率部分的能效比较差,低频段的能效比较好。可以通过target_loads来设定不同频率区间的调频阈值。
3) 引入above_hispeed_delay,在hispeed freq以上的部分,CPU频率需要在当前频率上驻留一定的时间才可以继续向上调频,用于功耗优化。
4) boost/boostpulse_duration: boost模式,上层应用程序按需触发boost模式。
5) 关于输入参数cpu 使用率,最初的interactive governor参数为CPU运行时间跟处于IDLE的时间。也就是说usage使用率处于0%~100%之间。设想一个大任务或类while(1)循环的场景,其负载始终处于100%。以target load处于80%为例,每次只能向上增加25%的频率。以这样的速度调频的话,什么时候才能调到最高频率呢?所以后来将interactive的CPU使用率参数从cputime切换成scheduler调度器统计的CPU使用负载(高通在walt的时候引入)。
schedutil
之前我们介绍过:CPU调速器schedutil原理分析
上面讲interactive的部分分析了其优缺点,虽然可以采用scheduler统计的负载来代替cputime的使用率,但是interactive仍然存在一个问题,即采样周期。由于interactive是周期性采样调频的,其结果必然受到了采样周期的影响。采样周期短的话,其响应更加及时,但是系统开销大;采样周期长的话,响应比较缓慢,但是系统开销比较小。随着屏幕的刷新率从60HZ提到了90HZ, 120HZ。每一帧绘制的时间越来越短,对于CPU频率的响应延迟要求越来越高。
基于上述的一些原因,引入了schedutil调频器。虽然其名字叫schedutil,即来自于scheduler的utilization,但是这一点在schedutil出现之前高通已经在interactive上引入了。个人认为schedutil最大的改动在于将周期性的采样触发改成了基于事件驱动触发(反模块化)。这些事件来自于scheduler。其工作原理大体如下:
1) 当一个任务被唤醒时,任务被放入到了某个CPU的队列中。此时意味着当前CPU的任务负载变重,需要及时的提高频率来保证性能。于是主动触发了调频。
2) 当一个任务完成工作,进入休眠状态,从任务队列中被移除。此时意味着当前CPU的任务负载变轻,需要及时的降低频率来降低功耗。于是主动触发了调频。
上面是2个典型的场景,诸如此类。当CPU上任务负载发生变化的时候,即由scheduler触发频率的调整。
cpufreq_schedutil.c中注册了hook函数
scheduler在某些事件发生的时候主动调用cpufreq_update_util来进行频率的调整。其实已经在调度器跟调频器之间搭了座桥梁。
cpufreq governor的实现
struct cpufreq_governor
kernel通过struct cpufreq_governor抽象cpufreq governor,如下:
/* include/linux/cpufreq.h */
struct cpufreq_governor {
char name[CPUFREQ_NAME_LEN];
int initialized;
int (*governor) (struct cpufreq_policy *policy,
unsigned int event);
ssize_t (*show_setspeed) (struct cpufreq_policy *policy,
char *buf);
int (*store_setspeed) (struct cpufreq_policy *policy,
unsigned int freq);
unsigned int max_transition_latency; /* HW must be able to switch to
next freq faster than this value in nano secs or we
will fallback to performance governor */
struct list_head governor_list;
struct module *owner;
};
name,该governor的名称,唯一标识某个governor。
initialized,记录governor是否已经初始化okay。
max_transition_latency,容许的最大频率切换延迟,硬件频率的切换必须小于这个值,才能满足需求。
governor_list,用于将该governor挂到一个全局的governor链表(cpufreq_governor_list)上。
show_setspeed和store_setspeed,有些governor支持从用户空间修改频率值,此时该governor必须提供show_setspeed和store_setspeed两个回调函数,用于响应用户空间的scaling_setspeed请求。
governor,cpufreq governor的主要功能都是通过该回调函数实现,该函数借助不同的event,以状态机的形式,实现governor的启动、停止等操作。
governor event
kernel将governor的控制方式抽象为下面的5个event,cpufreq core在合适的时机,以event的形式(.governor回调),控制governor完成相应的调频动作。
/* include/linux/cpufreq.h */
/* Governor Events */
#define CPUFREQ_GOV_START 1
#define CPUFREQ_GOV_STOP 2
#define CPUFREQ_GOV_LIMITS 3
#define CPUFREQ_GOV_POLICY_INIT 4
#define CPUFREQ_GOV_POLICY_EXIT 5
CPUFREQ_GOV_POLICY_INIT,policy启动新的governor之前(通常在cpufreq policy刚创建或者governor改变时)发送。governor接收到这个event之后,会进行前期的准备工作,例如初始化一些必要的数据结构(如timer)等。并不是所有governor都需要这个event,后面如果有时间,我们以ondemand governor为例,再介绍它的意义。
CPUFREQ_GOV_START启动governor。
CPUFREQ_GOV_STOP、CPUFREQ_GOV_POLICY_EXIT,和前面两个event的意义相反。
CPUFREQ_GOV_LIMITS,通常在governor启动后发送,要求governor检查并修改频率值,使其在policy规定的有效范围内。
governor register
所有governor都是通过cpufreq_register_governor注册到kernel中的,该接口比较简单,查找是否有相同名称的governor已经注册,如果没有,将这个governor挂到全局的链表即可,如下:
int cpufreq_register_governor(struct cpufreq_governor *governor)
{
int err;
if (!governor)
return -EINVAL;
if (cpufreq_disabled())
return -ENODEV;
mutex_lock(&cpufreq_governor_mutex);
governor->initialized = 0;
err = -EBUSY;
if (__find_governor(governor->name) == NULL) {
err = 0;
list_add(&governor->governor_list, &cpufreq_governor_list);
}
mutex_unlock(&cpufreq_governor_mutex);
return err;
}
EXPORT_SYMBOL_GPL(cpufreq_register_governor);
学习wiki:
linux cpufreq framework(4)_cpufreq governor
深入浅出CPUFreq