Linux動態頻率調節系統CPUFreq之二:核心(core)架構與API

上一節中,我們大致地講解了一下CPUFreq在用戶空間的sysfs接口和它的幾個重要的數據結構,同時也提到,CPUFreq子系統把一些公共的代碼邏輯組織在一起,構成了CPUFreq的核心部分,這些公共邏輯向CPUFreq和其它內核模塊提供了必要的API,像cpufreq_governor、cpufreq_driver等模塊通過這些API來完成一個完整的CPUFreq體系。這一節我們就來討論一下核心架構的代碼架構以及如何使用這些公共的API接口。

核心部分的代碼都在:/drivers/cpufreq/cpufreq.c中,本系列文章我使用的內核版本是3.10.0.

1. CPUFreq子系統的初始化

先看看具體的代碼:

<code>static int __init cpufreq_core_init(void)
{
int cpu;

if (cpufreq_disabled())
return -ENODEV;

for_each_possible_cpu(cpu) {
per_cpu(cpufreq_policy_cpu, cpu) = -1;
init_rwsem(&per_cpu(cpu_policy_rwsem, cpu));
}

cpufreq_global_kobject = kobject_create_and_add("cpufreq", &cpu_subsys.dev_root->kobj);
BUG_ON(!cpufreq_global_kobject);
register_syscore_ops(&cpufreq_syscore_ops);

return 0;
}
core_initcall(cpufreq_core_init);
/<code>

可見,在系統的啟動階段,經由initcall機制,cpufreq_core_init被調用,由它來完成核心部分的初始化工作,其中:

cpufreq_policy_cpu 是一個per_cpu變量,在smp的系統下,每個cpu可以有自己獨立的調頻policy,也可以所有的cpu都是用一種policy,這時候就有可能出現其中一個cpu管理者某個policy,而其它cpu因為也使用同一個policy,這些cpu的policy的就交由那個管理cpu代管,這個per_cpu變量就是用來記錄各個cpu的policy實際上是由那個cpu進行管理的。初始化時都被初始化為-1了,代表現在還沒有開始進行policy的管理。

接下來的kobject_create_and_add函數在/sys/devices/system/cpu這個節點下建立了一個cpufreq節點,該節點的下面以後會用來放置當前governor的一些配置參數。參數cpu_subsys是內核的一個全局變量,是由更早期的初始化時初始化的,代碼在drivers/base/cpu.c中:

<code>struct bus_type cpu_subsys = {
.name = "cpu",
.dev_name = "cpu",
};
EXPORT_SYMBOL_GPL(cpu_subsys);


void __init cpu_dev_init(void)
{
if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
panic("Failed to register CPU subsystem");

cpu_dev_register_generic();
}
/<code>

這將會建立一根cpu總線,總線下掛著系統中所有的cpu,cpu總線設備的根目錄就位於:/sys/devices/system/cpu,同時,/sys/bus下也會出現一個cpu的總線節點。。cpu總線設備的根目錄下會依次出現cpu0,cpu1,...... cpux節點,每個cpu對應其中的一個設備節點。CPUFreq子系統利用這個cpu_subsys來獲取系統中的cpu設備,並在這些cpu設備下面建立相應的cpufreq對象,這個我們在後面再討論。

這樣看來,cpufreq子系統的初始化其實沒有做什麼重要的事情,只是初始化了幾個per_cpu變量和建立了一個cpufreq文件節點。下圖是初始化過程的序列圖:

Linux動態頻率調節系統CPUFreq之二:核心(core)架構與API

圖 1.1 核心層初始化

2. 註冊cpufreq_governor

系統中可以同時存在多個governor策略,一個policy通過cpufreq_policy結構中的governor指針和某個governor相關聯。要想一個governor被policy使用,首先要把該governor註冊到cpufreq的核心中,我們可以通過核心層提供的API來完成註冊:

<code>int cpufreq_register_governor(struct cpufreq_governor *governor)
{
int err;
......

governor->initialized = 0;
err = -EBUSY;
if (__find_governor(governor->name) == NULL) {
err = 0;
list_add(&governor->governor_list, &cpufreq_governor_list);
}

......
return err;
}
/<code>

核心層定義了一個全局鏈表變量:cpufreq_governor_list,註冊函數首先根據governor的名稱,通過__find_governor()函數查找該governor是否已經被註冊過,如果沒有被註冊過,則把代表該governor的結構體添加到cpufreq_governor_list鏈表中。在上一篇中我們提到,目前的內核版本提供了5種governor供我們使用,我們可以通過內核的配置項來選擇需要編譯的governor,同時需要指定一個默認的governor。在cpufreq.h中,將會根據配置項的選擇,把CPUFREQ_DEFAULT_GOVERNOR宏指向默認governor結構體變量的地址,在註冊cpufreq_driver的階段需要使用這個宏來設定系統默認使用的governor。

