interactive governor study for android

来源:互联网 发布:淘宝网十二伏鹰眼灯 编辑:程序博客网 时间:2024/05/03 07:23

interactive governor全部思考思路:

1Interactive governor 初始化,一些定时器绑定的函数,创建内核线程进行调频并注册interative governor(对应cpufreq_interactive_initfunction)。


2、注册governor执行函数:cpufreq_governor_interactive这个函数通过三个不同的event来处理不同的情况,分别如下:

  1. CPUFREQ_GOV_START:用来对每个CPU对应的Governor info进行初始化操作并创建相应的governorsysfs interface,并注册idle和频率改变通知链。由于interactive governor是通过定时器来实时的统计cpuload,所以这个governor一启动的时候就必须设定相关的定时器。通过函数cpufreq_interactive_timer_start实现的

  2. CPUFREQ_GOV_STOP:用来暂停此governor的工作,那么就必须将启动这个governor设定的一些信息就必须清除了,比如创建的通知链、定时器、sysfsinterface等等。

  3. CPUFREQ_GOV_LIMITS:这里是当用户修改CPU的最小最大频率的时候会调用此处,目的是用户修改之后,governor要知道这些并将其写入到相应的CPU相关的结构体中。即对结构体cpufreq_policy中的最大最小频率进行修改,其实在linux系统中或者android手机中通过console执行”echox >/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq“,就可以修改最小频率。

  4. 通过kernel给出的文档我们可以更加容易的理解这三个event的用意:

    CPUFREQ_GOV_START: This governor shall start its duty for the CPU policy->cpu

    CPUFREQ_GOV_STOP: This governor shall end its duty for the CPU policy->cpu

    CPUFREQ_GOV_LIMITS: The limits for CPU policy->cpu have changed to policy->minand policy->max.

3、既然启动了interactive governor,那么它是如何工作的呢?

首先启动codecpufreq_interactive_timer_start,在初始化的时候,每一个CPU coretimer都是独立工作的。Code is as follow:

staticvoid cpufreq_interactive_timer_start(int cpu)

{

structcpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, cpu);

unsignedlong expires = jiffies + usecs_to_jiffies(timer_rate);

unsignedlong flags;


pcpu->cpu_timer.expires= expires;//CPU core 统计load的定时器

add_timer_on(&pcpu->cpu_timer,cpu);//加入到定时器链表中去

if(timer_slack_val >= 0 && pcpu->target_freq >pcpu->policy->min) {

expires+= usecs_to_jiffies(timer_slack_val);

pcpu->cpu_slack_timer.expires= expires;

add_timer_on(&pcpu->cpu_slack_timer,cpu);//not consider

}


spin_lock_irqsave(&pcpu->load_lock,flags);

//计算CPU启动到现在的idle时间

pcpu->time_in_idle=

get_cpu_idle_time(cpu,&pcpu->time_in_idle_timestamp);

pcpu->cputime_speedadj= 0;

//计算启动启动到现在的时间

pcpu->cputime_speedadj_timestamp= pcpu->time_in_idle_timestamp;

//上面的两个参数是用来计算cpuload使用的,即cpuidle时间是多少,忙的时间是多少。

spin_unlock_irqrestore(&pcpu->load_lock,flags);

}


定时器设置之后,一旦定时器到期的话就会调用定时器绑定的函数,并执行这个函数,这个函数是:

staticvoid cpufreq_interactive_timer(unsigned long data)。这个函数是interactivegovernor的关键,只要把这个函数弄懂了基本上就搞明白了这个governor是怎么工作的了。下面就来分析此函数。看它是怎样jisuacpuload的,通过如下代码实现:


now= update_load(data);//计算CPU运行到现在的总时间

//现在的总时间减去上次的总时间就是在一个统计周期里CPU运行的时间。

delta_time= (unsigned int)(now – pcpu->cputime_speedadj_timestamp);

/*下面的这个值,我们可以通过update_load这个函数来看它是怎么计算的。是 active_time乘以当前频率的valuepcpu->cputime_speedadj+= active_time * pcpu->policy->cur;*/

