之前章節介紹的電源管理都都直接下電,不用電當然能節能,但是還有比較溫柔的方法就是通過調節電壓頻率。比如經常的一個說法:CPU太熱了跑不動了,快給降頻下。頻率就干活的速度,干活太快,CPU都要燒了,太熱了,費電啊。但是在戶外的設備,環境溫度過高還是要考慮遮陽、通風來應對,但降頻也可以降低溫度,但是會引起卡頓啊。
一般電壓和頻率是成對出現的,也叫OPP(Operating Performance Points),對其進行調節也叫DVFS(Dynamic Voltage and Frequency Scaling),下面就來揭開這些技術的神秘面紗。
1. 整體介紹
1.1 DVFS
DVFS(Dynamic Voltage and Frequency Scaling)即動態電壓頻率調整。這項技術可以根據芯片運行的應用程序的計算需求制定策略,動態調整電壓和頻率:
- 在不需要高性能時,降低電壓和頻率,以降低功耗;
- 在需要高性能時,提高電壓和頻率,以提高性能,從而達到兼顧性能而又節能的目的。
DVFS技術利用了CMOS芯片的特性:CMOS芯片的能量消耗正比于電壓的平方和時鐘頻率:
- 減少能量消耗需要降低電壓和頻率。
- 僅僅降低時鐘頻率并不節約能量,因為時鐘頻率的降低會帶來任務執行時間的增加。調節電壓需要以相同的比例調節頻率以滿足信號傳播延遲要求。然而不管是電壓調節還是頻率調節,都會造成系統性能的損失,并增加系統的響應延遲。
- DVFS技術是以延長任務執行時間為代價來達到減少系統能量消耗的目的,體現了功耗與性能之間的權衡。可以通過減少時鐘頻率來降低通用處理器功耗的。
1.2 Linux 軟件流程框圖
CPUFreq系統流程:
- 用戶app可以使用/sys/devices/system/cpu/cpu0/cpufreq/下的接口文件設置cpu頻率
- 設置頻率的時候會調用相關governor的函數,主要包括查詢、設置等
- governor負責采集與系統負載有關的信號,計算當前的系統負載。根據系統的當前負載,根據調節策略預測系統在下一時間段需要的性能。將預測的性能轉換成需要的頻率和電壓在cpufreq table中選擇一個,進行調整芯片的時鐘和電壓設置。
- governor需要設置的時候會調用cpufreq core的接口cpufreq_driver->target_index進行設置
- driver會繼續調用opp驅動clk_set_rate(clk, freq)接口進行寄存器設置,讓電壓頻率生效 另外:動態策略的governor會自動收集系統中的各種信號進行動態調節
DVFS調節策略 一味的降頻降壓當然是不能降低功耗的,因為低頻下運行可能使系統處理任務的時長增加,從而整體上可能增加了功耗。所以DVFS的核心是動態調整的策略,其目的是根據當時的系統負載實時調整,從而提供滿足當時性能要求的最低功率,也就達到了最低功耗。
需要統計出這些模塊的負載情況,基本的策略當然是工作負載增加則升頻升壓,工作負載降低則降頻降壓。工作負載的粗略模型是在一個時間窗口內,統計模塊工作的時間長度,設定不同閾值,高閾值對應高電壓高頻率,低閾值對應低電壓低頻率。每次統計值穿過閾值邊界,觸發DVFS轉換。
> 在調整頻率和電壓時,要特別注意調整的順序: > - 當頻率由高到低調整時,應該先降頻率,再降電壓; > - 相反,當升高頻率時,應該先升電壓,再升頻率。
2. 相關代碼介紹
2.1 整體代碼框架
內核目前有一套完整的代碼支持DVFS,具體可參考內核下drivers/cpufreq/。
- cpufreq core:是cpufreq framework的核心模塊,和kernel其它framework類似,主要實現三類功能:
- 向上,以sysfs的形式向用戶空間提供統一的接口,以notifier的形式向其它driver提供頻率變化的通知。
- 內部,抽象調頻調壓的公共邏輯和接口,主要圍繞struct cpufreq_driver、struct cpufreq_policy和struct cpufreq_governor三個數據結構進行。包括:圍繞結構struct cpufreq_governor提供governor框架,用于實現不同的頻率調整機制;圍繞struct cpufreq_policy實現的一些功能等。
- 向下:提供CPU頻率和電壓控制的驅動框架,封裝通用操作接口給驅動,方便底層驅動的開發;
-
cpufreq governor:負責調頻調壓的各種策略,每種governor計算頻率的方式不同,根據提供的頻率范圍和參數(閾值等),計算合適的頻率。
-
cpufreq driver:負責平臺相關的調頻調壓機制的實現,基于cpu subsystem driver、OPP、clock driver、regulator driver等模塊,提供對CPU頻率和電壓的控制。kernel中實現了比較通用的驅動模塊cpufreq-dt.c
-
cpufreq stats:負責調頻信息和各頻點運行時間等統計,提供每個cpu的cpufreq有關的統計信息。
2.2 用戶態接口
cpufreq相關驅動模塊加載后,會在各cpu下創建:/sys/devices/system/cpu/cpuX/cpufreq接口
這是一個軟鏈接:cpufreq -> ../cpufreq/policy0
前綴是scaling的屬性文件表示軟件可調節的幾種屬性,前綴是cpuinfo的屬性文件表示硬件支持的幾種屬性。cpuinfo是scaling的子集,因為軟件設置范圍在硬件支持范圍內。 scaling_governor 可以手動修改設置:
echo ondemand > /sys/devices/system/cpu/cpu0/scaling_governor
一般系統啟動默認為performance,支持5種模式,可以通過make menuconfig配置。
目前DVFS支持調頻調壓策略主要就是上面支持的5種:
- userspace(用戶定義的) 使用用戶在/sys 節點scaling_setspeed設置的頻率運行。 最早的 cpufreq 子系統通過 userspace governor 為用戶提供了這種靈活性。系統將變頻策略的決策權交給了用戶態應用程序,并提供了相應的接口供用戶態應用程序調節 CPU 運行頻率使用。 (可以使用Dominik 等人開發了cpufrequtils 工具包 )
- performancecpu(突出性能) 按照支持的最高頻率運行
- ondemand(按需的) 系統負載小時以低頻率運行,系統負載提高時按需提高頻率 userspace是內核態的檢測,效率低。而ondemand正是人們長期以來希望看到的一個完全在內核態下工作并且能夠以更加細粒度的時間間隔對系統負載情況進行采樣分析的governor。
- conservative(保守的) 跟ondemand方式類似, 不同之處在于提高頻率時漸進提高,而ondemand是跳變提高,ondemand比conservative先進,是conservative的改良版本。 ondemand governor 的最初實現是在可選的頻率范圍內調低至下一個可用頻率。這種降頻策略的主導思想是盡量減小對系統性能的負面影響,從而不會使得系統性能在短時間內迅速降低以影響用戶體驗。但是在 ondemand governor 的這種最初實現版本在社區發布后,大量用戶的使用結果表明這種擔心實際上是多余的, ondemand governor在降頻時對于目標頻率的選擇完全可以更加激進。因此最新的 ondemand governor 在降頻時會在所有可選頻率中一次性選擇出可以保證 CPU 工作在 80% 以上負荷的頻率,當然如果沒有任何一個可選頻率滿足要求的話則會選擇 CPU 支持的最低運行頻率。大量用戶的測試結果表明這種新的算法可以在不影響系統性能的前提下做到更高效的節能。在算法改進后, ondemand governor 的名字并沒有改變,而 ondemand governor 最初的實現也保存了下來,并且由于其算法的保守性而得名conservative 。 Ondemand降頻更加激進,conservative降頻比較緩慢保守,事實使用ondemand的效果也是比較好的。
- powersavecpu(省電的) 以支持的最低頻率運行 CPU會固定工作在其支持的最低運行頻率上。因此其和performance這兩種 governors 都屬于靜態 governor ,即在使用它們時 CPU 的運行頻率不會根據系統運行時負載的變化動態作出調整。這兩種 governors 對應的是兩種極端的應用場景,使用 performance governor 體現的是對系統高性能的最大追求,而使用 powersave governor 則是對系統低功耗的最大追求。
- schedutil:通過將自己的調頻策略注冊到hook,在負載發生變化的時候,會調用該hook,此時就可以進行調頻決策或執行調頻動作。前面的調頻策略都是周期采樣計算cpu負載有滯后性,精度也有限,而schedutil可以使用PELT(per entity load tracking)或者WALT(window assist load tracking)準確的計算task的負載。如果支持fast_switch的功能,可以在中斷上下文直接進行調頻。
功耗:performance > ondemand > conservative >powersave
2.3 主要數據結構
2.3.1 驅動相關cpufreq_driver
在include/linux/cpufreq.h中,用于描述cpufreq的驅動,是驅動工程師最關注的結構。如下默認值:
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的名字,需要唯一,因為cpufreq framework允許同時注冊多個driver,用戶可以根據實際情況選擇使用哪個driver。driver的標識,就是name。
- flags,一些flag,具體會在后續的文章中介紹。 init,driver的入口,由cpufreq core在設備枚舉的時候調用,driver需要根據硬件情況,填充policy的內容。
- verify,驗證policy中的內容是否符合硬件要求。它和init接口都是必須實現的接口。
- setpolicy,driver需要提供這個接口,用于設置CPU core動態頻率調整的范圍(即policy)。
- target、target_index,driver需要實現這兩個接口中的一個(target為舊接口,不推薦使用),用于設置CPU core為指定頻率(同時修改為對應的電壓)。target_index()接口底層真正用于設置cpu為指定頻率的接口(同時修改為對應的電壓)
有關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的注冊、注銷。獲取當前所使用的driver名稱,以及該driver的私有數據結構(driver_data字段)。
2.3.2 策略相關cpufreq_policy
linux使用cpufreq policy來抽象cpu設備的調頻調壓功能,用于描述不同的policy,包含頻率表、cpuinfo等各種信息,并且每個policy都會對應某個具體的governor。
min/max frequency,調頻范圍,對于可以自動調頻的CPU而言,只需要這兩個參數就夠了。 current frequency和governor,對于不能自動調頻的CPU,需要governor設置具體的頻率值。下面介紹一下governor。 struct cpufreq_policy不會直接對外提供API。
2.3.3 管理策略cpufreq_governor
不同policy的管理策略,根據使用場景的不同,會有不同的調頻調壓策略。如下一個governor的默認值:
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狀態切換的回調函數。
- show_setspeed、store_setspeed,用于提供sysfs “setspeed” attribute文件的回調函數。
- max_transition_latency,該governor所能容忍的最大頻率切換延遲。
- cpufreq governors主要向具體的governor模塊提供governor的注冊和注銷接口
2.2 初始化流程
2.2.1 governor注冊
cpufreq_register_governor 如果policy中有默認的governor,則調用find_governor,在列表中尋找。cpufreq core定義了一個全局鏈表變量:cpufreq_governor_list,注冊函數首先根據governor的名稱,通過__find_governor()函數查找該governor是否已經被注冊過,如果沒有被注冊過,則把代表該governor的結構體添加到cpufreq_governor_list鏈表中。
系統中可以同時存在多個governor,policy通過cpufreq_policy->governor指針和某個governor相關聯。要想一個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驅動發現注冊
dt_cpufreq_probe()在drivers/cpufreq/cpufreq-dt.c中 系統啟動的時候平臺驅動dt_cpufreq_platdrv,會執行prob函數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注冊的入口,驅動程序通過調用該函數進行初始化,并傳入相關的struct cpufreq_driver,cpufreq_register_driver會調用subsys_interface_register,入參為:
static struct subsys_interface cpufreq_interface = {
.name = "cpufreq",
.subsys = &cpu_subsys,
.add_dev = cpufreq_add_dev,
.remove_dev = cpufreq_remove_dev,
};
最終執行回調函數cpufreq_add_dev。
2.2.3 CPU subsys注冊
kernel將cpu都抽象成device,并抽象出cpu_subsys bus,所有cpu都掛載在這個bus下。每個bus都包含一個struct subsys_private結構的成員p,該結構包括一個interface list成員interfaces和設備鏈表klist_devices。interface list上的一個interface通常用于抽象bus下的一個功能。
cpufreq是CPU device的一類特定功能,也就被抽象為一個subsys interface(kernel使用struct subsys_interface結構表示)即變量cpufreq_interface,, 掛載在interface list下。cpufreq作為一個功能掛載到cpu subsys下后會對相應的所有設備即cpu執行interface.add_dev()操作,表示對subsys_private支持的設備都添加這個功能,在添加這個功能時為每個cpu設備生成具體的policy結構,即struct cpufreq_policy.
上圖涉及cpu初始化,在系統啟動的時候:
//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);//向總線注冊設備
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);
這里可以看到對于多核,都執行了cpufreq_add_dev,會為cpu device創建struct cpufreq_policy結構。 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上線設置
cpufreq_online(cpu)在drivers/cpufreq/cpufreq.c中
?cpufreq_policy_alloc()創建policy節點/sys/devices/system/cpu/cpufreq/*
?cpufreq_driver->init(policy)指向cpufreq_init()
?cpufreq_add_dev_interface()創建sysfs節點的一些可選屬性
?cpufreq_init_policy()初始化policy的governor
cpufreq_driver->init對應cpufreq_init()函數 這個函數會解析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;
}
設置policy的時候,會讀取cpu的頻率表,賦值給policy->min和policy->max。另外各種governor也用到frequency table。
frequency table是CPU core可以正確運行的一組頻率/電壓組合,之所以存在的一個思考點是:table是頻率和電壓之間的一個一一對應的組合,因此cpufreq framework只需要關心頻率,所有的策略都稱做“調頻”策略。而cpufreq driver可以在“調頻”的同時,通過table取出和頻率對應的電壓,進行修改CPU core電壓,實現“調壓”的功能,這簡化了設計。 例如在DTS中:
2.2.5 策略初始化
cpufreq_init_policy(),drivers/cpufreq/cpufreq.c在 使用默認策略初始化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中有默認的governor,則調用find_governor,在列表中尋找。cpufreq core定義了一個全局鏈表變量:cpufreq_governor_list,注冊函數首先根據governor的名稱,通過__find_governor()函數查找該governor是否已經被注冊過,如果沒有被注冊過,則把代表該governor的結構體添加到cpufreq_governor_list鏈表中。
系統中可以同時存在多個governor,policy通過cpufreq_policy->governor指針和某個governor相關聯。要想一個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);
}
這里我們默認使用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,
};
最后調用cpufreq_set_policy(policy, &new_policy);去設置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初始化和啟動的時候會發生:CPUFreq通知 CPUFreq子系統會發出通知的情況有兩種:CPUFreq的策略變化或者CPU運行頻率變化。
在策略變化的過程中,例如cpufreq_set_policy函數中,會發送3次通知:
- CPUFREQ_ADJUST:所有注冊的notifier可以根據硬件或者溫度的情況去修改范圍(即policy->min和policy->max);
- CPUFREQ_INCOMPATIBLE:除非前面的策略設定可能會導致硬件出錯,否則被注冊的notifier不能改變范圍等設定;
- CPUFREQ_NOTIFY:所有注冊的notifier都會被告知新的策略已經被設置。
在頻率變化的過程中,例如__cpufreq_notify_transition函數中,會發送2次通知:
- CPUFREQ_PRECHANGE:準備進行頻率變更;
- CPUFREQ_POSTCHANGE:已經完成頻率變更。
/* notification of the new policy */
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_NOTIFY, new_policy);
cpufreq_policy_notifier_list
cpufreq_register_notifier()函數注冊這個鏈表
3.2.6 設置熱插拔計算機狀態的回調函數
cpuhp_setup_state_nocalls_cpuslocked(): 參數說明:
__cpuhp_setup_state_cpuslocked(
CPUHP_AP_ONLINE_DYN, "cpufreq:online", false,
cpuhp_cpufreq_online,cpuhp_cpufreq_offline, false);
* __cpuhp_setup_state_cpuuslocked—設置熱插拔計算機狀態的回調函數
* @state:要設置的狀態
* @invoke:如果為true,啟動函數將被調用于cpu,cpu state >= @state
* @startup:啟動回調函數
* @teardown: teardown回調函數
* @multi_instance:狀態是為多個實例設置的,然后添加。
2.3 userspace governor
用戶空間監控CPUFreq流程圖
### 2.3.1 用戶接口說明 userspace governor是一種用戶可以自己手動調整自己cpu頻率的governor,即在linux目錄下:/sys/devices/system/cpu/cpu0/cpufreq/,有一個參數scaling_setspeed,是這個governor轉有的,其他governor是不能對其進行讀寫操作的,只有這個governor才能這樣做。
對應底層有處理函數,設置也有處理函數。
2.3.2 配置說明
默認是Performance的策略,我們可以通過make menuconfig選擇,如下:
保存后在.config中可以看到
CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y
在代碼里面搜索這個宏,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 回調函數介紹
cpufreq_gov_userspace對應
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函數和start函數。 cpufreq_userspace_policy_init 申請一個governor_data
policy->governor_data = setspeed;
cpufreq_userspace_policy_start 設置policy的cur頻率
*setspeed = policy->cur;
cpufreq_userspace_policy_limits 就是約束性檢查,如果超過max或者小于min進行重新設定
show_setspeed 就是讀scaling_setspeed-當前cpu頻率
store_setspeed 就是寫scaling_setspeed,可以用戶控制。改變cpu頻率的時候會調用如下函數:
ret = __cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_L);
2.3.4 調頻調壓流程
例如輸入命令:
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()函數在drivers/base/power/opp/core.c中定義 找到opp_table進行調頻調壓,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是要設置的頻率
/* change the rates */
clk_change_rate(top);
top的名字為cpu_core0_mux_clk,節點父子關系為:
armpll1_912m_cpu_clk->cpu_core0_mux_clk->cpu_core0_div_clk->cpu_core0_clk
首先設置armpll1_912m_cpu_clk
clk_change_rate()
core->ops->set_parent(core->hw, core->new_parent_index);
set_parent對應clk_mux_set_parent()函數在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出來是默認值5,需要寫入為4
cpu_core0_div_clk進行了頻率設置
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已經是要設置的值了。
2.4 其他governor
2.4.1 ondemand governor
ondemand governor,最終是通過調頻接口od_dbs_update實現計算負載進行調頻的。
//drivers/cpufreq/cpufreq_ondemand.c
od_dbs_update
od_update
static void od_update(struct cpufreq_policy *policy)
{
unsigned int load = dbs_update(policy);//負載(百分比)(1)
/* Check for frequency increase */
if (load > dbs_data->up_threshold) {//(2)如果負載大于策略設置的閾值,則直接切換到最大頻率
/* 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)按照負載百分比,在頻率范圍內選擇合適頻率
/* 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);//設置頻率
}
(1)計算負載函數: od_dbs_update()核心方法是: 當前負載load = 100 * (time_elapsed - idle_time) / time_elapsed idle_time = 本次idle時間 - 上次idle時間 time_elapsed = 本次總運行時間 - 上次總運行時間 該函數返回使用此policy的各個cpu中的最大負載。
(2)當最大負載大于策略設置的最大閾值時,調用dbs_freq_increase()將頻率設置在最大頻率。
(3)按照負載百分比設置合適頻率 freq_next = min_f + load * (max_f - min_f) / 100;
(4) 表明我們為了進一步節省電力,我們希望在計算出來的新頻率的基礎上,再乘以一個powersave_bias設定的百分比,作為真正的運行頻率,powersave_bias的值從0-1000,每一步代表0.1%
2.4.2 schedutil governor
不同的governor的觸發調頻調壓流程不一樣,這里以schedutil governor為例。 CFS負載變化的時候或者RT、DL任務狀態更新的時候,就會啟動調頻。這幾個scheduler類會調用cpufreq_update_util函數(前面注冊進來的hook函數)觸發schedutil工作。每個cpu最終會回調到sugov_upate_shared或者sugov_upate_single函數中的一個。 由于是從scheduler里直接調用下來的,最終執行調頻切換時,無論是快速路徑觸發的簡單寫寄存器,還是慢速路徑觸發的kthread都不會占用過多時間或者調度開銷。
2.4.3 Interactive governor
Interactive 與Conservative相對,快速提升頻率,緩慢降低頻率
- 優點: 比Ondemand稍強的性能,較快的響應速度
- 缺點: 在不需要時仍然維持較高的頻率,比Ondemand耗電 Interactive X 基于Interactive改進,區分開關屏狀態情景
- 優點:比Interactive省電
- 缺點:穩定性不如Interactive 代碼位置:drivers/cpufreq/cpufreq_interactive.c 首先需要定義一個cpufreq_governor類型的結構體用來描述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,
}
};
后記
本節代碼有點多,不是調試這個可以不用關注代碼,想深入學習還是需要運行起來代碼打點log比較好。
-
電源管理
+關注
關注
115文章
6185瀏覽量
144631 -
cpu
+關注
關注
68文章
10880瀏覽量
212204 -
電壓頻率
+關注
關注
0文章
9瀏覽量
8082
原文標題:電源管理入門-6 CPUFreq
文章出處:【微信號:OS與AUTOSAR研究,微信公眾號:OS與AUTOSAR研究】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論