之前章節(jié)介紹的電源管理都都直接下電,不用電當(dāng)然能節(jié)能,但是還有比較溫柔的方法就是通過調(diào)節(jié)電壓頻率。比如經(jīng)常的一個(gè)說法:CPU太熱了跑不動了,快給降頻下。頻率就干活的速度,干活太快,CPU都要燒了,太熱了,費(fèi)電啊。但是在戶外的設(shè)備,環(huán)境溫度過高還是要考慮遮陽、通風(fēng)來應(yīng)對,但降頻也可以降低溫度,但是會引起卡頓啊。
一般電壓和頻率是成對出現(xiàn)的,也叫OPP(Operating Performance Points),對其進(jìn)行調(diào)節(jié)也叫DVFS(Dynamic Voltage and Frequency Scaling),下面就來揭開這些技術(shù)的神秘面紗。
1. 整體介紹
1.1 DVFS
DVFS(Dynamic Voltage and Frequency Scaling)即動態(tài)電壓頻率調(diào)整。這項(xiàng)技術(shù)可以根據(jù)芯片運(yùn)行的應(yīng)用程序的計(jì)算需求制定策略,動態(tài)調(diào)整電壓和頻率:
- 在不需要高性能時(shí),降低電壓和頻率,以降低功耗;
- 在需要高性能時(shí),提高電壓和頻率,以提高性能,從而達(dá)到兼顧性能而又節(jié)能的目的。
DVFS技術(shù)利用了CMOS芯片的特性:CMOS芯片的能量消耗正比于電壓的平方和時(shí)鐘頻率:
- 減少能量消耗需要降低電壓和頻率。
- 僅僅降低時(shí)鐘頻率并不節(jié)約能量,因?yàn)闀r(shí)鐘頻率的降低會帶來任務(wù)執(zhí)行時(shí)間的增加。調(diào)節(jié)電壓需要以相同的比例調(diào)節(jié)頻率以滿足信號傳播延遲要求。然而不管是電壓調(diào)節(jié)還是頻率調(diào)節(jié),都會造成系統(tǒng)性能的損失,并增加系統(tǒng)的響應(yīng)延遲。
- DVFS技術(shù)是以延長任務(wù)執(zhí)行時(shí)間為代價(jià)來達(dá)到減少系統(tǒng)能量消耗的目的,體現(xiàn)了功耗與性能之間的權(quán)衡??梢酝ㄟ^減少時(shí)鐘頻率來降低通用處理器功耗的。
1.2 Linux 軟件流程框圖
CPUFreq系統(tǒng)流程:
- 用戶app可以使用/sys/devices/system/cpu/cpu0/cpufreq/下的接口文件設(shè)置cpu頻率
- 設(shè)置頻率的時(shí)候會調(diào)用相關(guān)governor的函數(shù),主要包括查詢、設(shè)置等
- governor負(fù)責(zé)采集與系統(tǒng)負(fù)載有關(guān)的信號,計(jì)算當(dāng)前的系統(tǒng)負(fù)載。根據(jù)系統(tǒng)的當(dāng)前負(fù)載,根據(jù)調(diào)節(jié)策略預(yù)測系統(tǒng)在下一時(shí)間段需要的性能。將預(yù)測的性能轉(zhuǎn)換成需要的頻率和電壓在cpufreq table中選擇一個(gè),進(jìn)行調(diào)整芯片的時(shí)鐘和電壓設(shè)置。
- governor需要設(shè)置的時(shí)候會調(diào)用cpufreq core的接口cpufreq_driver->target_index進(jìn)行設(shè)置
- driver會繼續(xù)調(diào)用opp驅(qū)動clk_set_rate(clk, freq)接口進(jìn)行寄存器設(shè)置,讓電壓頻率生效 另外:動態(tài)策略的governor會自動收集系統(tǒng)中的各種信號進(jìn)行動態(tài)調(diào)節(jié)
DVFS調(diào)節(jié)策略 一味的降頻降壓當(dāng)然是不能降低功耗的,因?yàn)榈皖l下運(yùn)行可能使系統(tǒng)處理任務(wù)的時(shí)長增加,從而整體上可能增加了功耗。所以DVFS的核心是動態(tài)調(diào)整的策略,其目的是根據(jù)當(dāng)時(shí)的系統(tǒng)負(fù)載實(shí)時(shí)調(diào)整,從而提供滿足當(dāng)時(shí)性能要求的最低功率,也就達(dá)到了最低功耗。
需要統(tǒng)計(jì)出這些模塊的負(fù)載情況,基本的策略當(dāng)然是工作負(fù)載增加則升頻升壓,工作負(fù)載降低則降頻降壓。工作負(fù)載的粗略模型是在一個(gè)時(shí)間窗口內(nèi),統(tǒng)計(jì)模塊工作的時(shí)間長度,設(shè)定不同閾值,高閾值對應(yīng)高電壓高頻率,低閾值對應(yīng)低電壓低頻率。每次統(tǒng)計(jì)值穿過閾值邊界,觸發(fā)DVFS轉(zhuǎn)換。
> 在調(diào)整頻率和電壓時(shí),要特別注意調(diào)整的順序: > - 當(dāng)頻率由高到低調(diào)整時(shí),應(yīng)該先降頻率,再降電壓; > - 相反,當(dāng)升高頻率時(shí),應(yīng)該先升電壓,再升頻率。
2. 相關(guān)代碼介紹
2.1 整體代碼框架
內(nèi)核目前有一套完整的代碼支持DVFS,具體可參考內(nèi)核下drivers/cpufreq/。
- cpufreq core:是cpufreq framework的核心模塊,和kernel其它framework類似,主要實(shí)現(xiàn)三類功能:
- 向上,以sysfs的形式向用戶空間提供統(tǒng)一的接口,以notifier的形式向其它driver提供頻率變化的通知。
- 內(nèi)部,抽象調(diào)頻調(diào)壓的公共邏輯和接口,主要圍繞struct cpufreq_driver、struct cpufreq_policy和struct cpufreq_governor三個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行。包括:圍繞結(jié)構(gòu)struct cpufreq_governor提供governor框架,用于實(shí)現(xiàn)不同的頻率調(diào)整機(jī)制;圍繞struct cpufreq_policy實(shí)現(xiàn)的一些功能等。
- 向下:提供CPU頻率和電壓控制的驅(qū)動框架,封裝通用操作接口給驅(qū)動,方便底層驅(qū)動的開發(fā);
-
cpufreq governor:負(fù)責(zé)調(diào)頻調(diào)壓的各種策略,每種governor計(jì)算頻率的方式不同,根據(jù)提供的頻率范圍和參數(shù)(閾值等),計(jì)算合適的頻率。
-
cpufreq driver:負(fù)責(zé)平臺相關(guān)的調(diào)頻調(diào)壓機(jī)制的實(shí)現(xiàn),基于cpu subsystem driver、OPP、clock driver、regulator driver等模塊,提供對CPU頻率和電壓的控制。kernel中實(shí)現(xiàn)了比較通用的驅(qū)動模塊cpufreq-dt.c
-
cpufreq stats:負(fù)責(zé)調(diào)頻信息和各頻點(diǎn)運(yùn)行時(shí)間等統(tǒng)計(jì),提供每個(gè)cpu的cpufreq有關(guān)的統(tǒng)計(jì)信息。
2.2 用戶態(tài)接口
cpufreq相關(guān)驅(qū)動模塊加載后,會在各cpu下創(chuàng)建:/sys/devices/system/cpu/cpuX/cpufreq接口
這是一個(gè)軟鏈接:cpufreq -> ../cpufreq/policy0
前綴是scaling的屬性文件表示軟件可調(diào)節(jié)的幾種屬性,前綴是cpuinfo的屬性文件表示硬件支持的幾種屬性。cpuinfo是scaling的子集,因?yàn)檐浖O(shè)置范圍在硬件支持范圍內(nèi)。 scaling_governor 可以手動修改設(shè)置:
echo ondemand > /sys/devices/system/cpu/cpu0/scaling_governor
一般系統(tǒng)啟動默認(rèn)為performance,支持5種模式,可以通過make menuconfig配置。
目前DVFS支持調(diào)頻調(diào)壓策略主要就是上面支持的5種:
- userspace(用戶定義的) 使用用戶在/sys 節(jié)點(diǎn)scaling_setspeed設(shè)置的頻率運(yùn)行。 最早的 cpufreq 子系統(tǒng)通過 userspace governor 為用戶提供了這種靈活性。系統(tǒng)將變頻策略的決策權(quán)交給了用戶態(tài)應(yīng)用程序,并提供了相應(yīng)的接口供用戶態(tài)應(yīng)用程序調(diào)節(jié) CPU 運(yùn)行頻率使用。 (可以使用Dominik 等人開發(fā)了cpufrequtils 工具包 )
- performancecpu(突出性能) 按照支持的最高頻率運(yùn)行
- ondemand(按需的) 系統(tǒng)負(fù)載小時(shí)以低頻率運(yùn)行,系統(tǒng)負(fù)載提高時(shí)按需提高頻率 userspace是內(nèi)核態(tài)的檢測,效率低。而ondemand正是人們長期以來希望看到的一個(gè)完全在內(nèi)核態(tài)下工作并且能夠以更加細(xì)粒度的時(shí)間間隔對系統(tǒng)負(fù)載情況進(jìn)行采樣分析的governor。
- conservative(保守的) 跟ondemand方式類似, 不同之處在于提高頻率時(shí)漸進(jìn)提高,而ondemand是跳變提高,ondemand比conservative先進(jìn),是conservative的改良版本。 ondemand governor 的最初實(shí)現(xiàn)是在可選的頻率范圍內(nèi)調(diào)低至下一個(gè)可用頻率。這種降頻策略的主導(dǎo)思想是盡量減小對系統(tǒng)性能的負(fù)面影響,從而不會使得系統(tǒng)性能在短時(shí)間內(nèi)迅速降低以影響用戶體驗(yàn)。但是在 ondemand governor 的這種最初實(shí)現(xiàn)版本在社區(qū)發(fā)布后,大量用戶的使用結(jié)果表明這種擔(dān)心實(shí)際上是多余的, ondemand governor在降頻時(shí)對于目標(biāo)頻率的選擇完全可以更加激進(jìn)。因此最新的 ondemand governor 在降頻時(shí)會在所有可選頻率中一次性選擇出可以保證 CPU 工作在 80% 以上負(fù)荷的頻率,當(dāng)然如果沒有任何一個(gè)可選頻率滿足要求的話則會選擇 CPU 支持的最低運(yùn)行頻率。大量用戶的測試結(jié)果表明這種新的算法可以在不影響系統(tǒng)性能的前提下做到更高效的節(jié)能。在算法改進(jìn)后, ondemand governor 的名字并沒有改變,而 ondemand governor 最初的實(shí)現(xiàn)也保存了下來,并且由于其算法的保守性而得名conservative 。 Ondemand降頻更加激進(jìn),conservative降頻比較緩慢保守,事實(shí)使用ondemand的效果也是比較好的。
- powersavecpu(省電的) 以支持的最低頻率運(yùn)行 CPU會固定工作在其支持的最低運(yùn)行頻率上。因此其和performance這兩種 governors 都屬于靜態(tài) governor ,即在使用它們時(shí) CPU 的運(yùn)行頻率不會根據(jù)系統(tǒng)運(yùn)行時(shí)負(fù)載的變化動態(tài)作出調(diào)整。這兩種 governors 對應(yīng)的是兩種極端的應(yīng)用場景,使用 performance governor 體現(xiàn)的是對系統(tǒng)高性能的最大追求,而使用 powersave governor 則是對系統(tǒng)低功耗的最大追求。
- schedutil:通過將自己的調(diào)頻策略注冊到hook,在負(fù)載發(fā)生變化的時(shí)候,會調(diào)用該hook,此時(shí)就可以進(jìn)行調(diào)頻決策或執(zhí)行調(diào)頻動作。前面的調(diào)頻策略都是周期采樣計(jì)算cpu負(fù)載有滯后性,精度也有限,而schedutil可以使用PELT(per entity load tracking)或者WALT(window assist load tracking)準(zhǔn)確的計(jì)算task的負(fù)載。如果支持fast_switch的功能,可以在中斷上下文直接進(jìn)行調(diào)頻。
功耗:performance > ondemand > conservative >powersave
2.3 主要數(shù)據(jù)結(jié)構(gòu)
2.3.1 驅(qū)動相關(guān)cpufreq_driver
在include/linux/cpufreq.h中,用于描述cpufreq的驅(qū)動,是驅(qū)動工程師最關(guān)注的結(jié)構(gòu)。如下默認(rèn)值:
static struct cpufreq_driver dt_cpufreq_driver = {
.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.verify = cpufreq_generic_frequency_table_verify,
.target_index = set_target,
.get = cpufreq_generic_get,
.init = cpufreq_init,
.exit = cpufreq_exit,
.ready = cpufreq_ready,
.name = "cpufreq-dt",
.attr = cpufreq_dt_attr,
.suspend = cpufreq_generic_suspend,
};
- name,該driver的名字,需要唯一,因?yàn)閏pufreq framework允許同時(shí)注冊多個(gè)driver,用戶可以根據(jù)實(shí)際情況選擇使用哪個(gè)driver。driver的標(biāo)識,就是name。
- flags,一些flag,具體會在后續(xù)的文章中介紹。 init,driver的入口,由cpufreq core在設(shè)備枚舉的時(shí)候調(diào)用,driver需要根據(jù)硬件情況,填充policy的內(nèi)容。
- verify,驗(yàn)證policy中的內(nèi)容是否符合硬件要求。它和init接口都是必須實(shí)現(xiàn)的接口。
- setpolicy,driver需要提供這個(gè)接口,用于設(shè)置CPU core動態(tài)頻率調(diào)整的范圍(即policy)。
- target、target_index,driver需要實(shí)現(xiàn)這兩個(gè)接口中的一個(gè)(target為舊接口,不推薦使用),用于設(shè)置CPU core為指定頻率(同時(shí)修改為對應(yīng)的電壓)。target_index()接口底層真正用于設(shè)置cpu為指定頻率的接口(同時(shí)修改為對應(yīng)的電壓)
有關(guān)struct cpufreq_driver的API包括:
1: int cpufreq_register_driver(struct cpufreq_driver *driver_data);
2: int cpufreq_unregister_driver(struct cpufreq_driver *driver_data);
3:
4: const char *cpufreq_get_current_driver(void);
5: void *cpufreq_get_driver_data(void);
分別為driver的注冊、注銷。獲取當(dāng)前所使用的driver名稱,以及該driver的私有數(shù)據(jù)結(jié)構(gòu)(driver_data字段)。
2.3.2 策略相關(guān)cpufreq_policy
linux使用cpufreq policy來抽象cpu設(shè)備的調(diào)頻調(diào)壓功能,用于描述不同的policy,包含頻率表、cpuinfo等各種信息,并且每個(gè)policy都會對應(yīng)某個(gè)具體的governor。
min/max frequency,調(diào)頻范圍,對于可以自動調(diào)頻的CPU而言,只需要這兩個(gè)參數(shù)就夠了。 current frequency和governor,對于不能自動調(diào)頻的CPU,需要governor設(shè)置具體的頻率值。下面介紹一下governor。 struct cpufreq_policy不會直接對外提供API。
2.3.3 管理策略cpufreq_governor
不同policy的管理策略,根據(jù)使用場景的不同,會有不同的調(diào)頻調(diào)壓策略。如下一個(gè)governor的默認(rèn)值:
static struct cpufreq_governor cpufreq_gov_userspace = {
.name = "userspace",
.init = cpufreq_userspace_policy_init,
.exit = cpufreq_userspace_policy_exit,
.start = cpufreq_userspace_policy_start,
.stop = cpufreq_userspace_policy_stop,
.limits = cpufreq_userspace_policy_limits,
.store_setspeed = cpufreq_set,
.show_setspeed = show_speed,
.owner = THIS_MODULE,
};
- name,該governor的名稱。
- governor,用于governor狀態(tài)切換的回調(diào)函數(shù)。
- show_setspeed、store_setspeed,用于提供sysfs “setspeed” attribute文件的回調(diào)函數(shù)。
- max_transition_latency,該governor所能容忍的最大頻率切換延遲。
- cpufreq governors主要向具體的governor模塊提供governor的注冊和注銷接口
2.2 初始化流程
2.2.1 governor注冊
cpufreq_register_governor 如果policy中有默認(rèn)的governor,則調(diào)用find_governor,在列表中尋找。cpufreq core定義了一個(gè)全局鏈表變量:cpufreq_governor_list,注冊函數(shù)首先根據(jù)governor的名稱,通過__find_governor()函數(shù)查找該governor是否已經(jīng)被注冊過,如果沒有被注冊過,則把代表該governor的結(jié)構(gòu)體添加到cpufreq_governor_list鏈表中。
系統(tǒng)中可以同時(shí)存在多個(gè)governor,policy通過cpufreq_policy->governor指針和某個(gè)governor相關(guān)聯(lián)。要想一個(gè)governor能夠被使用,首先要把該governor注冊到cpufreq framework中。例如:
static int __init cpufreq_gov_userspace_init(void)
{
return cpufreq_register_governor(&cpufreq_gov_userspace);
}
#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE
struct cpufreq_governor *cpufreq_default_governor(void)
{
return &cpufreq_gov_userspace;
}
fs_initcall(cpufreq_gov_userspace_init);
注冊的gov定義為:
static struct cpufreq_governor cpufreq_gov_userspace = {
.name = "userspace",
.init = cpufreq_userspace_policy_init,
.exit = cpufreq_userspace_policy_exit,
.start = cpufreq_userspace_policy_start,
.stop = cpufreq_userspace_policy_stop,
.limits = cpufreq_userspace_policy_limits,
.store_setspeed = cpufreq_set,
.show_setspeed = show_speed,
.owner = THIS_MODULE,
};
2.2.2 cpufreq驅(qū)動發(fā)現(xiàn)注冊
dt_cpufreq_probe()在drivers/cpufreq/cpufreq-dt.c中 系統(tǒng)啟動的時(shí)候平臺驅(qū)動dt_cpufreq_platdrv,會執(zhí)行prob函數(shù)dt_cpufreq_probe()
static struct cpufreq_driver dt_cpufreq_driver = {
.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.verify = cpufreq_generic_frequency_table_verify,
.target_index = set_target,
.get = cpufreq_generic_get,
.init = cpufreq_init,
.exit = cpufreq_exit,
.ready = cpufreq_ready,
.name = "cpufreq-dt",
.attr = cpufreq_dt_attr,
.suspend = cpufreq_generic_suspend,
};
cpufreq_register_driver(&dt_cpufreq_driver),在drivers/cpufreq/cpufreq.c中 cpufreq_register_driver為cpufreqdriver注冊的入口,驅(qū)動程序通過調(diào)用該函數(shù)進(jìn)行初始化,并傳入相關(guān)的struct cpufreq_driver,cpufreq_register_driver會調(diào)用subsys_interface_register,入?yún)椋?/p>
static struct subsys_interface cpufreq_interface = {
.name = "cpufreq",
.subsys = &cpu_subsys,
.add_dev = cpufreq_add_dev,
.remove_dev = cpufreq_remove_dev,
};
最終執(zhí)行回調(diào)函數(shù)cpufreq_add_dev。
2.2.3 CPU subsys注冊
kernel將cpu都抽象成device,并抽象出cpu_subsys bus,所有cpu都掛載在這個(gè)bus下。每個(gè)bus都包含一個(gè)struct subsys_private結(jié)構(gòu)的成員p,該結(jié)構(gòu)包括一個(gè)interface list成員interfaces和設(shè)備鏈表klist_devices。interface list上的一個(gè)interface通常用于抽象bus下的一個(gè)功能。
cpufreq是CPU device的一類特定功能,也就被抽象為一個(gè)subsys interface(kernel使用struct subsys_interface結(jié)構(gòu)表示)即變量cpufreq_interface,, 掛載在interface list下。cpufreq作為一個(gè)功能掛載到cpu subsys下后會對相應(yīng)的所有設(shè)備即cpu執(zhí)行interface.add_dev()操作,表示對subsys_private支持的設(shè)備都添加這個(gè)功能,在添加這個(gè)功能時(shí)為每個(gè)cpu設(shè)備生成具體的policy結(jié)構(gòu),即struct cpufreq_policy.
上圖涉及cpu初始化,在系統(tǒng)啟動的時(shí)候:
//drivers/base/cpu.c
register_cpu
cpu->dev.bus = &cpu_subsys;
device_register
device_add
bus_add_device
error = device_add_groups(dev, bus->dev_groups);//向總線注冊設(shè)備
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);//向subsys_private
回到cpureq流程中,subsys_interface_register(),在drivers/base/bus.c中
mutex_lock(&subsys->p->mutex);
list_add_tail(&sif->node, &subsys->p->interfaces);
if (sif->add_dev) {
subsys_dev_iter_init(&iter, subsys, NULL, NULL);
while ((dev = subsys_dev_iter_next(&iter)))
sif->add_dev(dev, sif);
subsys_dev_iter_exit(&iter);
}
mutex_unlock(&subsys->p->mutex);
這里可以看到對于多核,都執(zhí)行了cpufreq_add_dev,會為cpu device創(chuàng)建struct cpufreq_policy結(jié)構(gòu)。 cpufreq_add_dev(),在drivers/cpufreq/cpufreq.c中
static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
{
struct cpufreq_policy *policy;
unsigned cpu = dev->id;
int ret;
if (cpu_online(cpu)) {
ret = cpufreq_online(cpu);
if (ret)
return ret;
}
/* Create sysfs link on CPU registration */
policy = per_cpu(cpufreq_cpu_data, cpu);
if (policy)
add_cpu_dev_symlink(policy, cpu);
return 0;
}
3.2.4 CPU上線設(shè)置
cpufreq_online(cpu)在drivers/cpufreq/cpufreq.c中
?cpufreq_policy_alloc()創(chuàng)建policy節(jié)點(diǎn)/sys/devices/system/cpu/cpufreq/*
?cpufreq_driver->init(policy)指向cpufreq_init()
?cpufreq_add_dev_interface()創(chuàng)建sysfs節(jié)點(diǎn)的一些可選屬性
?cpufreq_init_policy()初始化policy的governor
cpufreq_driver->init對應(yīng)cpufreq_init()函數(shù) 這個(gè)函數(shù)會解析cpu信息得到cpu_dev、cpu_clk、opp_table等
cpu_dev = get_cpu_device(policy->cpu);
cpu_clk = clk_get(cpu_dev, NULL);
ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, policy->cpus);//多CPU共享
opp_table = dev_pm_opp_set_regulators(cpu_dev, &name, 1);
priv->reg_name = name;
priv->opp_table = opp_table;
priv->cpu_dev = cpu_dev;
policy->driver_data = priv;
policy->clk = cpu_clk;
policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(cpu_dev) / 1000;
ret = cpufreq_table_validate_and_show(policy, freq_table);
cpufreq_table_validate_and_show()里面找到CPU支持的最大和最小頻率
int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy,
struct cpufreq_frequency_table *table)
{
struct cpufreq_frequency_table *pos;
unsigned int min_freq = ~0;
unsigned int max_freq = 0;
unsigned int freq;
cpufreq_for_each_valid_entry(pos, table) {
freq = pos->frequency;
if (!cpufreq_boost_enabled()
&& (pos->flags & CPUFREQ_BOOST_FREQ))
continue;
pr_debug("table entry %u: %u kHz
", (int)(pos - table), freq);
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;
}
設(shè)置policy的時(shí)候,會讀取cpu的頻率表,賦值給policy->min和policy->max。另外各種governor也用到frequency table。
frequency table是CPU core可以正確運(yùn)行的一組頻率/電壓組合,之所以存在的一個(gè)思考點(diǎn)是:table是頻率和電壓之間的一個(gè)一一對應(yīng)的組合,因此cpufreq framework只需要關(guān)心頻率,所有的策略都稱做“調(diào)頻”策略。而cpufreq driver可以在“調(diào)頻”的同時(shí),通過table取出和頻率對應(yīng)的電壓,進(jìn)行修改CPU core電壓,實(shí)現(xiàn)“調(diào)壓”的功能,這簡化了設(shè)計(jì)。 例如在DTS中:
2.2.5 策略初始化
cpufreq_init_policy(),drivers/cpufreq/cpufreq.c在 使用默認(rèn)策略初始化policy
/* Update governor of new_policy to the governor used before hotplug */
gov = find_governor(policy->last_governor);
if (gov) {
pr_info("dddd Restoring governor %s for cpu %d
",
policy->governor->name, policy->cpu);
} else {
gov = cpufreq_default_governor();
if (!gov)
return -ENODATA;
}
new_policy.governor = gov;
/* set default policy */
return cpufreq_set_policy(policy, &new_policy);
如果policy中有默認(rèn)的governor,則調(diào)用find_governor,在列表中尋找。cpufreq core定義了一個(gè)全局鏈表變量:cpufreq_governor_list,注冊函數(shù)首先根據(jù)governor的名稱,通過__find_governor()函數(shù)查找該governor是否已經(jīng)被注冊過,如果沒有被注冊過,則把代表該governor的結(jié)構(gòu)體添加到cpufreq_governor_list鏈表中。
系統(tǒng)中可以同時(shí)存在多個(gè)governor,policy通過cpufreq_policy->governor指針和某個(gè)governor相關(guān)聯(lián)。要想一個(gè)governor能夠被使用,首先要把該governor注冊到cpufreq framework中。例如:
fs_initcall(cpufreq_gov_performance_init);
static int __init cpufreq_gov_performance_init(void)
{
return cpufreq_register_governor(&cpufreq_gov_performance);
}
這里我們默認(rèn)使用default
#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE
struct cpufreq_governor *cpufreq_default_governor(void)
{
return &cpufreq_gov_performance;
}
#endif
static struct cpufreq_governor cpufreq_gov_performance = {
.name = "performance",
.owner = THIS_MODULE,
.limits = cpufreq_gov_performance_limits,
};
最后調(diào)用cpufreq_set_policy(policy, &new_policy);去設(shè)置policy
2.2.6 governor初始化
cpufreq_set_policy(),在drivers/cpufreq/cpufreq.c中
cpufreq_init_governor->policy->governor->init(policy);
cpufreq_start_governor->policy->governor->start(policy);
在governor初始化和啟動的時(shí)候會發(fā)生:CPUFreq通知 CPUFreq子系統(tǒng)會發(fā)出通知的情況有兩種:CPUFreq的策略變化或者CPU運(yùn)行頻率變化。
在策略變化的過程中,例如cpufreq_set_policy函數(shù)中,會發(fā)送3次通知:
- CPUFREQ_ADJUST:所有注冊的notifier可以根據(jù)硬件或者溫度的情況去修改范圍(即policy->min和policy->max);
- CPUFREQ_INCOMPATIBLE:除非前面的策略設(shè)定可能會導(dǎo)致硬件出錯,否則被注冊的notifier不能改變范圍等設(shè)定;
- CPUFREQ_NOTIFY:所有注冊的notifier都會被告知新的策略已經(jīng)被設(shè)置。
在頻率變化的過程中,例如__cpufreq_notify_transition函數(shù)中,會發(fā)送2次通知:
- CPUFREQ_PRECHANGE:準(zhǔn)備進(jìn)行頻率變更;
- CPUFREQ_POSTCHANGE:已經(jīng)完成頻率變更。
/* notification of the new policy */
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_NOTIFY, new_policy);
cpufreq_policy_notifier_list
cpufreq_register_notifier()函數(shù)注冊這個(gè)鏈表
3.2.6 設(shè)置熱插拔計(jì)算機(jī)狀態(tài)的回調(diào)函數(shù)
cpuhp_setup_state_nocalls_cpuslocked(): 參數(shù)說明:
__cpuhp_setup_state_cpuslocked(
CPUHP_AP_ONLINE_DYN, "cpufreq:online", false,
cpuhp_cpufreq_online,cpuhp_cpufreq_offline, false);
* __cpuhp_setup_state_cpuuslocked—設(shè)置熱插拔計(jì)算機(jī)狀態(tài)的回調(diào)函數(shù)
* @state:要設(shè)置的狀態(tài)
* @invoke:如果為true,啟動函數(shù)將被調(diào)用于cpu,cpu state >= @state
* @startup:啟動回調(diào)函數(shù)
* @teardown: teardown回調(diào)函數(shù)
* @multi_instance:狀態(tài)是為多個(gè)實(shí)例設(shè)置的,然后添加。
2.3 userspace governor
用戶空間監(jiān)控CPUFreq流程圖
### 2.3.1 用戶接口說明 userspace governor是一種用戶可以自己手動調(diào)整自己cpu頻率的governor,即在linux目錄下:/sys/devices/system/cpu/cpu0/cpufreq/,有一個(gè)參數(shù)scaling_setspeed,是這個(gè)governor轉(zhuǎn)有的,其他governor是不能對其進(jìn)行讀寫操作的,只有這個(gè)governor才能這樣做。
對應(yīng)底層有處理函數(shù),設(shè)置也有處理函數(shù)。
2.3.2 配置說明
默認(rèn)是Performance的策略,我們可以通過make menuconfig選擇,如下:
保存后在.config中可以看到
CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y
在代碼里面搜索這個(gè)宏,drivers/cpufreq/cpufreq_userspace.c中:
#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE
struct cpufreq_governor *cpufreq_default_governor(void)
{
return &cpufreq_gov_userspace;
}
2.3.3 回調(diào)函數(shù)介紹
cpufreq_gov_userspace對應(yīng)
static struct cpufreq_governor cpufreq_gov_userspace = {
.name = "userspace",
.init = cpufreq_userspace_policy_init,
.exit = cpufreq_userspace_policy_exit,
.start = cpufreq_userspace_policy_start,
.stop = cpufreq_userspace_policy_stop,
.limits = cpufreq_userspace_policy_limits,
.store_setspeed = cpufreq_set,
.show_setspeed = show_speed,
.owner = THIS_MODULE,
};
可以看到其中有init函數(shù)和start函數(shù)。 cpufreq_userspace_policy_init 申請一個(gè)governor_data
policy->governor_data = setspeed;
cpufreq_userspace_policy_start 設(shè)置policy的cur頻率
*setspeed = policy->cur;
cpufreq_userspace_policy_limits 就是約束性檢查,如果超過max或者小于min進(jìn)行重新設(shè)定
show_setspeed 就是讀scaling_setspeed-當(dāng)前cpu頻率
store_setspeed 就是寫scaling_setspeed,可以用戶控制。改變cpu頻率的時(shí)候會調(diào)用如下函數(shù):
ret = __cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_L);
2.3.4 調(diào)頻調(diào)壓流程
例如輸入命令:
echo 700000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
__cpufreq_driver_target->__target_index->cpufreq_driver->target_index
static int set_target(struct cpufreq_policy *policy, unsigned int index)
{
struct private_data *priv = policy->driver_data;
return dev_pm_opp_set_rate(priv->cpu_dev,
policy->freq_table[index].frequency * 1000);
}
dev_pm_opp_set_rate()函數(shù)在drivers/base/power/opp/core.c中定義 找到opp_table進(jìn)行調(diào)頻調(diào)壓,opp_table的名字是/cpus/cpu0_opp_table
opp_table = _find_opp_table(dev);
clk = opp_table->clk;
freq = clk_round_rate(clk, target_freq);
if ((long)freq <= 0)
freq = target_freq;
old_freq = clk_get_rate(clk);
ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
clk_set_rate(clk, freq);在drivers/clk/clk.c中定義
ret = clk_core_set_rate_nolock(clk->core, rate);
clk的名字是,rate是要設(shè)置的頻率
/* change the rates */
clk_change_rate(top);
top的名字為cpu_core0_mux_clk,節(jié)點(diǎn)父子關(guān)系為:
armpll1_912m_cpu_clk->cpu_core0_mux_clk->cpu_core0_div_clk->cpu_core0_clk
首先設(shè)置armpll1_912m_cpu_clk
clk_change_rate()
core->ops->set_parent(core->hw, core->new_parent_index);
set_parent對應(yīng)clk_mux_set_parent()函數(shù)在drivers/clk/clk-mux.c中
static int clk_mux_set_parent(struct clk_hw *hw, u8 index) { struct clk_mux *mux = to_clk_mux(hw);
val = clk_readl(mux->reg);
val &= ~(mux->mask << mux->shift);
val |= index << mux->shift;
clk_writel(val, mux->reg);
mux->reg值是0x42000020,index是4,clk_readl出來是默認(rèn)值5,需要寫入為4
cpu_core0_div_clk進(jìn)行了頻率設(shè)置
clk_change_rate
core->ops->set_rate(core->hw, core->new_rate, best_parent_rate);
const struct clk_ops clk_divider_ops = { .recalc_rate = clk_divider_recalc_rate, .round_rate = clk_divider_round_rate, .set_rate = clk_divider_set_rate, };
clk_divider_set_rate
static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { value = divider_get_val(rate, parent_rate, divider->table, divider->width, divider->flags);
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
val = div_mask(divider->width) << (divider->shift + 16);
} else {
val = clk_readl(divider->reg);
val &= ~(div_mask(divider->width) << divider->shift);
}
val |= (u32)value << divider->shift;
clk_writel(val, divider->reg);
其中divider->reg為0x42000020,value=0 divider->shift的值為8 clk_readl(divider->reg);讀出來值為4已經(jīng)是要設(shè)置的值了。
2.4 其他governor
2.4.1 ondemand governor
ondemand governor,最終是通過調(diào)頻接口od_dbs_update實(shí)現(xiàn)計(jì)算負(fù)載進(jìn)行調(diào)頻的。
//drivers/cpufreq/cpufreq_ondemand.c
od_dbs_update
od_update
static void od_update(struct cpufreq_policy *policy)
{
unsigned int load = dbs_update(policy);//負(fù)載(百分比)(1)
/* Check for frequency increase */
if (load > dbs_data->up_threshold) {//(2)如果負(fù)載大于策略設(shè)置的閾值,則直接切換到最大頻率
/* If switching to max speed, apply sampling_down_factor */
if (policy->cur < policy->max)
policy_dbs->rate_mult = dbs_data->sampling_down_factor;
dbs_freq_increase(policy, policy->max);
} else {
/* Calculate the next frequency proportional to load */
unsigned int freq_next, min_f, max_f;
min_f = policy->cpuinfo.min_freq;
max_f = policy->cpuinfo.max_freq;
freq_next = min_f + load * (max_f - min_f) / 100;
//(3)按照負(fù)載百分比,在頻率范圍內(nèi)選擇合適頻率
/* No longer fully busy, reset rate_mult */
policy_dbs->rate_mult = 1;
if (od_tuners->powersave_bias)//(4)
freq_next = od_ops.powersave_bias_target(policy,
freq_next,
CPUFREQ_RELATION_L);
__cpufreq_driver_target(policy, freq_next, CPUFREQ_RELATION_C);//設(shè)置頻率
}
(1)計(jì)算負(fù)載函數(shù): od_dbs_update()核心方法是: 當(dāng)前負(fù)載load = 100 * (time_elapsed - idle_time) / time_elapsed idle_time = 本次idle時(shí)間 - 上次idle時(shí)間 time_elapsed = 本次總運(yùn)行時(shí)間 - 上次總運(yùn)行時(shí)間 該函數(shù)返回使用此policy的各個(gè)cpu中的最大負(fù)載。
(2)當(dāng)最大負(fù)載大于策略設(shè)置的最大閾值時(shí),調(diào)用dbs_freq_increase()將頻率設(shè)置在最大頻率。
(3)按照負(fù)載百分比設(shè)置合適頻率 freq_next = min_f + load * (max_f - min_f) / 100;
(4) 表明我們?yōu)榱诉M(jìn)一步節(jié)省電力,我們希望在計(jì)算出來的新頻率的基礎(chǔ)上,再乘以一個(gè)powersave_bias設(shè)定的百分比,作為真正的運(yùn)行頻率,powersave_bias的值從0-1000,每一步代表0.1%
2.4.2 schedutil governor
不同的governor的觸發(fā)調(diào)頻調(diào)壓流程不一樣,這里以schedutil governor為例。 CFS負(fù)載變化的時(shí)候或者RT、DL任務(wù)狀態(tài)更新的時(shí)候,就會啟動調(diào)頻。這幾個(gè)scheduler類會調(diào)用cpufreq_update_util函數(shù)(前面注冊進(jìn)來的hook函數(shù))觸發(fā)schedutil工作。每個(gè)cpu最終會回調(diào)到sugov_upate_shared或者sugov_upate_single函數(shù)中的一個(gè)。 由于是從scheduler里直接調(diào)用下來的,最終執(zhí)行調(diào)頻切換時(shí),無論是快速路徑觸發(fā)的簡單寫寄存器,還是慢速路徑觸發(fā)的kthread都不會占用過多時(shí)間或者調(diào)度開銷。
2.4.3 Interactive governor
Interactive 與Conservative相對,快速提升頻率,緩慢降低頻率
- 優(yōu)點(diǎn): 比Ondemand稍強(qiáng)的性能,較快的響應(yīng)速度
- 缺點(diǎn): 在不需要時(shí)仍然維持較高的頻率,比Ondemand耗電 Interactive X 基于Interactive改進(jìn),區(qū)分開關(guān)屏狀態(tài)情景
- 優(yōu)點(diǎn):比Interactive省電
- 缺點(diǎn):穩(wěn)定性不如Interactive 代碼位置:drivers/cpufreq/cpufreq_interactive.c 首先需要定義一個(gè)cpufreq_governor類型的結(jié)構(gòu)體用來描述interactive governor.
static struct interactive_governor interactive_gov = {
.gov = {
.name = "interactive",
.max_transition_latency = TRANSITION_LATENCY_LIMIT,
.owner = THIS_MODULE,
.init = cpufreq_interactive_init,
.exit = cpufreq_interactive_exit,
.start = cpufreq_interactive_start,
.stop = cpufreq_interactive_stop,
.limits = cpufreq_interactive_limits,
}
};
后記
本節(jié)代碼有點(diǎn)多,不是調(diào)試這個(gè)可以不用關(guān)注代碼,想深入學(xué)習(xí)還是需要運(yùn)行起來代碼打點(diǎn)log比較好。
-
電源管理
+關(guān)注
關(guān)注
115文章
6183瀏覽量
144541 -
cpu
+關(guān)注
關(guān)注
68文章
10870瀏覽量
211901 -
電壓頻率
+關(guān)注
關(guān)注
0文章
9瀏覽量
8075
原文標(biāo)題:電源管理入門-6 CPUFreq
文章出處:【微信號:OS與AUTOSAR研究,微信公眾號:OS與AUTOSAR研究】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論