3. 註冊一個cpufreq_driver驅動

與governor不同,系統中只會存在一個cpufreq_driver驅動,根據上一篇Linux動態頻率調節系統CPUFreq之一:概述的介紹,cpufreq_driver是平臺相關的,負責最終實施頻率的調整動作,而選擇工作頻率的策略是由governor完成的。所以,系統中只需要註冊一個cpufreq_driver即可,它只負責知道如何控制該平臺的時鐘系統,從而設定由governor確定的工作頻率。註冊cpufreq_driver驅動會觸發cpufreq核心的一系列額外的初始化動作,第一節所說的核心初始化工作非常簡單,實際上,更多的初始化動作在註冊cpufreq_driver階段完成。核心提供了一個API:cpufreq_register_driver來完成註冊工作。下面我們分析一下這個函數的工作過程:

<code>int cpufreq_register_driver(struct cpufreq_driver *driver_data)
{
......

if (cpufreq_disabled())
return -ENODEV;

if (!driver_data || !driver_data->verify || !driver_data->init ||
((!driver_data->setpolicy) && (!driver_data->target)))
return -EINVAL;
/<code>

該API只有一個參數:一個cpufreq_driver指針,driver_data,該結構事先在驅動的代碼中定義,調用該API時作為參數傳入。函數先判斷系統目前是否禁止了調頻功能,然後檢查cpufreq_driver的幾個回調函數是否被實現,由代碼可以看出,verify和init回調函數必須要實現,而setpolicy和target回調則至少要被實現其中的一個。這幾個回調的作用請參考本系列的第一篇文章。接下來:

<code> write_lock_irqsave(&cpufreq_driver_lock, flags);
if (cpufreq_driver) {
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
return -EBUSY;
}
cpufreq_driver = driver_data;
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
/<code>

檢查全局變量cpufreq_driver是否已經被賦值,如果沒有,則傳入的參數被賦值給全局變量cpufreq_driver,從而保證了系統中只會註冊一個cpufreq_driver驅動。然後:

<code>        ret = subsys_interface_register(&cpufreq_interface);

......
......

register_hotcpu_notifier(&cpufreq_cpu_notifier);
/<code>

通過subsys_interface_register給每一個cpu建立一個cpufreq_policy,最後註冊cpu hot plug通知,以便在cpu hot plug的時候,能夠動態地處理各個cpu policy之間的關係(比如遷移負責管理的cpu等等)。這裡要重點討論一下subsys_interface_register的過程,回到第一節的內容,我們知道初始化階段,cpu_subsys被建立,從而每個cpu都會在cpu總線設備下建立一個屬於自己的設備:sys/devices/system/cpu/cpux。subsys_interface_register負責在cpu_subsys子系統的子設備下面註冊公共的接口。我們看看參數cpufreq_interface的定義:

<code>static struct subsys_interface cpufreq_interface = {
.name = "cpufreq",
.subsys = &cpu_subsys,
.add_dev = cpufreq_add_dev,
.remove_dev = cpufreq_remove_dev,
};
/<code>

subsys_interface_register函數的代碼我就不再展開了,它的大致作用就是:遍歷子系統下面的每一個子設備,然後用這個子設備作為參數,調用cpufrq_interface結構的add_dev回調函數,這裡的回調函數被指向了cpufreq_add_dev,它的具體工作方式我們在下一節中討論。

driver註冊完成後,驅動被保存在全局變量cpufreq_driver中,供核心層使用,同時,每個cpu也會建立自己的policy策略,governor也開始工作,實時地監控著cpu的負載並計算合適的工作頻率,然後通過driver調整真正的工作頻率。下圖是cpufreq_driver註冊過程的序列圖:

Linux動態頻率調節系統CPUFreq之二:核心(core)架構與API

4. 為每個cpu建立頻率調整策略(policy)

為每個cpu建立頻率調整策略實在註冊cpufreq_driver階段的subsys_interface_registe函數中完成的,上一節已經提到,該函數最終會調用cpufreq_add_dev回調函數,現在展開這個函數分析一下:

因為subsys_interface_registe會枚舉各個cpu設備,不管該cpu處於offline還是online狀態,cpufreq_add_dev都會被調用,所以函數的一開始,判斷如果cpu處於offline狀態,直接返回。

<code>static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
{
......

if (cpu_is_offline(cpu))
return 0;
/<code>

如果是smp系統,本cpu的policy可能和其他cpu共同使用同一個policy,並委託另一個叫做管理cpu的cpu進行管理,下面的代碼判斷這種情況,如果已經委託別的cpu管理,則直接返回,核心層定義了另一個per_cpu變量:cpufreq_cpu_data,用來保存各個cpu所使用的cpufreq_policy結構的指針,cpufreq_cpu_get函數實際上就是通過這個per_cpu變量,獲取該指針,如果該指針非0,代表該cpu已經建立好了它自身的policy(可能是在他之前的管理cpu建立policy期間一併建立的)。

<code>        policy = cpufreq_cpu_get(cpu);
if (unlikely(policy)) {
cpufreq_cpu_put(policy);
return 0;
}

/<code>

因為cpu hot plug期間,cpufreq_add_dev也會被調用,下面的代碼片段檢測該cpu之前是否被hot-unpluged過,如果是,找到其中一個相關的cpu(這些相關的cpu都委託給同一個託管它cpu進行管理,調用cpufreq_add_policy_cpu函數,該函數只是簡單地建立一個cpufreq鏈接,鏈接到管理cpu的cpufreq節點。

<code>       for_each_online_cpu(sibling) {
struct cpufreq_policy *cp = per_cpu(cpufreq_cpu_data, sibling);
if (cp && cpumask_test_cpu(cpu, cp->related_cpus)) {
read_unlock_irqrestore(&cpufreq_driver_lock, flags);
return cpufreq_add_policy_cpu(cpu, sibling, dev);
}
}
/<code>

當這是系統初始化階段第一次調用cpufreq_add_dev時(subsys_interface_register枚舉到的第一個cpu,通常就是cpu0),cpufreq_cpu_data應該為NULL,所以我們要為這樣的cpu分配一個cpufreq_policy結構,並初始化該policy所管理的cpu,包括online的cpus字段和online+offline的cpu_related字段,並把自己設置為這個policy的管理cpu,使用默認governor初始化policy->governor字段,同時把自己加入到online的cpus字段中:

<code>        policy = kzalloc(sizeof(struct cpufreq_policy), GFP_KERNEL);
if (!policy)
goto nomem_out;

if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL))
goto err_free_policy;

if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL))
goto err_free_cpumask;

