choose_freq函数用来选频,使选频后的系统workload小于或等于target load.
核心思想是:选择最小的频率来满足target load.
影响选频结果的因素有两个:
1.两次统计时间内系统频率的平均频率loadadjfreq,
2.系统设定好的target load,在INIT的时候设定,tunables->target_loads = default_target_loads;
drivers/cpufreq/cpufreq_interactive.c
/*
* If increasing frequencies never map to a lower target load then
* choose_freq() will find the minimum frequency that does not exceed its
* target load given the current load.
*/
static unsigned int choose_freq(struct cpufreq_interactive_cpuinfo *pcpu,
unsigned int loadadjfreq)
{
unsigned int freq = pcpu->policy->cur;
unsigned int prevfreq, freqmin, freqmax;
unsigned int tl;
int index;
freqmin = 0;
freqmax = UINT_MAX;
do {
prevfreq = freq;
tl = freq_to_targetload(pcpu->policy->governor_data, freq);
/*
* Find the lowest frequency where the computed load is less
* than or equal to the target load.
*/
if (cpufreq_frequency_table_target(
pcpu->policy, pcpu->freq_table, loadadjfreq / tl,
CPUFREQ_RELATION_L, &index))
break;
freq = pcpu->freq_table[index].frequency;
if (freq > prevfreq) {
/* The previous frequency is too low. */
freqmin = prevfreq;
if (freq >= freqmax) {
/*
* Find the highest frequency that is less
* than freqmax.
*/
if (cpufreq_frequency_table_target(
pcpu->policy, pcpu->freq_table,
freqmax - 1, CPUFREQ_RELATION_H,
&index))
break;
freq = pcpu->freq_table[index].frequency;
if (freq == freqmin) {
/*
* The first frequency below freqmax
* has already been found to be too
* low. freqmax is the lowest speed
* we found that is fast enough.
*/
freq = freqmax;
break;
}
}
} else if (freq < prevfreq) {
/* The previous frequency is high enough. */
freqmax = prevfreq;
if (freq <= freqmin) {
/*
* Find the lowest frequency that is higher
* than freqmin.
*/
if (cpufreq_frequency_table_target(
pcpu->policy, pcpu->freq_table,
freqmin + 1, CPUFREQ_RELATION_L,
&index))
break;
freq = pcpu->freq_table[index].frequency;
/*
* If freqmax is the first frequency above
* freqmin then we have already found that
* this speed is fast enough.
*/
if (freq == freqmax)
break;
}
}
/* If same frequency chosen as previous then done. */
} while (freq != prevfreq);
return freq;
}
freq的初始数值是pcpu->policy->cur
prevfreq保存着上次的freq,freq表示本次选频的结果,当二者相等时,就表示已经达到了最佳的选频结果.
顺便说一下:
#define CPUFREQ_RELATION_L 0 /* lowest frequency at or above target */
#define CPUFREQ_RELATION_H 1 /* highest frequency below or at target */
CPUFREQ_RELATION_L,表示要取大于等于target的最小值
CPUFREQ_RELATION_H,表示要取小于等于target的最大值
之后是一个循环:
把上次的freq赋值给prevfreq
通过freq_to_targetload得到target load——tl
然后调用cpufreq_frequency_table_target,取大于等于loadadjfreq / tl,即target freq的最小值.
loadadjfreq是两次采样统计间的平均频率,除以target load就得到target freq.
freq = pcpu->freq_table[index].frequency;
我觉得情景应该是这样的,刚开始freq = pcpu->policy->cur,备份到prevfreq,调用cpufreq_frequency_table_target得到新的freq,然后执行下面的if判断,在这个判断中可能会调整freq,先不管这些.
到了下一次循环,上一次的freq又被备份到prevfreq,然后又调用cpufreq_frequency_table_target得到新的freq,如此往复循环,prevfreq和freq的数值会越来越接近,直到相等,就完成了选频.
总体思路是这样,那么来看if判断做了什么.
拿freq和prevfreq比较:
若freq > prevfreq,则
freqmin = prevfreq;
否则
freqmax = prevfreq;
如果freq > prevfreq,说明比上次大,但是不能比之前的记录最大值大,否则调节就没有意义了,所以
如果freq >= freqmax,那么调用cpufreq_frequency_table_target,找小于freqmax的最近一个频点,如果该频点正好是最小频点,说明只有freqmax可以用了,直接break;
如果freq < prevfreq,说明比上次小,但是不能比之前记录的最小值小,否则调节就没有意义了,所以
如果freq <= freqmax,那么调用cpufreq_frequency_table_target,找大于freqmax的最近一个频点,如果该频点正好是最大频点,直接break,;
最后返回选好的频点freq.
choose_freq中调用了cpufreq_frequency_table_target,
/drivers/cpufeq/freq_table.c
int cpufreq_frequency_table_target(struct cpufreq_policy *policy,
struct cpufreq_frequency_table *table,
unsigned int target_freq,
unsigned int relation,
unsigned int *index);
is the corresponding frequency table helper for the ->target
stage. Just pass the values to this function, and the unsigned int
index returns the number of the frequency table entry which contains
the frequency the CPU shall be set to.
注释中说的比较清楚了,最有效的理解方式就是带几个index个频点进去算一下~~
int cpufreq_frequency_table_target(struct cpufreq_policy *policy,
struct cpufreq_frequency_table *table,
unsigned int target_freq,
unsigned int relation,
unsigned int *index)
{
struct cpufreq_frequency_table optimal = {
.driver_data = ~0,
.frequency = 0,
};
struct cpufreq_frequency_table suboptimal = {
.driver_data = ~0,
.frequency = 0,
};
unsigned int i;
pr_debug("request for target %u kHz (relation: %u) for cpu %u\n",
target_freq, relation, policy->cpu);
switch (relation) {
case CPUFREQ_RELATION_H:
suboptimal.frequency = ~0;
break;
case CPUFREQ_RELATION_L:
optimal.frequency = ~0;
break;
}
for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
unsigned int freq = table[i].frequency;
if (freq == CPUFREQ_ENTRY_INVALID)
continue;
if ((freq < policy->min) || (freq > policy->max))
continue;
switch (relation) {
case CPUFREQ_RELATION_H:
if (freq <= target_freq) {
if (freq >= optimal.frequency) {
optimal.frequency = freq;
optimal.driver_data = i;
}
} else {
if (freq <= suboptimal.frequency) {
suboptimal.frequency = freq;
suboptimal.driver_data = i;
}
}
break;
case CPUFREQ_RELATION_L:
if (freq >= target_freq) {
if (freq <= optimal.frequency) {
optimal.frequency = freq;
optimal.driver_data = i;
}
} else {
if (freq >= suboptimal.frequency) {
suboptimal.frequency = freq;
suboptimal.driver_data = i;
}
}
break;
}
}
if (optimal.driver_data > i) {
if (suboptimal.driver_data > i)
return -EINVAL;
*index = suboptimal.driver_data;
} else
*index = optimal.driver_data;
pr_debug("target is %u (%u kHz, %u)\n", *index, table[*index].frequency,
table[*index].driver_data);
return 0;
}
EXPORT_SYMBOL_GPL(cpufreq_frequency_table_target);