cputime_speedadj= pcpu->cputime_speedadj;

spin_unlock_irqrestore(&pcpu->load_lock,flags);


if(WARN_ON_ONCE(!delta_time))//detect whether delta_time is zero?

gotorearm;

/*活动时间除以总的运行时间在乘以当前频率,值存储在cputime_speedadj*/

do_div(cputime_speedadj,delta_time);

/*上面计算的结果存储在cputime_speedadj中,是一个百分比*当前频率,下面乘以 100的目的就是计算cpu_load的时候是一个大于1的整数。*/


loadadjfreq= (unsigned int)cputime_speedadj * 100;

/*得到cpu_load的值,至于为什么这样做,是值得思考的。我的理解就是既考虑了时间又考虑了当前频率,因为在调整频率的时候不能只看单纯的load信息,即cpu的忙闲的比例。*/

cpu_load= loadadjfreq / pcpu->target_freq;

上面的分析我们已经知道了CPUload的值是怎样计算的。接下来如何使用CPUload的值来调节CPU的频率呢?

看如下的code

boosted= boost_val || now < boostpulse_endtime;//用在突发任务的时候

if(cpu_load >= go_hispeed_load || boosted) {

if(pcpu->target_freq < hispeed_freq) {

new_freq= hispeed_freq;

}else {

new_freq= choose_freq(pcpu, loadadjfreq);


if(new_freq < hispeed_freq)

new_freq= hispeed_freq;

}

}else {

new_freq= choose_freq(pcpu, loadadjfreq);

}

go_hispeed_loaddefault value is 99.我们可以很清楚的看到这些语句是如何实现计算new_freq的值的。这些语句比较好理解,如果CPUload>=99的话,那么无论如何都要将CPU的频率提高到最高频率。否则按照普通方式进行处理。


这个函数choose_freq是用来计算new_freq的,那么它又是如何实现的呢?简单的说下就是系统设置了一个target_load,目的是当前设置当前的频率是CPUcoreload值降低到此target_load值之下,如果这个值越小,系统就会越频繁的升高CPU的频率使loadvalue<target_load。它的整个代码就是这个意思,对于target_load的含义我们看kerneldocument有如下解释:

target_loads:CPU load values used to adjust speed to influence thecurrentCPU load toward that value. In general, the lower the targetload,the more often the governor will raise CPU speeds to bring loadbelowthe target. The format is a single target load, optionallyfollowedby pairs of CPU speeds and CPU loads to target at or abovethosespeeds. Colons can be used between the speeds and associated

targetloads for readability. For example:

851000000:90 1700000:99

targetsCPU load 85% below speed 1GHz, 90% at or above 1GHz, until1.7GHzand above, at which load 99% is targeted. If speeds arespecifiedthese must appear in ascending order. Higher target load

valuesare typically specified for higher speeds, that is, target loadvaluesalso usually appear in an ascending order. The default istargetload 90% for all speeds.好好思考这些东西对理解一些参数还是很有帮助的。

找到了new_freq的值,还没有完,code继续告诉我们,究竟在什么时候才可以进行调节频率而不影响系统的实时性能,尤其对interactivegovernor是一个升频快速的governor,能够及时的响应系统的负载信息,及时的调节CPU频率来满足用户体验,当然这是以耗电为代价的。

不说废话,接着分析,知道我们看到调节频率的code为止:

if(pcpu->target_freq >= hispeed_freq &&

new_freq > pcpu->target_freq &&

now - pcpu->hispeed_validate_time <

freq_to_above_hispeed_delay(pcpu->target_freq)) {

trace_cpufreq_interactive_notyet(data,cpu_load, pcpu->target_freq,pcpu->policy->cur,new_freq);

goto rearm;

}


pcpu->hispeed_validate_time= now;


if(cpufreq_frequency_table_target(pcpu->policy, pcpu->freq_table,new_freq, CPUFREQ_RELATION_L,

&index))