policy->cpu = cpu;
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
cpumask_copy(policy->cpus, cpumask_of(cpu));

/* Initially set CPU itself as the policy_cpu */
per_cpu(cpufreq_policy_cpu, cpu) = cpu;

/<code>

接下來初始化一個供kobject系統註銷時使用的同步變量,初始化一個workqueue,某些時候不能馬上執行對該policy的更新操作,可以使用該workqueue來延遲執行。

<code>        init_completion(&policy->kobj_unregister);
INIT_WORK(&policy->update, handle_update);
/<code>

接著,調用cpufreq_driver的init回調,進一步初始化該policy:

<code>       ret = cpufreq_driver->init(policy);
if (ret) {
pr_debug("initialization failed\\n");
goto err_set_policy_cpu;
}
/<code>

在上述驅動的初始化內部,應該完成以下工作:

  • 設定該cpu的最大和最小工作頻率
  • 設定該policy的最大和最小工作頻率
  • 設定該policy可供調節的頻率檔位
  • 設定cpu調節頻率時的延遲時間特性
  • 該policy可以管理的cpu個數(policy->cpus)

繼續:

<code>        /* related cpus should atleast have policy->cpus */ 

cpumask_or(policy->related_cpus, policy->related_cpus, policy->cpus);
/<code>

註釋已經寫的很清楚了,把online的cpu加到代表online+offline的related字段中。接著,剔除offline的cpu:

<code>        cpumask_and(policy->cpus, policy->cpus, cpu_online_mask);
/<code>

然後,發出CPUFREQ_START通知:

<code>        blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_START, policy);
/<code>

如果是hot-plug加入的cpu,找出它上次使用的governor:

<code>#ifdef CONFIG_HOTPLUG_CPU
gov = __find_governor(per_cpu(cpufreq_cpu_governor, cpu));
if (gov) {
policy->governor = gov;
pr_debug("Restoring governor %s for cpu %d\\n",
policy->governor->name, cpu);
}
#endif
/<code>

最後,建立cpu設備下的sysfs文件節點:cpufreq,它的完整路徑是:/sys/devices/system/cpu/cpux/cpufreq,同時,在它的下面,相應的sysfs節點也同時被建立,節點的內容請參考本系列的第一篇文章:Linux動態頻率調節系統CPUFreq之一:概述:

<code>       ret = cpufreq_add_dev_interface(cpu, policy, dev);
/<code>

