CPUFreq子系統位於 drivers/cpufreq目錄下,負責進行運行過程中CPU頻率和電壓的動態調整,即DvFS( Dynamic Voltage Frequency Scaling,動態電壓頻率調整)。運行時進行CPU電壓和頻率調整的原因是:CMOS電路中的功耗與電壓的平方成正比、與頻率成 ...
CPUFreq子系統位於 drivers/cpufreq目錄下,負責進行運行過程中CPU頻率和電壓的動態調整,即DvFS( Dynamic Voltage Frequency Scaling,動態電壓頻率調整)。運行時進行CPU電壓和頻率調整的原因是:CMOS電路中的功耗與電壓的平方成正比、與頻率成正比(P∝fV2)因此降低電壓和頻率可降低功耗。
CPUFreq的核心層位於drivers/cpufreq/cpufreq,c下,它為各個SoC的CPUFreq驅動的實現提供了一套統一的介面,並實現了一套notifier機制,可以在 CPUFreq的策略和頻率改變的時候向其他模塊發出通知。另外,在CPU運行頻率發生變化的時候,內核的 loops perify常數也會發生相應變化。
SOC的CPUFreq驅動實現
每個SoC的具體CPUFreq驅動實例只需要實現電壓、頻率表,以及從硬體層面完成這些變化。
CPUFreq核心層提供瞭如下API以供SoC註冊自身的CPUFreq驅動:
int cpufreq_register_driver(struct cpufreq_driver *driver_data)
其參數為一個cpufreq_driver結構體指針,實際上,cpufreq_driver封裝了一個具體的SoC的CPUFreq驅動的主體,該結構體形如代碼如下所示。
struct cpufreq_driver {
char name[CPUFREQ_NAME_LEN];
u8 flags;
/* needed by all drivers */
int (*init) (struct cpufreq_policy *policy);
int (*verify) (struct cpufreq_policy *policy);
/* define one out of two */
int (*setpolicy) (struct cpufreq_policy *policy);
int (*target) (struct cpufreq_policy *policy, /* Deprecated */
unsigned int target_freq,
unsigned int relation);
int (*target_index) (struct cpufreq_policy *policy,
unsigned int index);
/* should be defined, if possible */
unsigned int (*get) (unsigned int cpu);
/* optional */
int (*bios_limit) (int cpu, unsigned int *limit);
int (*exit) (struct cpufreq_policy *policy);
int (*suspend) (struct cpufreq_policy *policy);
int (*resume) (struct cpufreq_policy *policy);
struct freq_attr **attr;
};
其中的 owner成員一般被設置為 THIS MODULE;name成員是CPUFreq驅動的名字,如drivers/cpufreq/s5pv210-cpufreq.c
設置name為s5pv210, drivers/cpufreq/omap-cpufreq.c設置name為omap;falgs是一些暗示性的標誌,譬如,若設置了 CPUFREQ_CONST_LOOPS,則是告訴內核loops_per_jiffy不會因為CPU頻率的變化而變化。
init()成員是一個per-CPU初始化函數指針,每當一個新的CPU被註冊進系統的時候,該函數就被調用,該函數接受一個cpufreq_policy的指針參數,在init()成員函數中,可進行如下設置:
policy->cpuinfo.min_freq
policy->cpuinfo.max_freq
上述代碼描述的是該CPU支持的最小頻率和最大頻率(單位是kHz)。
policy->cur
上述代碼描述的是CPU的當前頻率;
policy->policy
policy->governor
policy->min
policy->max
上述代碼定義該CPU的預設策略,以及在預設策略情況下,該策略支持的最小、最大CPU頻率。
verify成員函數用於對用戶的 CPUFreq策略設置進行有效性驗證和數據修正。每當用戶設定一個新策略時,該函數根據老的策略和新的策略,檢驗新策略設置的有效性並對無效設置進行必要的修正。在該成員函數的具體實現中,常用到如下輔助函數:
static inline void
cpufreq_verify_within_cpu_limits(struct cpufreq_policy *policy)
{
cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
policy->cpuinfo.max_freq);
}
setpolicyo成員函數接受一個policy參數(包含policy->policy
、policy->min
和policy->max
等成員),實現了這個成員函數的CPU一般具備在一個範圍(limit,從policy->min
到policy->max
)里自動調整頻率的能力。目前只有少數驅動(如intel_pstate.c和longrun.c)包含這樣的成員函數,而絕大多數CPU都不會實現此函數,一般只實現target()成員函數,target()的參數直接就是一個指定的頻率。
target()成員函數用於將頻率調整到一個指定的值,接受3個參數: policy、 target_freq和relation, target freq是目標頻率,實際驅動總是要設定真實的CPU頻率到最接近於 target_feq,並且設定的頻率必須位於 policy->min到 policy->max之間。在設定頻率接近 target_feq的情況下, relation若為 CPUFREQ REL I,則暗示設置的頻率應該大於或等於 target_freq; relation若為 CPUFREQ_REL_H,則暗示設置的頻率應該小於或等於 target_freq。
表19.1描述了 setpolicy()和 target()所針對的CPU以及調用方式上的區別。
setpolicy() | target() |
---|---|
CPU有在一定範圍內獨立調整頻率的能力 | CPU只能指定頻率 |
CPU freq policy 調用到setpolicy(),由CPU獨立在一個範圍內調整頻率 | 由CPU Freq核心層根據系統負載和策略綜合決定目標頻率 |
根據晶元內部PLL和分頻器的關係, ARM SOC一般不具備獨立調整頻率的能力,往往SoC的 CPUFreq驅動會提供一個頻率表,頻率在該表的範圍內進行變更,因此一般實現target()成員函數。
CPUFreq核心層提供了一組與頻率表相關的輔助API。
int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy,
struct cpufreq_frequency_table *table)
{
unsigned int min_freq = ~0;
unsigned int max_freq = 0;
unsigned int i;
for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
unsigned int freq = table[i].frequency;
if (freq == CPUFREQ_ENTRY_INVALID) {
pr_debug("table entry %u is invalid, skipping\n", i);
continue;
}
pr_debug("table entry %u: %u kHz, %u driver_data\n",
i, freq, table[i].driver_data);
if (freq < min_freq)
min_freq = freq;
if (freq > max_freq)
max_freq = freq;
}
policy->min = policy->cpuinfo.min_freq = min_freq;
policy->max = policy->cpuinfo.max_freq = max_freq;
if (policy->min == ~0)
return -EINVAL;
else
return 0;
}
它是 cpufreq driver的init成員函數的助手,用於將policy->min和 policy->max設置為與 cpuinfo->min_freq和 cpuinfo.max_freq相同的值。
int cpufreq_frequency_table_verify(struct cpufreq_policy *policy,
struct cpufreq_frequency_table *table)
它是 cpufreq driver的verify成員函數的助手,確保至少有1個有效的CPU頻率位於policy->min到 policy->max的範圍內。
int cpufreq_frequency_table_target(struct cpufreq_policy *policy,
struct cpufreq_frequency_table *table,
unsigned int target_freq,
unsigned int relation,
unsigned int *index)
CPUFreq的策略
SoCCPUFreq驅動只是設定了CPU的頻率參數,以及提供了設置頻率的途徑,但是它並不會管CPU自身究竟應該運行在哪種頻率上。究竟頻率依據的是哪種標準,進行何種變化而這些完全由 CPUFreq的策略( policy)決定,這些策略如表19.2所示。
CPUFreq的策略 | 策略實現的方式 |
---|---|
cpufreq_ondemand | 平時以低速的方式運行,當系統負載提高需自動提高頻率 |
cpufreq_performance | cpu以最高頻率運行,即scaling_max_freq |
cpufreq_consevative | 字面含義是傳統的、保守的,跟ondemand相似,區別在於動態頻率在變更的時候採用漸進的方式 |
cpufreq_powersave | cpu以最低頻率運行,即scaling_min_freq |
cpufreq_userspace | 讓根用戶通過sys節點scaling_setspeed設置頻率 |
在 Android系統中,則增加了1個交互策略,該策略適合於對延遲敏感的U1交互任務,當有UI交互任務的時候,該策略會更加激進並及時地調整CPU頻率。
總而言之,系統的狀態以及CPUFreq的策略共同決定了CPU頻率跳變的目標, CPUFreq核心層並將目標頻率傳遞給底層具體SoC的 CPUFreq驅動,該驅動修改硬體,完成頻率的變換,如圖19.2所示。
用戶空間一般可通過 /sys/devices/system/cpu/cpux/cpufreq節點來設置 CPUFreq。譬如,我們要設置 CPUFreq到700Mhz,採用 userspace策略,則運行如下命令:
echo userspace >/sys/devices/system/cpu/cpu0/cpufreq/scaling governor
echo 700000>/sys/devices/system/cpu/cpu/cpufreq/scaling set speed
CPUFreq的性能測試和調優
使用cpufreq-bench
工具可以幫助工程師分析採用CPUFreq後對系統性能的影響;
CPUFreq通知
CPUFreq子系統會發出通知的情況有兩種:CPUFreq的策略變化或者CPU運行頻率變化。
在策略變化的過程中,會發送3次通知:
- CPUFREQ ADJUST:所有註冊的 notifier可以根據硬體或者溫度的情況去修改範圍(即 policy->min和 policy->max);
- CPUFREQ INCOMPATIBLE:除非前面的策略設定可能會導致硬體出錯,否則被註冊的notifier不能改變範圍等設定;
- CPUFREQ NOTIFY:所有註冊的notifier都會被告知新的策略已經被設置。在頻率變化的過程中,會發送2次通知;
- CPUFREQ PRECHANGE:準備進行頻率變更;
- CPUFREQ POSTCHANGE:已經完成頻率變更。
notifier中的第3個參數是一個 cpufreq_freqs的結構體,包含cpu(CPU號)、old(過去的頻率)和new(現在的頻率)這3個成員。發送 CPUFREQ_PRECHANGE和 CPUFREQ_POSTCHANGE的代碼如下:
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
unsigned long val, void *v)
如果某模塊關心 CPUFREQ_PRECHANGE或 CPUFREQ_POSTCHANGE事件,可簡單地使用 Linux notifier機制監控。譬如, drivers/video/sallo0fbc在CPU頻率變化過程中需對自身硬體進行相關設置,因此它註冊了 notifier併在 CPUFREQ _PRECHANGE和CPUFREQ_POSTCHANGE情況下分別進行不同的處理,如代碼清單19.3所示。
#ifdef CONFIG_CPU_FREQ
fbi->freq_transition.notifier_call = sa1100fb_freq_transition;
fbi->freq_policy.notifier_call = sa1100fb_freq_policy;
cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER);
cpufreq_register_notifier(&fbi->freq_policy, CPUFREQ_POLICY_NOTIFIER);
#endif
/*
* CPU clock speed change handler. We need to adjust the LCD timing
* parameters when the CPU clock is adjusted by the power management
* subsystem.
*/
static int
sa1100fb_freq_transition(struct notifier_block *nb, unsigned long val,
void *data)
{
struct sa1100fb_info *fbi = TO_INF(nb, freq_transition);
struct cpufreq_freqs *f = data;
u_int pcd;
switch (val) {
case CPUFREQ_PRECHANGE:
set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE);
break;
case CPUFREQ_POSTCHANGE:
pcd = get_pcd(fbi->fb.var.pixclock, f->new);
fbi->reg_lccr3 = (fbi->reg_lccr3 & ~0xff) | LCCR3_PixClkDiv(pcd);
set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE);
break;
}
return 0;
}
此外,如果在系統掛起/恢復的過程中CPU頻率會發生變化,則 CPUFreq子系統也會發出CPUFREQ_SUSPENDCHANGE和 CPUFREQ _RESUMECHANGE這兩個通知。
值得一提的是,除了CPU以外,一些非CPU設備也支持多個操作頻率和電壓,存在多個OPP。Linux3.2之後的內核也支持針對這種非CPU設備的DVFS,該套子系統為Devfreq。與CPUFreq存在一個drivers/cpufreq目錄相似,在內核中也存在一個drivers/devore的目錄。