goto rearm;


new_freq= pcpu->freq_table[index].frequency;

我们来分析第一个if语句。如果满足了第一个条件就是当前的目标频率是最高频率了,新计算出来的new_freq比最高频率还高的话,继续看now- pcpu->hispeed_validate_time <freq_to_above_hispeed_delay(pcpu->target_freq)这条语句,比较难以费解。我的理解是这样的:CPU到现在的运行时间减去CPU上次呆在hispeed的时间点上,如果这个value小于设定的值,即函数freq_to_above_hispeed_delay(pcpu->target_freq)。即如果系统的频率处在较高的频率点上的时间小于这个值的话,那么就没有必要调节频率了,因为此时的CPU的频率值就符合要求的。所以直接”gotorearm“了。

如果这条语句不符合,则更新pcpu->hispeed_validate_time,设置为CPU到现在的运行时间。

接下来分析第二条if语句。在freq_table中查找>=new_freq的频率点。有的话,直接设置new_freqfreq_table中的值。这样我们的new_freq就找出来了,至于是否就是本次CPU的调节的频率接着往下看。

下面这些code还是比较费解的。

/*

* Do not scale below floor_freq unless we have been at or above the

* floor frequency for the minimum sample time since last validated.

*/

if(new_freq < pcpu->floor_freq) {

if(now - pcpu->floor_validate_time < min_sample_time) {

trace_cpufreq_interactive_notyet(

data,cpu_load, pcpu->target_freq,

pcpu->policy->cur,new_freq);

goto rearm;

}

}

我的理解就是当new_freq小于pcpu->floor_freq(基准频率,暂且这样说吧)的时候,那么就没有必要急于频率的调整了,如果此时的CPU运行时间减去pcpu->floor_validate_time还小于最小抽样间隔,那么就真的不需要调整频率了,为和选用最小抽样时间间隔来做比较呢,而这整体现了google工程师的高明之处,在最小抽样间隔期间内,CPU的频率是不会改变的,这就说明了这点。

接着看下面的code

它的注释很好的解释了一切。不再多讲。

/*

* Update the timestamp for checking whether speed has been held at

* or above the selected frequency for a minimum of min_sample_time,

* if not boosted to hispeed_freq. If boosted to hispeed_freqthen we

* allow the speed to drop as soon as the boostpulse duration expires

* (or the indefinite boost is turned off).

*/

if(!boosted || new_freq > hispeed_freq) {

pcpu->floor_freq= new_freq;

pcpu->floor_validate_time= now;

}

接着下面的code

if(pcpu->target_freq == new_freq) {

trace_cpufreq_interactive_already(

data,cpu_load, pcpu->target_freq,

pcpu->policy->cur,new_freq);

goto  rearm_if_notmax;

}

如果计算的new_freq与目标频率是一样的,跳转到”rearm_if_notmax“处,在那里判断是否是最高频率,如果是最高频率的话,就结束本次频率的调整,并关闭cputime定时器,CPU频率会一直呆在最高频率上(不管其他CPUcoreload是轻还重),等待下次此CPUcore idle的时候重新设置定时器。idle的进出我们一开始就分析了,使用了idle通知链机制:

idle_notifier_register(&cpufreq_interactive_idle_nb);//注册

//通知链处理函数

staticint cpufreq_interactive_idle_notifier(struct notifier_block *nb,

unsigned long val,

void *data)

{

switch(val) {

case IDLE_START:

cpufreq_interactive_idle_start();

break;

case IDLE_END:

cpufreq_interactive_idle_end();

break;

}


return0;

}


staticstruct notifier_block cpufreq_interactive_idle_nb = {

.notifier_call= cpufreq_interactive_idle_notifier,//对应的通知链处理函数

};

接下来,一切都比较顺利的情况,interactivegovernor是怎样实现频率调节的:

相关代码如下:

pcpu->target_freq= new_freq;//设置新的目标频率

spin_lock_irqsave(&speedchange_cpumask_lock,flags);