至此,一個cpu的policy建立完成,它的頻率限制條件、使用的governor策略,sysfs文件節點都已經建立完成。需要注意點是,系統中有多少個cpu,cpufreq_add_dev函數就會被調用多少次,最後,每個cpu都會建立自己的policy,當然,也有可能只有部分cpu建立了真正的policy,而其它cpu則委託這些cpu進行policy的管理,關於這一點,一開始讀代碼的時候可能有點困擾,為了搞清楚他們之間的關係,我們再跟入cpufreq_add_dev_interface函數看看:

<code>static int cpufreq_add_dev_interface(unsigned int cpu,
struct cpufreq_policy *policy,
struct device *dev)
{
......

/* prepare interface data */
ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq,
&dev->kobj, "cpufreq");
......

/* set up files for this cpu device */
drv_attr = cpufreq_driver->attr;
while ((drv_attr) && (*drv_attr)) {
ret = sysfs_create_file(&policy->kobj, &((*drv_attr)->attr));
if (ret)
goto err_out_kobj_put;
drv_attr++;
}
/<code>

函數的一開始,建立cpufreq文件節點,然後在它的下面再建立一系列節點,用戶可以通過這些文件節點控制該policy的一些參數。這不是我們的重點,我們看下面這一段代碼:

<code>        for_each_cpu(j, policy->cpus) {
per_cpu(cpufreq_cpu_data, j) = policy;
per_cpu(cpufreq_policy_cpu, j) = policy->cpu;
}
/<code>

前面的代碼已經設定了該policy所管理的online cpu:policy->cpus,通過兩個per_cpu變量,這裡把每個online cpu的policy都設置為了本cpu(管理cpu)的policy,並且把所有online的cpu的管理cpu也指定為了本cpu。接下來,cpufreq_add_dev_symlink被調用,所有policy->cpus指定的cpu會建立一個cpufreq鏈接,指向本cpu(管理cpu)的真實cpufreq節點:

<code>        ret = cpufreq_add_dev_symlink(cpu, policy);
/<code>

注意,假如這時的cpu是cpu0,也就是說,其它cpu的cpufreq_add_dev還沒有被調用,但是在cpufreq_cpu_data中,與之對應的policy指針已經被賦值為cpu0所對應的policy,這樣,回到cpufreq_add_dev函數的開頭部分,當接下其它被認為使用cpu0託管他們的policy的cpu也會進入cpufreq_add_dev函數,但是,因為cpufreq_cpu_data中對應的policy已經在cpu0的建立階段被賦值,所以這些cpu他們不會走完所有的流程,在函數的開頭的判斷部分,判斷cpufreq_cpu_data中cpu對應的policy已經被賦值,就直接返回了。

接著往下看cpufreq_add_dev_interface的代碼:

<code>        memcpy(&new_policy, policy, sizeof(struct cpufreq_policy));
/* assure that the starting sequence is run in __cpufreq_set_policy */
policy->governor = NULL;

/* set default policy */
ret = __cpufreq_set_policy(policy, &new_policy);
policy->user_policy.policy = policy->policy;
policy->user_policy.governor = policy->governor;
/<code>

通過__cpufreq_set_policy函數,最終使得該policy正式生效。到這裡,每個cpu的policy已經建立完畢,並正式開始工作。關於__cpufreq_set_policy的代碼這裡就不展開了,我只給出它的序列圖:

Linux動態頻率調節系統CPUFreq之二:核心(core)架構與API

5. 其它API

cpufreq的核心層除了提供上面幾節討論的註冊governor,註冊cpufreq_driver等API外,還提供了其他一些輔助的API,以方便其它模塊的使用。

  • int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
  • int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);

以上兩個API用於註冊和註銷cpufreq系統的通知消息,第二個參數可以選擇通知的類型,可以有以下兩種類型:

  • CPUFREQ_TRANSITION_NOTIFIER 收到頻率變更通知
  • CPUFREQ_POLICY_NOTIFIER 收到policy更新通知
  • int cpufreq_driver_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation);
  • int __cpufreq_driver_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation);

以上兩個API用來設置cpu的工作頻率,區別在於cpufreq_driver_target是帶鎖的版本,而__cpufreq_driver_target是不帶鎖的版本,如果確定是在governor的上下文中,使用不帶鎖的版本,否則需要使用帶鎖的版本。

  • void cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min, unsigned int max);

這個API用來檢查並重新設定policy的最大和最小頻率。

  • int cpufreq_update_policy(unsigned int cpu);

這個API用來觸發cpufreq核心進行policy的更新操作。


分享到:


相關文章: