分类: LINUX
2013-01-17 09:21:09
1. 基本介绍
1) 调整CPU运行频率是一个节能的好方法,CPU运行频率越低,CPU功耗越小。
2) 下面的我现在正在使用的CPU为例进行说明。触发CPU频率调整的有两个源:
1)根据CPU负荷进行调整(代码位于:kernel/drivers/cpufreq,下面以cpufreq_interactive.c为例,当/sys/drivers/system/cpu/cpu0/cpufreq/scaling_governor中的值为interactive时生效)
2)根据CPU的温度时行调整(需要CPU内有温度传感器,且在cpufreq_driver中实现,它由各个芯片厂家实现)
2. 根据CPU负荷进行调整的调用流程
cpufreq_interactive_up_task(或cpufreq_interactive_freq_down
或cpufreq_governor_interactive :cpufreq_interactive.c)->
__cpufreq_driver_target->
cpufreq_driver->target(mycpu_cpufreq_driver.target=mycpu_target)->
mycpu_target->
clk_set_rate->
dvfs_set_rate->
dvfs_target_cpu (或dvfs_target_core)->
dvfs_clk_get_ref_volt(获得与频率对应的参考电压)
dvfs_scale_volt_direct(设置电压)
clk_set_rate_locked->clk_set_rate_nolock->clk->set_rate(设置频率)
3. cpufreq_interactive工作流程
1) 驱动入口:cpufreq_interactive_init
a) 注册CPU timer函数:cpufreq_interactive_timer
b) 创建线程:kinteractiveup,入口函数:cpufreq_interactive_up_task
c) 创建workqueue “knteractive_down”,其工作处理函数为:cpufreq_interactive_freq_down
d) 注册idle notify函数cpufreq_interactive_idle_nb
e) 注册cpufreq_governor cpufreq_gov_interactive,其中的governor函数为:cpufreq_governor_interactive
2) cpufreq_interactive_timer
这个函数通过timer触发,它执行cpu负荷计算。timer的默认间隔为20ms。根据CPU负荷计算CPU的频率的代码如下:
a) 如果新的频率大于当前工作频率,则唤醒进程kinteractiveup进行处理,此进程调用__cpufreq_driver_target设置新的工作频率。
b) 如果新的频率小于当前工作频率,则把一个工作放于workqueue “knteractive_down”,它调用__cpufreq_driver_target设置新的工作频率。
4. 根据温度调节CPU频率
把温度检测和频率调整工作放于一个work中, 然后加入一个workqueue,delay 2ms再执行。其调用流程如下:
温度检测及频率计算函数->
cpufreq_driver_target->
__cpufreq_driver_target->
...与第2.中的一样。
/*
* Once pcpu->timer_run_time is updated to >= pcpu->idle_exit_time,
* this lets idle exit know the current idle time sample has
* been processed, and idle exit can generate a new sample and
* re-arm the timer. This prevents a concurrent idle
* exit on that CPU from writing a new set of info at the same time
* the timer function runs (the timer function can't use that info
* until more time passes).
*/
time_in_idle = pcpu->time_in_idle;
idle_exit_time = pcpu->idle_exit_time;
now_idle = get_cpu_idle_time_us(data, &pcpu->timer_run_time);
smp_wmb();
/* If we raced with cancelling a timer, skip. */
if (!idle_exit_time)
goto exit;
delta_idle = (unsigned int) cputime64_sub(now_idle, time_in_idle);
delta_time = (unsigned int) cputime64_sub(pcpu->timer_run_time,
idle_exit_time);
/*
* If timer ran less than 1ms after short-term sample started, retry.
*/
if (delta_time < 1000)
goto rearm;
if (delta_idle > delta_time)
cpu_load = 0;
else
cpu_load = 100 * (delta_time - delta_idle) / delta_time;
delta_idle = (unsigned int) cputime64_sub(now_idle,
pcpu->freq_change_time_in_idle);
delta_time = (unsigned int) cputime64_sub(pcpu->timer_run_time,
pcpu->freq_change_time);
if ((delta_time == 0) || (delta_idle > delta_time))
load_since_change = 0;
else
load_since_change =
100 * (delta_time - delta_idle) / delta_time;
/*
* Choose greater of short-term load (since last idle timer
* started or timer function re-armed itself) or long-term load
* (since last frequency change).
*/
if (load_since_change > cpu_load)
cpu_load = load_since_change;
if (cpu_load >= go_hispeed_load) {
if (pcpu->policy->cur == pcpu->policy->min)
new_freq = hispeed_freq;
else
new_freq = pcpu->policy->max * cpu_load / 100;
} else {
new_freq = pcpu->policy->cur * cpu_load / 100;
}
if (cpufreq_frequency_table_target(pcpu->policy, pcpu->freq_table,
new_freq, CPUFREQ_RELATION_H,
&index)) {
pr_warn_once("timer %d: cpufreq_frequency_table_target error\n",
(int) data);
goto rearm;
}
new_freq = pcpu->freq_table[index].frequency;