/*设置需要调节频率的CPUcorecpumask*/

cpumask_set_cpu(data,&speedchange_cpumask);

spin_unlock_irqrestore(&speedchange_cpumask_lock,flags);

/*唤醒内核线程来执行频率的改变*/

wake_up_process(speedchange_task);


rearm_if_notmax:

/*

* Already set max speed and don't see a need to change that,

* wait until next idle to re-evaluate, don't need timer.非常重要

*/

if(pcpu->target_freq == pcpu->policy->max) {

goto exit;

}

rearm:

/*

*判断cpu_timer是否为空,为空的话就重新设置相关的timer

*将其加入到定时器链表中。

*/

if(!timer_pending(&pcpu->cpu_timer))

cpufreq_interactive_timer_resched(pcpu);


exit:

/*放弃使能信号量*/

up_read(&pcpu->enable_sem);

return;

到此为止,cpufreq_interactive_timer函数就这样结束了,比较复杂,这些搞懂了,其他之类的函数就是为它服务的。


下面进入调节频率的函数了,也就是创建的内核线程绑定的函数cpufreq_interactive_speedchange_task,分析如下:

staticint cpufreq_interactive_speedchange_task(void *data)

{

unsignedint cpu;

cpumask_ttmp_mask;

unsignedlong flags;

structcpufreq_interactive_cpuinfo *pcpu;


/*这个循环不断的在执行*/

while(1) {

set_current_state(TASK_INTERRUPTIBLE);

spin_lock_irqsave(&speedchange_cpumask_lock,flags);


if(cpumask_empty(&speedchange_cpumask)) {

spin_unlock_irqrestore(&speedchange_cpumask_lock,flags);

schedule();//如果没有哪个CPUcore的频率需要调整,就去执行其他事情


if(kthread_should_stop())

break;


spin_lock_irqsave(&speedchange_cpumask_lock,flags);

}

/*将线程设置为可运行状态*/

set_current_state(TASK_RUNNING);

tmp_mask= speedchange_cpumask;//临时保存

/*记得每次都要清除,因为这个值可能时刻在改变着*/

cpumask_clear(&speedchange_cpumask);

spin_unlock_irqrestore(&speedchange_cpumask_lock,flags);

/*这里开始真正的频率调节了*/

for_each_cpu(cpu,&tmp_mask) {

unsignedint j;

unsignedint max_freq = 0;


pcpu= &per_cpu(cpuinfo, cpu);

if(!down_read_trylock(&pcpu->enable_sem))

continue;

if(!pcpu->governor_enabled) {

up_read(&pcpu->enable_sem);

continue;

}


for_each_cpu(j,pcpu->policy->cpus) {

structcpufreq_interactive_cpuinfo *pjcpu =&per_cpu(cpuinfo,j);

/*找出所有CPUcore的最大频率*/

if(pjcpu->target_freq > max_freq)

max_freq= pjcpu->target_freq;

}

/*调节频率,执行的函数是一个回调函数(callbackfunction),应该是DVFSdriver执行具体动作的*/

if(max_freq != pcpu->policy->cur)

__cpufreq_driver_target(pcpu->policy,max_freq,CPUFREQ_RELATION_H);

trace_cpufreq_interactive_setspeed(cpu,pcpu->target_freq,pcpu->policy->cur);


up_read(&pcpu->enable_sem);

}

}


return 0;

}

到此调节频率完成了。就这样周而复始的执行上述code

我们必须知道一种情况就是,如果某个CPUcore不处在idlestatus,并且此时的CPUcorefrequency==max_freq的话,统计CPUcore load的函数(staticvoid cpufreq_interactive_timer(unsigned long data))不会执行,直到下次idle的到来。

如果对于有些参数不是很理解的话,可以查看kernel目录下:document/cpufreq/governor.txt,里面对多种governor的参数进行了详细的说明。


还有一些细节自己还是没有弄明白,希望有疑问的朋友,或者说的有错误的地方,望各位看官不吝赐教,谢谢!

1 0