在linux中,內(nèi)核提供了一套框架模型來(lái)完成CPU頻率的動(dòng)態(tài)調(diào)節(jié),以達(dá)到省電的目的。 這就是所謂的CPUFreq系統(tǒng)。
我們先從CPUFreq提供的sysfs接口入手,直觀地看看它提供了那些功能。以下是我的電腦輸出的結(jié)果:
droidphone@990:~$ cd /sys/devices/system/cpudroidphone@990:/sys/devices/system/cpu$ lscpu0 cpu3 cpu6 cpuidle offline power releasecpu1 cpu4 cpu7 kernel_max online PResent ueventcpu2 cpu5 cpufreq modalias possible probe所有與CPUFreq相關(guān)的sysfs接口都位于:/sys/devices/system/cpu下面,我們可以看到,8個(gè)cpu分別建立了一個(gè)自己的目錄,從cpu0到cpu7,我們?cè)倏纯磑ffline和online以及present的內(nèi)容:
droidphone@990:/sys/devices/system/cpu$ cat online0-7droidphone@990:/sys/devices/system/cpu$ cat offline8-15droidphone@990:/sys/devices/system/cpu$ cat present0-7droidphone@990:/sys/devices/system/cpu$online代表目前正在工作的cpu,輸出顯示編號(hào)為0-7這8個(gè)cpu在工作,offline代表目前被關(guān)掉的cpu,present則表示主板上已經(jīng)安裝的cpu,由輸出可以看到,我的主板可以安裝16個(gè)cpu(因?yàn)閕ntel的超線程技術(shù),其實(shí)物理上只是8個(gè)),第8-15號(hào)cpu處于關(guān)閉狀態(tài)(實(shí)際上不存在,因?yàn)閜resent只有0-7)。
接著往下看:
droidphone@990:/sys/devices/system/cpu/cpu0$ lscache cpuidle microcode power thermal_throttle ueventcpufreq crash_notes node0 subsystem topologydroidphone@990:/sys/devices/system/cpu/cpu0$ cd cpufreq/droidphone@990:/sys/devices/system/cpu/cpu0/cpufreq$ lsaffected_cpus related_cpus scaling_max_freqbios_limit scaling_available_frequencies scaling_min_freqcpuinfo_cur_freq scaling_available_governors scaling_setspeedcpuinfo_max_freq scaling_cur_freq statscpuinfo_min_freq scaling_drivercpuinfo_transition_latency scaling_governordroidphone@990:/sys/devices/system/cpu/cpu0/cpufreq$在我的電腦上,部分的值如下: cpuinfo_cur_freq: 1600000
cpuinfo_max_freq: 3401000
cpuinfo_min_freq: 1600000
scaling_cur_freq: 1600000
scaling_max_freq: 3401000
scaling_min_freq: 1600000 所以,我的cpu0的最低運(yùn)行頻率是1.6GHz,最高是3.4GHz,目前正在運(yùn)行的頻率是1.6GHz,前綴cpuinfo代表的是cpu硬件上支持的頻率,而scaling前綴代表的是可以通過(guò)CPUFreq系統(tǒng)用軟件進(jìn)行調(diào)節(jié)時(shí)所支持的頻率。cpuinfo_cur_freq代表通過(guò)硬件實(shí)際上讀到的頻率值,而scaling_cur_freq則是軟件當(dāng)前的設(shè)置值,多數(shù)情況下這兩個(gè)值是一致的,但是也有可能因?yàn)橛布脑颍形⑿〉牟町悺caling_available_frequencies會(huì)輸出當(dāng)前軟件支持的頻率值,看看我的cpu支持那些頻率:
droidphone@990:/sys/devices/system/cpu/cpu0/cpufreq$ cat scaling_available_frequencies 3401000 3400000 3000000 2800000 2600000 2400000 2200000 2000000 1800000 1600000 droidphone@990:/sys/devices/system/cpu/cpu0/cpufreq$Oh,從1.6GHz到3.4GHz,一共支持10擋的頻率可供選擇。scaling_available_governors則會(huì)輸出當(dāng)前可供選擇的頻率調(diào)節(jié)策略:
conservative ondemand userspace powersave performance一共有5中策略供我們選擇,那么當(dāng)前系統(tǒng)選用那種策略?讓我們看看:
dong@dong-990:/sys/devices/system/cpu/cpu0/cpufreq$ cat scaling_governorondemandOK,我的系統(tǒng)當(dāng)前選擇ondemand這種策略,這種策略的主要思想是:只要cpu的負(fù)載超過(guò)某一個(gè)閥值,cpu的頻率會(huì)立刻提升至最高,然后再根據(jù)實(shí)際情況降到合適的水平。詳細(xì)的情況我們留在后面的章節(jié)中討論。scaling_driver則會(huì)輸出當(dāng)前使用哪一個(gè)驅(qū)動(dòng)來(lái)設(shè)置cpu的工作頻率。 當(dāng)我們選擇userspace作為我們的調(diào)頻governor時(shí),我們可以通過(guò)scaling_setspeed手工設(shè)置需要的頻率。powersave則簡(jiǎn)單地使用最低的工作頻率進(jìn)行運(yùn)行,而performance則一直選擇最高的頻率進(jìn)行運(yùn)行。
通過(guò)上一節(jié)的介紹,我們可以大致梳理出CPUFreq系統(tǒng)的構(gòu)成和工作方式。首先,CPU的硬件特性決定了這個(gè)CPU的最高和最低工作頻率,所有的頻率調(diào)整數(shù)值都必須在這個(gè)范圍內(nèi),它們用cpuinfo_xxx_freq來(lái)表示。然后,我們可以在這個(gè)范圍內(nèi)再次定義出一個(gè)軟件的調(diào)節(jié)范圍,它們用scaling_xxx_freq來(lái)表示,同時(shí),根據(jù)具體的硬件平臺(tái)的不同,我們還需要提供一個(gè)頻率表,這個(gè)頻率表規(guī)定了cpu可以工作的頻率值,當(dāng)然這些頻率值必須要在cpuinfo_xxx_freq的范圍內(nèi)。有了這些頻率信息,CPUFreq系統(tǒng)就可以根據(jù)當(dāng)前cpu的負(fù)載輕重狀況,合理地從頻率表中選擇一個(gè)合適的頻率供cpu使用,已達(dá)到節(jié)能的目的。至于如何選擇頻率表中的頻率,這個(gè)要由不同的governor來(lái)實(shí)現(xiàn),目前的內(nèi)核版本提供了5種governor供我們選擇。選擇好適當(dāng)?shù)念l率以后,具體的頻率調(diào)節(jié)工作就交由scaling_driver來(lái)完成。CPUFreq系統(tǒng)把一些公共的邏輯和接口代碼抽象出來(lái),這些代碼與平臺(tái)無(wú)關(guān),也與具體的調(diào)頻策略無(wú)關(guān),內(nèi)核的文檔把它稱為CPUFreq Core(/Documents/cpufreq/core.txt)。另外一部分,與實(shí)際的調(diào)頻策略相關(guān)的部分被稱作cpufreq_policy,cpufreq_policy又是由頻率信息和具體的governor組成,governor才是具體策略的實(shí)現(xiàn)者,當(dāng)然governor需要我們提供必要的頻率信息,governor的實(shí)現(xiàn)最好能做到平臺(tái)無(wú)關(guān),與平臺(tái)相關(guān)的代碼用cpufreq_driver表述,它完成實(shí)際的頻率調(diào)節(jié)工作。最后,如果其他內(nèi)核模塊需要在頻率調(diào)節(jié)的過(guò)程中得到通知消息,則可以通過(guò)cpufreq notifiers來(lái)完成。由此,我們可以總結(jié)出CPUFreq系統(tǒng)的軟件結(jié)構(gòu)如下:
一種調(diào)頻策略的各種限制條件的組合稱之為policy,代碼中用cpufreq_policy這一數(shù)據(jù)結(jié)構(gòu)來(lái)表示:
struct cpufreq_policy { cpumask_var_t cpus; cpumask_var_t related_cpus; unsigned int shared_type; unsigned int cpu; unsigned int last_cpu; struct cpufreq_cpuinfo cpuinfo; unsigned int min; /* in kHz */ unsigned int max; /* in kHz */ unsigned int cur; unsigned int policy; struct cpufreq_governor *governor; void *governor_data; struct work_struct update; struct cpufreq_real_policy user_policy; struct kobject kobj; struct completion kobj_unregister;};其中的各個(gè)字段的解釋如下:
cpus和related_cpus 這兩個(gè)都是cpumask_var_t變量,cpus表示的是這一policy控制之下的所有還出于online狀態(tài)的cpu,而related_cpus則是online和offline兩者的合集。主要是用于多個(gè)cpu使用同一種policy的情況,實(shí)際上,我們平常見(jiàn)到的大多數(shù)系統(tǒng)中都是這種情況:所有的cpu同時(shí)使用同一種policy。我們需要related_cpus變量指出這個(gè)policy所管理的所有cpu編號(hào)。cpu和last_cpu 雖然一種policy可以同時(shí)用于多個(gè)cpu,但是通常一種policy只會(huì)由其中的一個(gè)cpu進(jìn)行管理,cpu變量用于記錄用于管理該policy的cpu編號(hào),而last_cpu則是上一次管理該policy的cpu編號(hào)(因?yàn)楣芾韕olicy的cpu可能會(huì)被plug out,這時(shí)候就要把管理工作遷移到另一個(gè)cpu上)。cpuinfo 保存cpu硬件所能支持的最大和最小的頻率以及切換延遲信息。min/max/cur 該policy下的可使用的最小頻率,最大頻率和當(dāng)前頻率。policy 該變量可以取以下兩個(gè)值:CPUFREQ_POLICY_POWERSAVE和CPUFREQ_POLICY_PERFORMANCE,該變量只有當(dāng)調(diào)頻驅(qū)動(dòng)支持setpolicy回調(diào)函數(shù)的時(shí)候有效,這時(shí)候由驅(qū)動(dòng)根據(jù)policy變量的值來(lái)決定系統(tǒng)的工作頻率或狀態(tài)。如果調(diào)頻驅(qū)動(dòng)(cpufreq_driver)支持target回調(diào),則頻率由相應(yīng)的governor來(lái)決定。governor和governor_data 指向該policy當(dāng)前使用的cpufreq_governor結(jié)構(gòu)和它的上下文數(shù)據(jù)。governor是實(shí)現(xiàn)該policy的關(guān)鍵所在,調(diào)頻策略的邏輯由governor實(shí)現(xiàn)。update 有時(shí)在中斷上下文中需要更新policy,需要利用該工作隊(duì)列把實(shí)際的工作移到稍后的進(jìn)程上下文中執(zhí)行。user_policy 有時(shí)候因?yàn)樘厥獾脑蛐枰薷膒olicy的參數(shù),比如溫度過(guò)高時(shí),最大可允許的運(yùn)行頻率可能會(huì)被降低,為了在適當(dāng)?shù)臅r(shí)候恢復(fù)原有的運(yùn)行參數(shù),需要使用user_policy保存原始的參數(shù)(min,max,policy,governor)。kobj 該policy在sysfs中對(duì)應(yīng)的kobj的對(duì)象。所謂的governor,我把它翻譯成:調(diào)節(jié)器。governor負(fù)責(zé)檢測(cè)cpu的使用狀況,從而在可用的范圍中選擇一個(gè)合適的頻率,代碼中它用cpufreq_governor結(jié)構(gòu)來(lái)表示:
struct cpufreq_governor { char name[CPUFREQ_NAME_LEN]; int initialized; int (*governor) (struct cpufreq_policy *policy, unsigned int event); ssize_t (*show_setspeed) (struct cpufreq_policy *policy, char *buf); int (*store_setspeed) (struct cpufreq_policy *policy, unsigned int freq); unsigned int max_transition_latency; /* HW must be able to switch to next freq faster than this value in nano secs or we will fallback to performance governor */ struct list_head governor_list; struct module *owner;};其中的各個(gè)字段的解釋如下:
name 該governor的名字。initialized 初始化標(biāo)志。governor 指向一個(gè)回調(diào)函數(shù),CPUFreq Core會(huì)在不同的階段調(diào)用該回調(diào)函數(shù),用于該governor的啟動(dòng)、停止、初始化、退出動(dòng)作。list_head 所有注冊(cè)的governor都會(huì)利用該字段鏈接在一個(gè)全局鏈表中,以供系統(tǒng)查詢和使用。上一節(jié)提到的gonvernor只是負(fù)責(zé)計(jì)算并提出合適的頻率,但是頻率的設(shè)定工作是平臺(tái)相關(guān)的,這需要cpufreq_driver驅(qū)動(dòng)來(lái)完成,cpufreq_driver的結(jié)構(gòu)如下:
struct cpufreq_driver { struct module *owner; char name[CPUFREQ_NAME_LEN]; u8 flags; bool have_governor_per_policy; /* 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, unsigned int target_freq, unsigned int relation); /* should be defined, if possible */ unsigned int (*get) (unsigned int cpu); /* optional */ unsigned int (*getavg) (struct cpufreq_policy *policy, unsigned int cpu); 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;};相關(guān)的字段的意義解釋如下:
name 該頻率驅(qū)動(dòng)的名字。init 回調(diào)函數(shù),該回調(diào)函數(shù)必須實(shí)現(xiàn),CPUFreq Core會(huì)通過(guò)該回調(diào)函數(shù)對(duì)該驅(qū)動(dòng)進(jìn)行必要的初始化工作。verify 回調(diào)函數(shù),該回調(diào)函數(shù)必須實(shí)現(xiàn),CPUFreq Core會(huì)通過(guò)該回調(diào)函數(shù)檢查policy的參數(shù)是否被驅(qū)動(dòng)支持。setpolicy/target 回調(diào)函數(shù),驅(qū)動(dòng)必須實(shí)現(xiàn)這兩個(gè)函數(shù)中的其中一個(gè),如果不支持通過(guò)governor選擇合適的運(yùn)行頻率,則實(shí)現(xiàn)setpolicy回調(diào)函數(shù),這樣系統(tǒng)只能支持CPUFREQ_POLICY_POWERSAVE和CPUFREQ_POLICY_PERFORMANCE這兩種工作策略。反之,實(shí)現(xiàn)target回調(diào)函數(shù),通過(guò)target回調(diào)設(shè)定 - - governor所需要的頻率。get 回調(diào)函數(shù),用于獲取cpu當(dāng)前的工作頻率。getavg 回調(diào)函數(shù),用于獲取cpu當(dāng)前的平均工作頻率。CPUFreq的通知系統(tǒng)使用了內(nèi)核的標(biāo)準(zhǔn)通知接口。它對(duì)外提供了兩個(gè)通知事件:policy通知和transition通知。
policy通知用于通知其它模塊cpu的policy需要改變,每次policy改變時(shí),該通知鏈上的回調(diào)將會(huì)用不同的事件參數(shù)被調(diào)用3次,分別是:
CPUFREQ_ADJUST 只要有需要,所有的被通知者可以在此時(shí)修改policy的限制信息,比如溫控系統(tǒng)可能會(huì)修改在大允許運(yùn)行的頻率。CPUFREQ_INCOMPATIBLE 只是為了避免硬件錯(cuò)誤的情況下,可以在該通知中修改policy的限制信息。CPUFREQ_NOTIFY 真正切換policy前,該通知會(huì)發(fā)往所有的被通知者。 transition通知鏈用于在驅(qū)動(dòng)實(shí)施調(diào)整cpu的頻率時(shí),用于通知相關(guān)的注冊(cè)者。每次調(diào)整頻率時(shí),該通知會(huì)發(fā)出兩次通知事件:CPUFREQ_PRECHANGE 調(diào)整前的通知。CPUFREQ_POSTCHANGE 完成調(diào)整后的通知。 當(dāng)檢測(cè)到因系統(tǒng)進(jìn)入suspend而造成頻率被改變時(shí),以下通知消息會(huì)被發(fā)出:CPUFREQ_RESUMECHANGE上面我們大致地講解了一下CPUFreq在用戶空間的sysfs接口和它的幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu),同時(shí)也提到,CPUFreq子系統(tǒng)把一些公共的代碼邏輯組織在一起,構(gòu)成了CPUFreq的核心部分,這些公共邏輯向CPUFreq和其它內(nèi)核模塊提供了必要的API,像cpufreq_governor、cpufreq_driver等模塊通過(guò)這些API來(lái)完成一個(gè)完整的CPUFreq體系。這一節(jié)我們就來(lái)討論一下核心架構(gòu)的代碼架構(gòu)以及如何使用這些公共的API接口。
核心部分的代碼都在:/drivers/cpufreq/cpufreq.c中,本系列文章我使用的內(nèi)核版本是3.10.0.
先看看具體的代碼:
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);可見(jiàn),在系統(tǒng)的啟動(dòng)階段,經(jīng)由initcall機(jī)制,cpufreq_core_init被調(diào)用,由它來(lái)完成核心部分的初始化工作,其中: cpufreq_policy_cpu 是一個(gè)per_cpu變量,在smp的系統(tǒng)下,每個(gè)cpu可以有自己獨(dú)立的調(diào)頻policy,也可以所有的cpu都是用一種policy,這時(shí)候就有可能出現(xiàn)其中一個(gè)cpu管理著某個(gè)policy,而其它c(diǎn)pu因?yàn)橐彩褂猛粋€(gè)policy,這些cpu的policy的就交由那個(gè)管理cpu代管,這個(gè)per_cpu變量就是用來(lái)記錄各個(gè)cpu的policy實(shí)際上是由那個(gè)cpu進(jìn)行管理的。初始化時(shí)都被初始化為-1了,代表現(xiàn)在還沒(méi)有開(kāi)始進(jìn)行policy的管理。
接下來(lái)的kobject_create_and_add函數(shù)在/sys/devices/system/cpu這個(gè)節(jié)點(diǎn)下建立了一個(gè)cpufreq節(jié)點(diǎn),該節(jié)點(diǎn)的下面以后會(huì)用來(lái)放置當(dāng)前governor的一些配置參數(shù)。參數(shù)cpu_subsys是內(nèi)核的一個(gè)全局變量,是由更早期的初始化時(shí)初始化的,代碼在drivers/base/cpu.c中:
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();}這將會(huì)建立一根cpu總線,總線下掛著系統(tǒng)中所有的cpu,cpu總線設(shè)備的根目錄就位于:/sys/devices/system/cpu,同時(shí),/sys/bus下也會(huì)出現(xiàn)一個(gè)cpu的總線節(jié)點(diǎn)。cpu總線設(shè)備的根目錄下會(huì)依次出現(xiàn)cpu0,cpu1,…… cpux節(jié)點(diǎn),每個(gè)cpu對(duì)應(yīng)其中的一個(gè)設(shè)備節(jié)點(diǎn)。CPUFreq子系統(tǒng)利用這個(gè)cpu_subsys來(lái)獲取系統(tǒng)中的cpu設(shè)備,并在這些cpu設(shè)備下面建立相應(yīng)的cpufreq對(duì)象,這個(gè)我們?cè)诤竺嬖儆懻摗?這樣看來(lái),cpufreq子系統(tǒng)的初始化其實(shí)沒(méi)有做什么重要的事情,只是初始化了幾個(gè)per_cpu變量和建立了一個(gè)cpufreq文件節(jié)點(diǎn)。下圖是初始化過(guò)程的序列圖:
圖 1.1 核心層初始化系統(tǒng)中可以同時(shí)存在多個(gè)governor策略,一個(gè)policy通過(guò)cpufreq_policy結(jié)構(gòu)中的governor指針和某個(gè)governor相關(guān)聯(lián)。要想一個(gè)governor被policy使用,首先要把該governor注冊(cè)到cpufreq的核心中,我們可以通過(guò)核心層提供的API來(lái)完成注冊(cè):
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;}核心層定義了一個(gè)全局鏈表變量:cpufreq_governor_list,注冊(cè)函數(shù)首先根據(jù)governor的名稱,通過(guò)__find_governor()函數(shù)查找該governor是否已經(jīng)被注冊(cè)過(guò),如果沒(méi)有被注冊(cè)過(guò),則把代表該governor的結(jié)構(gòu)體添加到cpufreq_governor_list鏈表中。在上一篇中我們提到,目前的內(nèi)核版本提供了5種governor供我們使用,我們可以通過(guò)內(nèi)核的配置項(xiàng)來(lái)選擇需要編譯的governor,同時(shí)需要指定一個(gè)默認(rèn)的governor。在cpufreq.h中,將會(huì)根據(jù)配置項(xiàng)的選擇,把CPUFREQ_DEFAULT_GOVERNOR宏指向默認(rèn)governor結(jié)構(gòu)體變量的地址,在注冊(cè)cpufreq_driver的階段需要使用這個(gè)宏來(lái)設(shè)定系統(tǒng)默認(rèn)使用的governor。
與governor不同,系統(tǒng)中只會(huì)存在一個(gè)cpufreq_driver驅(qū)動(dòng),根據(jù)上一篇Linux動(dòng)態(tài)頻率調(diào)節(jié)系統(tǒng)CPUFreq之一:概述的介紹,cpufreq_driver是平臺(tái)相關(guān)的,負(fù)責(zé)最終實(shí)施頻率的調(diào)整動(dòng)作,而選擇工作頻率的策略是由governor完成的。所以,系統(tǒng)中只需要注冊(cè)一個(gè)cpufreq_driver即可,它只負(fù)責(zé)知道如何控制該平臺(tái)的時(shí)鐘系統(tǒng),從而設(shè)定由governor確定的工作頻率。注冊(cè)cpufreq_driver驅(qū)動(dòng)會(huì)觸發(fā)cpufreq核心的一系列額外的初始化動(dòng)作,第一節(jié)所說(shuō)的核心初始化工作非常簡(jiǎn)單,實(shí)際上,更多的初始化動(dòng)作在注冊(cè)cpufreq_driver階段完成。核心提供了一個(gè)API:cpufreq_register_driver來(lái)完成注冊(cè)工作。下面我們分析一下這個(gè)函數(shù)的工作過(guò)程:
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;該API只有一個(gè)參數(shù):一個(gè)cpufreq_driver指針,driver_data,該結(jié)構(gòu)事先在驅(qū)動(dòng)的代碼中定義,調(diào)用該API時(shí)作為參數(shù)傳入。函數(shù)先判斷系統(tǒng)目前是否禁止了調(diào)頻功能,然后檢查cpufreq_driver的幾個(gè)回調(diào)函數(shù)是否被實(shí)現(xiàn),由代碼可以看出,verify和init回調(diào)函數(shù)必須要實(shí)現(xiàn),而setpolicy和target回調(diào)則至少要被實(shí)現(xiàn)其中的一個(gè)。這幾個(gè)回調(diào)的作用請(qǐng)參考本系列的第一篇文章。接下來(lái):
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);檢查全局變量cpufreq_driver是否已經(jīng)被賦值,如果沒(méi)有,則傳入的參數(shù)被賦值給全局變量cpufreq_driver,從而保證了系統(tǒng)中只會(huì)注冊(cè)一個(gè)cpufreq_driver驅(qū)動(dòng)。然后:
ret = subsys_interface_register(&cpufreq_interface); ...... ...... register_hotcpu_notifier(&cpufreq_cpu_notifier);通過(guò)subsys_interface_register給每一個(gè)cpu建立一個(gè)cpufreq_policy,最后注冊(cè)cpu hot plug通知,以便在cpu hot plug的時(shí)候,能夠動(dòng)態(tài)地處理各個(gè)cpu policy之間的關(guān)系(比如遷移負(fù)責(zé)管理的cpu等等)。這里要重點(diǎn)討論一下subsys_interface_register的過(guò)程,回到第一節(jié)的內(nèi)容,我們知道初始化階段,cpu_subsys被建立,從而每個(gè)cpu都會(huì)在cpu總線設(shè)備下建立一個(gè)屬于自己的設(shè)備:sys/devices/system/cpu/cpux。subsys_interface_register負(fù)責(zé)在cpu_subsys子系統(tǒng)的子設(shè)備下面注冊(cè)公共的接口。我們看看參數(shù)cpufreq_interface的定義:
static struct subsys_interface cpufreq_interface = { .name = "cpufreq", .subsys = &cpu_subsys, .add_dev = cpufreq_add_dev, .remove_dev = cpufreq_remove_dev,};subsys_interface_register函數(shù)的代碼我就不再展開(kāi)了,它的大致作用就是:遍歷子系統(tǒng)下面的每一個(gè)子設(shè)備,然后用這個(gè)子設(shè)備作為參數(shù),調(diào)用cpufrq_interface結(jié)構(gòu)的add_dev回調(diào)函數(shù),這里的回調(diào)函數(shù)被指向了cpufreq_add_dev,它的具體工作方式我們?cè)谙乱还?jié)中討論。 driver注冊(cè)完成后,驅(qū)動(dòng)被保存在全局變量cpufreq_driver中,供核心層使用,同時(shí),每個(gè)cpu也會(huì)建立自己的policy策略,governor也開(kāi)始工作,實(shí)時(shí)地監(jiān)控著cpu的負(fù)載并計(jì)算合適的工作頻率,然后通過(guò)driver調(diào)整真正的工作頻率。下圖是cpufreq_driver注冊(cè)過(guò)程的序列圖:
圖 3.1 cpufreq_driver的注冊(cè)過(guò)程
為每個(gè)cpu建立頻率調(diào)整策略實(shí)在注冊(cè)cpufreq_driver階段的subsys_interface_registe函數(shù)中完成的,上一節(jié)已經(jīng)提到,該函數(shù)最終會(huì)調(diào)用cpufreq_add_dev回調(diào)函數(shù),現(xiàn)在展開(kāi)這個(gè)函數(shù)分析一下:
因?yàn)閟ubsys_interface_registe會(huì)枚舉各個(gè)cpu設(shè)備,不管該cpu處于offline還是online狀態(tài),cpufreq_add_dev都會(huì)被調(diào)用,所以函數(shù)的一開(kāi)始,判斷如果cpu處于offline狀態(tài),直接返回。
static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif){ ...... if (cpu_is_offline(cpu)) return 0;如果是smp系統(tǒng),本cpu的policy可能和其他cpu共同使用同一個(gè)policy,并委托另一個(gè)叫做管理cpu的cpu進(jìn)行管理,下面的代碼判斷這種情況,如果已經(jīng)委托別的cpu管理,則直接返回,核心層定義了另一個(gè)per_cpu變量:cpufreq_cpu_data,用來(lái)保存各個(gè)cpu所使用的cpufreq_policy結(jié)構(gòu)的指針,cpufreq_cpu_get函數(shù)實(shí)際上就是通過(guò)這個(gè)per_cpu變量,獲取該指針,如果該指針?lè)?,代表該cpu已經(jīng)建立好了它自身的policy(可能是在他之前的管理cpu建立policy期間一并建立的)。
policy = cpufreq_cpu_get(cpu); if (unlikely(policy)) { cpufreq_cpu_put(policy); return 0; }因?yàn)閏pu hot plug期間,cpufreq_add_dev也會(huì)被調(diào)用,下面的代碼片段檢測(cè)該cpu之前是否被hot-unpluged過(guò),如果是,找到其中一個(gè)相關(guān)的cpu(這些相關(guān)的cpu都委托給同一個(gè)托管它c(diǎn)pu進(jìn)行管理,調(diào)用cpufreq_add_policy_cpu函數(shù),該函數(shù)只是簡(jiǎn)單地建立一個(gè)cpufreq鏈接,鏈接到管理cpu的cpufreq節(jié)點(diǎn)。
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); } }當(dāng)這是系統(tǒng)初始化階段第一次調(diào)用cpufreq_add_dev時(shí)(subsys_interface_register枚舉到的第一個(gè)cpu,通常就是cpu0),cpufreq_cpu_data應(yīng)該為NULL,所以我們要為這樣的cpu分配一個(gè)cpufreq_policy結(jié)構(gòu),并初始化該policy所管理的cpu,包括online的cpus字段和online+offline的cpu_related字段,并把自己設(shè)置為這個(gè)policy的管理cpu,使用默認(rèn)governor初始化policy->governor字段,同時(shí)吧自己加入到online的cpus字段中:
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;接下來(lái)初始化一個(gè)供kobject系統(tǒng)注銷時(shí)使用的同步變量,初始化一個(gè)workqueue,某些時(shí)候不能馬上執(zhí)行對(duì)該policy的更新操作,可以使用該workqueue來(lái)延遲執(zhí)行。
init_completion(&policy->kobj_unregister); INIT_WORK(&policy->update, handle_update);接著,調(diào)用cpufreq_driver的init回調(diào),進(jìn)一步初始化該policy:
ret = cpufreq_driver->init(policy); if (ret) { pr_debug("initialization failed/n"); goto err_set_policy_cpu; }在上述驅(qū)動(dòng)的初始化內(nèi)部,應(yīng)該完成以下工作:
設(shè)定該cpu的最大和最小工作頻率設(shè)定該policy的最大和最小工作頻率設(shè)定該policy可供調(diào)節(jié)的頻率檔位設(shè)定cpu調(diào)節(jié)頻率時(shí)的延遲時(shí)間特性該policy可以管理的cpu個(gè)數(shù)(policy->cpus) 繼續(xù): /* related cpus should atleast have policy->cpus */ cpumask_or(policy->related_cpus, policy->related_cpus, policy->cpus);注釋已經(jīng)寫的很清楚了,把online的cpu加到代表online+offline的related字段中。接著,剔除offline的cpu:
cpumask_and(policy->cpus, policy->cpus, cpu_online_mask);然后,發(fā)出CPUFREQ_START通知:
blocking_notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_START, policy);如果是hot-plug加入的cpu,找出它上次使用的governor:
#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最后,建立cpu設(shè)備下的sysfs文件節(jié)點(diǎn):cpufreq,它的完整路徑是:/sys/devices/system/cpu/cpux/cpufreq,同時(shí),在他的下面,相應(yīng)的sysfs節(jié)點(diǎn)也同時(shí)被建立,節(jié)點(diǎn)的內(nèi)容請(qǐng)參考本系列的第一篇文章:Linux動(dòng)態(tài)頻率調(diào)節(jié)系統(tǒng)CPUFreq之一:概述:
ret = cpufreq_add_dev_interface(cpu, policy, dev);至此,一個(gè)cpu的policy建立完成,它的頻率限制條件、使用的governor策略,sysfs文件節(jié)點(diǎn)都已經(jīng)建立完成。需要注意點(diǎn)是,系統(tǒng)中有多少個(gè)cpu,cpufreq_add_dev函數(shù)就會(huì)被調(diào)用多少次,最后,每個(gè)cpu都會(huì)建立自己的policy,當(dāng)然,也有可能只有部分cpu建立了真正的policy,而其它c(diǎn)pu則委托這些cpu進(jìn)行policy的管理,關(guān)于這一點(diǎn),一開(kāi)始讀代碼的時(shí)候可能有點(diǎn)困擾,為了搞清楚他們之間的關(guān)系,我們?cè)俑隿pufreq_add_dev_interface函數(shù)看看:
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++; }函數(shù)的一開(kāi)始,建立cpufreq文件節(jié)點(diǎn),然后在它的下面再建立一系列節(jié)點(diǎn),用戶可以通過(guò)這些文件節(jié)點(diǎn)控制該policy的一些參數(shù)。這不是我們的重點(diǎn),我們看下面這一段代碼:
for_each_cpu(j, policy->cpus) { per_cpu(cpufreq_cpu_data, j) = policy; per_cpu(cpufreq_policy_cpu, j) = policy->cpu; }前面的代碼已經(jīng)設(shè)定了該policy所管理的online cpu:policy->cpus,通過(guò)兩個(gè)per_cpu變量,這里把每個(gè)online cpu的policy都設(shè)置為了本cpu(管理cpu)的policy,并且把所有online的cpu的管理cpu也指定為了本cpu。接下來(lái),cpufreq_add_dev_symlink被調(diào)用,所有policy->cpus指定的cpu會(huì)建立一個(gè)cpufreq鏈接,指向本cpu(管理cpu)的真實(shí)cpufreq節(jié)點(diǎn):
ret = cpufreq_add_dev_symlink(cpu, policy);注意,假如這時(shí)的cpu是cpu0,也就是說(shuō),其它c(diǎn)pu的cpufreq_add_dev還沒(méi)有被調(diào)用,但是在cpufreq_cpu_data中,與之對(duì)應(yīng)的policy指針已經(jīng)被賦值為cpu0所對(duì)應(yīng)的policy,這樣,回到cpufreq_add_dev函數(shù)的開(kāi)頭部分,當(dāng)接下其它被認(rèn)為使用cpu0托管他們的policy的cpu也會(huì)進(jìn)入cpufreq_add_dev函數(shù),但是,因?yàn)閏pufreq_cpu_data中對(duì)應(yīng)的policy已經(jīng)在cpu0的建立階段被賦值,所以這些cpu他們不會(huì)走完所有的流程,在函數(shù)的開(kāi)頭的判斷部分,判斷cpufreq_cpu_data中cpu對(duì)應(yīng)的policy已經(jīng)被賦值,就直接返回了。 接著往下看cpufreq_add_dev_interface的代碼:
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;通過(guò)__cpufreq_set_policy函數(shù),最終使得該policy正式生效。到這里,每個(gè)cpu的policy已經(jīng)建立完畢,并正式開(kāi)始工作。關(guān)于__cpufreq_set_policy的代碼這里就不展開(kāi)了,我只給出它的序列圖:
圖 4.1 設(shè)置一個(gè)cpufreq_policy
cpufreq的核心層除了提供上面幾節(jié)討論的注冊(cè)governor,注冊(cè)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);以上兩個(gè)API用于注冊(cè)和注銷cpufreq系統(tǒng)的通知消息,第二個(gè)參數(shù)可以選擇通知的類型,可以有以下兩種類型:
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);以上兩個(gè)API用來(lái)設(shè)置cpu的工作頻率,區(qū)別在于cpufreq_driver_target是帶鎖的版本,而__cpufreq_driver_target是不帶鎖的版本,如果確定是在governor的上下文中,使用不帶鎖的版本,否則需要使用帶鎖的版本。
void cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min, unsigned int max);這個(gè)API用來(lái)檢查并重新設(shè)定policy的最大和最小頻率。
int cpufreq_update_policy(unsigned int cpu);這個(gè)API用來(lái)觸發(fā)cpufreq核心進(jìn)行policy的更新操作。
在上一篇文章中,介紹了cpufreq的core層,core提供了cpufreq系統(tǒng)的初始化,公共數(shù)據(jù)結(jié)構(gòu)的建立以及對(duì)cpufreq中其它子部件提供注冊(cè)功能。core的最核心功能是對(duì)policy的管理,一個(gè)policy通過(guò)cpufreq_policy結(jié)構(gòu)中的governor字段,和某個(gè)governor相關(guān)聯(lián),本章的內(nèi)容正是要對(duì)governor進(jìn)行討論。 通過(guò)前面兩篇文章的介紹,我們知道,governor的作用是:檢測(cè)系統(tǒng)的負(fù)載狀況,然后根據(jù)當(dāng)前的負(fù)載,選擇出某個(gè)可供使用的工作頻率,然后把該工作頻率傳遞給cpufreq_driver,完成頻率的動(dòng)態(tài)調(diào)節(jié)。內(nèi)核默認(rèn)提供了5種governor供我們使用,在之前的內(nèi)核版本中,每種governor幾乎是獨(dú)立的代碼,它們各自用自己的方式實(shí)現(xiàn)對(duì)系統(tǒng)的負(fù)載進(jìn)行監(jiān)測(cè),很多時(shí)候,檢測(cè)的邏輯其實(shí)是很相似的,各個(gè)governor最大的不同之處其實(shí)是根據(jù)檢測(cè)的結(jié)果,選擇合適頻率的策略。所以,為了減少代碼的重復(fù),在我現(xiàn)在分析的內(nèi)核版本中(3.10.0),一些公共的邏輯代碼被單獨(dú)抽象出來(lái),單獨(dú)用一個(gè)文件來(lái)實(shí)現(xiàn):/drivers/cpufreq/cpufreq_governor.c,而各個(gè)具體的governor則分別有自己的代碼文件,如:cpufreq_ondemand.c,cpufreq_performance.c。下面我們先從公共部分討論。
cpu_dbs_common_info 該結(jié)構(gòu)把對(duì)計(jì)算cpu負(fù)載需要使用到的一些輔助變量整合在了一起,通常,每個(gè)cpu都需要一個(gè)cpu_dbs_common_info結(jié)構(gòu)體,該結(jié)構(gòu)體中的成員會(huì)在governor的生命周期期間進(jìn)行傳遞,以用于統(tǒng)計(jì)當(dāng)前cpu的負(fù)載,它的定義如下:
/* Per cpu structures */struct cpu_dbs_common_info { int cpu; u64 prev_cpu_idle; u64 prev_cpu_wall; u64 prev_cpu_nice; struct cpufreq_policy *cur_policy; struct delayed_work work; struct mutex timer_mutex; ktime_t time_stamp;};cpu 與該結(jié)構(gòu)體相關(guān)聯(lián)的cpu編號(hào)。prev_cpu_idle 上一次統(tǒng)計(jì)時(shí)刻該cpu停留在idle狀態(tài)的總時(shí)間。prev_cpu_wall 上一次統(tǒng)計(jì)時(shí)刻對(duì)應(yīng)的總工作時(shí)間。cur_policy 指向該cpu所使用的cpufreq_policy結(jié)構(gòu)。work 工作隊(duì)列,該工作隊(duì)列會(huì)被定期地觸發(fā),然后定期地進(jìn)行負(fù)載的更新和統(tǒng)計(jì)工作。 dbs縮寫,實(shí)際是:demand based switching,通常,因?yàn)閏pu_dbs_common_info只包含了經(jīng)過(guò)抽象后的公共部分,所以,各個(gè)governor會(huì)自己定義的一個(gè)包含cpu_dbs_common_info的自定義結(jié)構(gòu),例如對(duì)于ondemand,他會(huì)定義:struct od_cpu_dbs_info_s { struct cpu_dbs_common_info cdbs; struct cpufreq_frequency_table *freq_table; unsigned int freq_lo; unsigned int freq_lo_jiffies; unsigned int freq_hi_jiffies; unsigned int rate_mult; unsigned int sample_type:1;};而對(duì)于Conservative,他的定義如下:
struct cs_cpu_dbs_info_s { struct cpu_dbs_common_info cdbs; unsigned int down_skip; unsigned int requested_freq; unsigned int enable:1;};把它理解為類似于C++語(yǔ)言的基類和子類的概念就是了。
common_dbs_data 各個(gè)獨(dú)立的governor,需要和governor的公共層交互,需要實(shí)現(xiàn)一套公共的接口,這個(gè)接口由common_dbs_data結(jié)構(gòu)來(lái)提供:
struct common_dbs_data { /* Common across governors */ #define GOV_ONDEMAND 0 #define GOV_CONSERVATIVE 1 int governor; struct attribute_group *attr_group_gov_sys; /* one governor - system */ struct attribute_group *attr_group_gov_pol; /* one governor - policy */ /* Common data for platforms that don't set have_governor_per_policy */ struct dbs_data *gdbs_data; struct cpu_dbs_common_info *(*get_cpu_cdbs)(int cpu); void *(*get_cpu_dbs_info_s)(int cpu); void (*gov_dbs_timer)(struct work_struct *work); void (*gov_check_cpu)(int cpu, unsigned int load); int (*init)(struct dbs_data *dbs_data); void (*exit)(struct dbs_data *dbs_data); /* Governor specific ops, see below */ void *gov_ops;};主要的字段意義如下:
governor 因?yàn)閛ndemand和conservative的實(shí)現(xiàn)部分有很多相似的地方,用該字段做一區(qū)分,可以設(shè)置為GOV_ONDEMAND或GOV_CONSERVATIVE的其中之一。attr_group_gov_sys 該公共的sysfs屬性組。attr_group_gov_pol 各policy使用的屬性組,有時(shí)候多個(gè)policy會(huì)使用同一個(gè)governor算法。gdbs_data 通常,當(dāng)沒(méi)有設(shè)置have_governor_per_policy時(shí),表示所有的policy使用了同一種 - - governor,該字段指向該governor的dbs_data結(jié)構(gòu)。get_cpu_cdbs 回調(diào)函數(shù),公共層用它取得對(duì)應(yīng)cpu的cpu_dbs_common_info結(jié)構(gòu)指針。get_cpu_dbs_info_s 回調(diào)函數(shù),公共層用它取得對(duì)應(yīng)cpu的cpu_dbs_common_info_s的派生結(jié)構(gòu)指針,例如:od_cpu_dbs_info_s,cs_cpu_dbs_info_s。gov_dbs_timer 前面說(shuō)過(guò),cpu_dbs_common_info_s結(jié)構(gòu)中有一個(gè)工作隊(duì)列,該回調(diào)通常用作工作隊(duì)列的工作函數(shù)。gov_check_cpu 計(jì)算cpu負(fù)載的回調(diào)函數(shù),通常會(huì)直接調(diào)用公共層提供的dbs_check_cpu函數(shù)完成實(shí)際的計(jì)算工作。init 初始化回調(diào),用于完成該governor的一些額外的初始化工作。exit 回調(diào)函數(shù),governor被移除時(shí)調(diào)用。gov_ops 各個(gè)governor可以用該指針定義各自特有的一些操作接口。 dbs_data 該結(jié)構(gòu)體通常由governor的公共層代碼在governor的初始化階段動(dòng)態(tài)創(chuàng)建,該結(jié)構(gòu)的一個(gè)最重要的字段就是cdata:一個(gè)common_dbs_data結(jié)構(gòu)指針,另外,該結(jié)構(gòu)還包含一些定義governor工作方式的一些調(diào)節(jié)參數(shù)。該結(jié)構(gòu)的詳細(xì)定義如下:struct dbs_data { struct common_dbs_data *cdata; unsigned int min_sampling_rate; int usage_count; void *tuners; /* dbs_mutex protects dbs_enable in governor start/stop */ struct mutex mutex;};幾個(gè)主要的字段:
cdata 一個(gè)common_dbs_data結(jié)構(gòu)指針,通常由具體governor的實(shí)現(xiàn)部分定義好,然后作為參數(shù),通過(guò)公共層的API:cpufreq_governor_dbs,傳遞到公共層,cpufreq_governor_dbs函數(shù)在創(chuàng)建好dbs_data結(jié)構(gòu)后,把該指針賦值給該字段。min_sampling_rate 用于記錄統(tǒng)計(jì)cpu負(fù)載的采樣周期。usage_count 當(dāng)沒(méi)有設(shè)置have_governor_per_policy時(shí),意味著所有的policy采用同一個(gè)governor,該字段就是用來(lái)統(tǒng)計(jì)目前該governor被多少個(gè)policy引用。tuners 指向governor的調(diào)節(jié)參數(shù)結(jié)構(gòu),不同的governor可以定義自己的tuner結(jié)構(gòu),公共層代碼會(huì)在governor的初始化階段調(diào)用common_dbs_data結(jié)構(gòu)的init回調(diào)函數(shù),governor的實(shí)現(xiàn)可以在init回調(diào)中初始化tuners字段。 如果設(shè)置了have_governor_per_policy,每個(gè)policy擁有各自獨(dú)立的governor,也就是說(shuō),擁有獨(dú)立的dbs_data結(jié)構(gòu),它會(huì)記錄在cpufreq_policy結(jié)構(gòu)的governor_data字段中,否則,如果沒(méi)有設(shè)置have_governor_per_policy,多個(gè)policy共享一個(gè)governor,和同一個(gè)dbs_data結(jié)構(gòu)關(guān)聯(lián),此時(shí),dbs_data被賦值在common_dbs_data結(jié)構(gòu)的gdbs_data字段中。cpufreq_governor 這個(gè)結(jié)構(gòu)在本系列文章的第一篇已經(jīng)介紹過(guò)了,請(qǐng)參看Linux動(dòng)態(tài)頻率調(diào)節(jié)系統(tǒng)CPUFreq之一:概述。幾個(gè)數(shù)據(jù)結(jié)構(gòu)的關(guān)系如下圖所示:
圖 1.1 governor的數(shù)據(jù)結(jié)構(gòu)關(guān)系
下面我們以ondemand這個(gè)系統(tǒng)已經(jīng)實(shí)現(xiàn)的governor為例,說(shuō)明一下如何實(shí)現(xiàn)一個(gè)governor。具體的代碼請(qǐng)參看:/drivers/cpufreq/cpufreq_ondemand.c。
要實(shí)現(xiàn)一個(gè)governor,首先要定義一個(gè)cpufreq_governor結(jié)構(gòu),對(duì)于ondeman來(lái)說(shuō),它的定義如下:
struct cpufreq_governor cpufreq_gov_ondemand = { .name = "ondemand", .governor = od_cpufreq_governor_dbs, .max_transition_latency = TRANSITION_LATENCY_LIMIT, .owner = THIS_MODULE,};其中,governor是這個(gè)結(jié)構(gòu)的核心字段,cpufreq_governor注冊(cè)后,cpufreq的核心層通過(guò)該字段操縱這個(gè)governor的行為,包括:初始化、啟動(dòng)、退出等工作。現(xiàn)在,該字段被設(shè)置為od_cpufreq_governor_dbs,我們看看它的實(shí)現(xiàn):
static int od_cpufreq_governor_dbs(struct cpufreq_policy *policy, unsigned int event){ return cpufreq_governor_dbs(policy, &od_dbs_cdata, event);}只是簡(jiǎn)單地調(diào)用了governor的公共層提供的API:cpufreq_governor_dbs,關(guān)于這個(gè)API,我們?cè)诤竺鏁?huì)逐一進(jìn)行展開(kāi),這里我們注意到參數(shù):&od_dbs_cdata,正是我們前面討論過(guò)得common_dbs_data結(jié)構(gòu),作為和governor公共層的接口,在這里它的定義如下:
static struct common_dbs_data od_dbs_cdata = { .governor = GOV_ONDEMAND, .attr_group_gov_sys = &od_attr_group_gov_sys, .attr_group_gov_pol = &od_attr_group_gov_pol, .get_cpu_cdbs = get_cpu_cdbs, .get_cpu_dbs_info_s = get_cpu_dbs_info_s, .gov_dbs_timer = od_dbs_timer, .gov_check_cpu = od_check_cpu, .gov_ops = &od_ops, .init = od_init, .exit = od_exit,};這里先介紹一下get_cpu_cdbs和get_cpu_dbs_info_s這兩個(gè)回調(diào),前面介紹cpu_dbs_common_info_s結(jié)構(gòu)的時(shí)候已經(jīng)說(shuō)過(guò),各個(gè)governor需要定義一個(gè)cpu_dbs_common_info_s結(jié)構(gòu)的派生結(jié)構(gòu),對(duì)于ondemand來(lái)說(shuō),這個(gè)派生結(jié)構(gòu)是:od_cpu_dbs_info_s。兩個(gè)回調(diào)函數(shù)分別用來(lái)獲得基類和派生類這兩個(gè)結(jié)構(gòu)的指針。我們先看看od_cpu_dbs_info_s是如何定義的:
static DEFINE_PER_CPU(struct od_cpu_dbs_info_s, od_cpu_dbs_info);沒(méi)錯(cuò),它被定義為了一個(gè)per_cpu變量,也就是說(shuō),每個(gè)cpu擁有各自獨(dú)立的od_cpu_dbs_info_s,這很正常,因?yàn)槊總€(gè)cpu需要的實(shí)時(shí)負(fù)載是不一樣的,需要獨(dú)立的上下文變量來(lái)進(jìn)行負(fù)載的統(tǒng)計(jì)。前面也已經(jīng)列出了od_cpu_dbs_info_s的聲明,他的第一個(gè)字段cdbs就是一個(gè)cpu_dbs_common_info_s結(jié)構(gòu)。內(nèi)核為我們提供了一個(gè)輔助宏來(lái)幫助我們定義get_cpu_cdbs和get_cpu_dbs_info_s這兩個(gè)回調(diào)函數(shù):
#define define_get_cpu_dbs_routines(_dbs_info) /static struct cpu_dbs_common_info *get_cpu_cdbs(int cpu) /{ / return &per_cpu(_dbs_info, cpu).cdbs; /} / /static void *get_cpu_dbs_info_s(int cpu) /{ / return &per_cpu(_dbs_info, cpu); /}所以,在cpufreq_ondemand.c中,我們只要簡(jiǎn)單地使用上述的宏即可定義這兩個(gè)回調(diào):
define_get_cpu_dbs_routines(od_cpu_dbs_info);經(jīng)過(guò)上述這一系列的定義以后,governor的公共層即可通過(guò)這兩個(gè)回調(diào)獲取各個(gè)cpu所對(duì)應(yīng)的cpu_dbs_common_info_s和od_cpu_dbs_info_s的結(jié)構(gòu)指針,用來(lái)記錄本次統(tǒng)計(jì)周期的一些上下文參數(shù)(idle時(shí)間和運(yùn)行時(shí)間等等)。
當(dāng)一個(gè)governor被policy選定后,核心層會(huì)通過(guò)__cpufreq_set_policy函數(shù)對(duì)該cpu的policy進(jìn)行設(shè)定,參看 Linux動(dòng)態(tài)頻率調(diào)節(jié)系統(tǒng)CPUFreq之二:核心(core)架構(gòu)與API中的第4節(jié)和圖4.1。如果policy認(rèn)為這是一個(gè)新的governor(和原來(lái)使用的舊的governor不相同),policy會(huì)通過(guò)__cpufreq_governor函數(shù),并傳遞CPUFREQ_GOV_POLICY_INIT參數(shù),而__cpufreq_governor函數(shù)實(shí)際上是調(diào)用cpufreq_governor結(jié)構(gòu)中的governor回調(diào)函數(shù),在第2節(jié)中我們已經(jīng)知道,這個(gè)回調(diào)最后會(huì)進(jìn)入governor公共API:cpufreq_governor_dbs,下面是它收到CPUFREQ_GOV_POLICY_INIT參數(shù)時(shí),經(jīng)過(guò)簡(jiǎn)化后的代碼片段:
case CPUFREQ_GOV_POLICY_INIT: ...... dbs_data = kzalloc(sizeof(*dbs_data), GFP_KERNEL); ...... dbs_data->cdata = cdata; dbs_data->usage_count = 1; rc = cdata->init(dbs_data); ...... rc = sysfs_create_group(get_governor_parent_kobj(policy), get_sysfs_attr(dbs_data)); ...... policy->governor_data = dbs_data; ...... /* Bring kernel and HW constraints together */ dbs_data->min_sampling_rate = max(dbs_data->min_sampling_rate, MIN_LATENCY_MULTIPLIER * latency); set_sampling_rate(dbs_data, max(dbs_data->min_sampling_rate, latency * LATENCY_MULTIPLIER)); if ((cdata->governor == GOV_CONSERVATIVE) && (!policy->governor->initialized)) { struct cs_ops *cs_ops = dbs_data->cdata->gov_ops; cpufreq_register_notifier(cs_ops->notifier_block, CPUFREQ_TRANSITION_NOTIFIER); } if (!have_governor_per_policy()) cdata->gdbs_data = dbs_data; return 0;首先,它會(huì)給這個(gè)policy分配一個(gè)dbs_data實(shí)例,然后把通過(guò)參數(shù)cdata傳入的common_dbs_data指針,賦值給它的cdata字段,這樣,policy就可以通過(guò)該字段獲得governor的操作接口(通過(guò)cdata的一系列回調(diào)函數(shù))。然后,調(diào)用cdata的init回調(diào)函數(shù),對(duì)這個(gè)governor做進(jìn)一步的初始化工作,對(duì)于ondemand來(lái)說(shuō),init回調(diào)的實(shí)際執(zhí)行函數(shù)是:od_init,主要是完成和governor相關(guān)的一些調(diào)節(jié)參數(shù)的初始化,然后把初始化好的od_dbs_tuners結(jié)構(gòu)指針賦值到dbs_data的tuners字段中,它的詳細(xì)代碼這里就不貼出了。接著,通過(guò)sysfs_create_group函數(shù),建立該governor在sysfs中的節(jié)點(diǎn),以后我們就可以通過(guò)這些節(jié)點(diǎn)對(duì)該governor的算法邏輯進(jìn)行微調(diào),ondemand在我的電腦中,建立了以下這些節(jié)點(diǎn)(sys/devices/system/cpu/cpufreq/ondemand): sampling_rate; io_is_busy; up_threshold; sampling_down_factor; ignore_nice; powersave_bias; sampling_rate_min;
繼續(xù),把初始化好的dbs_data結(jié)構(gòu)賦值給policy的governor_data字段,以方便以后的訪問(wèn)。最后是通過(guò)set_sampling_rate設(shè)置governor的采樣周期,如果還有設(shè)置have_governor_per_policy,把dbs_data結(jié)構(gòu)指針賦值給cdata結(jié)構(gòu)的gdbs_data字段,至此,governor的初始化工作完成,下面是整個(gè)過(guò)程的序列圖:
圖 3.1 governor的初始化
核心層會(huì)通過(guò)__cpufreq_set_policy函數(shù),通過(guò)CPUFREQ_GOV_POLICY_INIT參數(shù),在公共層的API:cpufreq_governor_dbs中,完成了對(duì)governor的初始化工作,緊接著,__cpufreq_set_policy會(huì)通過(guò)CPUFREQ_GOV_START參數(shù),和初始化governor的流程一樣,最終會(huì)到達(dá)cpufreq_governor_dbs函數(shù)中,我們看看它是如何啟動(dòng)一個(gè)governor的:
case CPUFREQ_GOV_START: if (!policy->cur) return -EINVAL; mutex_lock(&dbs_data->mutex); for_each_cpu(j, policy->cpus) { struct cpu_dbs_common_info *j_cdbs = dbs_data->cdata->get_cpu_cdbs(j); j_cdbs->cpu = j; j_cdbs->cur_policy = policy; j_cdbs->prev_cpu_idle = get_cpu_idle_time(j, &j_cdbs->prev_cpu_wall, io_busy); if (ignore_nice) j_cdbs->prev_cpu_nice = kcpustat_cpu(j).cpustat[CPUTIME_NICE]; mutex_init(&j_cdbs->timer_mutex); INIT_DEFERRABLE_WORK(&j_cdbs->work, dbs_data->cdata->gov_dbs_timer); }首先,遍歷使用該policy的所有的處于online狀態(tài)的cpu,針對(duì)每一個(gè)cpu,做以下動(dòng)作:
取出該cpu相關(guān)聯(lián)的cpu_dbs_common_info結(jié)構(gòu)指針,之前已經(jīng)討論過(guò),governor定義了一個(gè)per_cpu變量來(lái)定義各個(gè)cpu所對(duì)應(yīng)的cpu_dbs_common_info結(jié)構(gòu),通過(guò)common_dbs_data結(jié)構(gòu)的回調(diào)函數(shù)可以獲取該結(jié)構(gòu)的指針。
初始化cpu_dbs_common_info結(jié)構(gòu)的cpu,cur_policy,prev_cpu_idle,prev_cpu_wall,prev_cpu_nice字段,其中,prev_cpu_idle,prev_cpu_wall這兩個(gè)字段會(huì)被以后的負(fù)載計(jì)算所使用。
為每個(gè)cpu初始化一個(gè)工作隊(duì)列,工作隊(duì)列的執(zhí)行函數(shù)是common_dbs_data結(jié)構(gòu)中的gov_dbs_timer字段所指向的回調(diào)函數(shù),對(duì)于ondemand來(lái)說(shuō),該函數(shù)是:od_dbs_timer。這個(gè)工作隊(duì)列會(huì)被按照設(shè)定好的采樣率定期地被喚醒,進(jìn)行cpu負(fù)載的統(tǒng)計(jì)工作。然后,記錄目前的時(shí)間戳,調(diào)度初始化好的工作隊(duì)列在稍后某個(gè)時(shí)間點(diǎn)運(yùn)行:
/* Initiate timer time stamp */ cpu_cdbs->time_stamp = ktime_get(); gov_queue_work(dbs_data, policy, delay_for_sampling_rate(sampling_rate), true);下圖表達(dá)了啟動(dòng)一個(gè)governor的過(guò)程:
圖 4.1 啟動(dòng)一個(gè)governor
工作隊(duì)列被調(diào)度執(zhí)行后,會(huì)在工作隊(duì)列的執(zhí)行函數(shù)中進(jìn)行cpu負(fù)載的統(tǒng)計(jì)工作,這個(gè)我們?cè)谙乱还?jié)中討論。
上一節(jié)我們提到,核心層啟動(dòng)一個(gè)governor后,會(huì)在每個(gè)使用該governor的cpu上建立一個(gè)工作隊(duì)列,工作隊(duì)列的執(zhí)行函數(shù)是在common_dbs_data中g(shù)ov_dbs_timer字段所指向的函數(shù),理所當(dāng)然,該函數(shù)由各個(gè)governor的具體代碼來(lái)實(shí)現(xiàn),對(duì)于ondemand governor,它的實(shí)現(xiàn)函數(shù)是od_dbs_timer。governor的公共層代碼為我們提供了一個(gè)API:dbs_check_cpu,該API用來(lái)計(jì)算兩個(gè)統(tǒng)計(jì)周期期間某個(gè)cpu的負(fù)載情況,我們先分析一下dbs_check_cpu:
void dbs_check_cpu(struct dbs_data *dbs_data, int cpu){ struct cpu_dbs_common_info *cdbs = dbs_data->cdata->get_cpu_cdbs(cpu); ...... policy = cdbs->cur_policy; /* Get Absolute Load (in terms of freq for ondemand gov) */ for_each_cpu(j, policy->cpus) { struct cpu_dbs_common_info *j_cdbs; ...... j_cdbs = dbs_data->cdata->get_cpu_cdbs(j); ...... cur_idle_time = get_cpu_idle_time(j, &cur_wall_time, io_busy); wall_time = (unsigned int) (cur_wall_time - j_cdbs->prev_cpu_wall); j_cdbs->prev_cpu_wall = cur_wall_time; idle_time = (unsigned int) (cur_idle_time - j_cdbs->prev_cpu_idle); j_cdbs->prev_cpu_idle = cur_idle_time; ...... load = 100 * (wall_time - idle_time) / wall_time; ...... load *= cur_freq; /* 實(shí)際的代碼不是這樣,為了簡(jiǎn)化討論,精簡(jiǎn)為實(shí)際的計(jì)算邏輯*/ if (load > max_load) max_load = load; } dbs_data->cdata->gov_check_cpu(cpu, max_load);}由代碼可以看出,遍歷該policy下每個(gè)online的cpu,取出該cpu對(duì)應(yīng)的cpu_dbs_common_info結(jié)構(gòu),該結(jié)構(gòu)中的prev_cpu_idle和prev_cpu_wall保存有上一次采樣周期時(shí)記錄的idle時(shí)間和運(yùn)行時(shí)間,負(fù)載的計(jì)算其實(shí)很簡(jiǎn)單:
idle_time = 本次idle時(shí)間 - 上次idle時(shí)間;wall_time = 本次總運(yùn)行時(shí)間 - 上次總運(yùn)行時(shí)間;負(fù)載load = 100 * (wall_time - idle_time)/ wall_time;把所有cpu中,負(fù)載最大值記入max_load中,作為選擇頻率的依據(jù);計(jì)算出最大負(fù)載max_load后,調(diào)用具體governor實(shí)現(xiàn)的gov_check_cpu回調(diào)函數(shù),對(duì)于ondemand來(lái)說(shuō),該回調(diào)函數(shù)是:od_check_cpu,我們跟進(jìn)去看看:
static void od_check_cpu(int cpu, unsigned int load_freq){ struct od_cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, cpu); struct cpufreq_policy *policy = dbs_info->cdbs.cur_policy; struct dbs_data *dbs_data = policy->governor_data; struct od_dbs_tuners *od_tuners = dbs_data->tuners; dbs_info->freq_lo = 0; /* Check for frequency increase */ if (load_freq > od_tuners->up_threshold * policy->cur) { /* If switching to max speed, apply sampling_down_factor */ if (policy->cur < policy->max) dbs_info->rate_mult = od_tuners->sampling_down_factor; dbs_freq_increase(policy, policy->max); return; }當(dāng)負(fù)載比預(yù)設(shè)的閥值高時(shí)(od_tuners->up_threshold,默認(rèn)值是95%),立刻選擇該policy最大的工作頻率作為接下來(lái)的工作頻率。如果負(fù)載沒(méi)有達(dá)到預(yù)設(shè)的閥值,但是當(dāng)前頻率已經(jīng)是最低頻率了,則什么都不做,直接返回:
if (policy->cur == policy->min) return;運(yùn)行到這里,cpu的頻率可能已經(jīng)在上面的過(guò)程中被設(shè)置為最大頻率,實(shí)際上我們可能并不需要這么高的頻率,所以接著判斷,當(dāng)負(fù)載低于另一個(gè)預(yù)設(shè)值時(shí),這時(shí)需要計(jì)算一個(gè)合適于該負(fù)載的新頻率:
if (load_freq < od_tuners->adj_up_threshold * policy->cur) { unsigned int freq_next; freq_next = load_freq / od_tuners->adj_up_threshold; /* No longer fully busy, reset rate_mult */ dbs_info->rate_mult = 1; if (freq_next < policy->min) freq_next = policy->min; if (!od_tuners->powersave_bias) { __cpufreq_driver_target(policy, freq_next, CPUFREQ_RELATION_L); return; } freq_next = od_ops.powersave_bias_target(policy, freq_next, CPUFREQ_RELATION_L); __cpufreq_driver_target(policy, freq_next, CPUFREQ_RELATION_L); }}對(duì)于ondemand來(lái)說(shuō),因?yàn)閭魅氲呢?fù)載是乘上了當(dāng)前頻率后的歸一化值,所以計(jì)算新頻率時(shí),直接用load_freq除以想要的負(fù)載即可。本來(lái)計(jì)算出來(lái)的頻率直接通過(guò)__cpufreq_driver_target函數(shù),交給cpufreq_driver調(diào)節(jié)頻率即可,但是這里的處理考慮了powersave_bias的設(shè)置情況,當(dāng)設(shè)置了powersave_bias時(shí),表明我們?yōu)榱诉M(jìn)一步節(jié)省電力,我們希望在計(jì)算出來(lái)的新頻率的基礎(chǔ)上,再乘以一個(gè)powersave_bias設(shè)定的百分比,作為真正的運(yùn)行頻率,powersave_bias的值從0-1000,每一步代表0.1%。實(shí)際的情況比想象中稍微復(fù)雜一點(diǎn),考慮到乘以一個(gè)powersave_bias后的新頻率可能不在cpu所支持的頻率表中,ondemand算法會(huì)在頻率表中查找,分別找出最接近新頻率的一個(gè)區(qū)間,由高低兩個(gè)頻率組成,低的頻率記入od_cpu_dbs_info_s結(jié)構(gòu)的freq_lo字段中,高的頻率通過(guò)od_ops.powersave_bias_target回調(diào)返回。同時(shí),od_ops.powersave_bias_target回調(diào)函數(shù)還計(jì)算出高低兩個(gè)頻率應(yīng)該運(yùn)行的時(shí)間,分別記入od_cpu_dbs_info_s結(jié)構(gòu)的freq_hi_jiffies和freq_low_jiffies字段中。原則是,通過(guò)兩個(gè)不同頻率的運(yùn)行時(shí)間的組合,使得綜合結(jié)果接近我們想要的目標(biāo)頻率。詳細(xì)的計(jì)算邏輯請(qǐng)參考函數(shù):generic_powersave_bias_target。 討論完上面兩個(gè)函數(shù),讓我們回到本節(jié)的開(kāi)頭,負(fù)載的計(jì)算工作是在一個(gè)工作隊(duì)列中發(fā)起的,前面說(shuō)過(guò),ondemand對(duì)應(yīng)的工作隊(duì)列的工作函數(shù)是od_dbs_timer,我們看看他的實(shí)現(xiàn)代碼:
static void od_dbs_timer(struct work_struct *work){ ...... /* Common NORMAL_SAMPLE setup */ core_dbs_info->sample_type = OD_NORMAL_SAMPLE; if (sample_type == OD_SUB_SAMPLE) { delay = core_dbs_info->freq_lo_jiffies; __cpufreq_driver_target(core_dbs_info->cdbs.cur_policy, core_dbs_info->freq_lo, CPUFREQ_RELATION_H); } else { dbs_check_cpu(dbs_data, cpu); if (core_dbs_info->freq_lo) { /* Setup timer for SUB_SAMPLE */ core_dbs_info->sample_type = OD_SUB_SAMPLE; delay = core_dbs_info->freq_hi_jiffies; } }max_delay: if (!delay) delay = delay_for_sampling_rate(od_tuners->sampling_rate * core_dbs_info->rate_mult); gov_queue_work(dbs_data, dbs_info->cdbs.cur_policy, delay, modify_all); mutex_unlock(&core_dbs_info->cdbs.timer_mutex);}如果sample_type是OD_SUB_SAMPLE時(shí),表明上一次采樣時(shí),需要用高低兩個(gè)頻率來(lái)模擬實(shí)際的目標(biāo)頻率中的第二步:需要運(yùn)行freq_lo,并且持續(xù)時(shí)間為freq_lo_jiffies。否則,調(diào)用公共層計(jì)算負(fù)載的API:dbs_check_cpu,開(kāi)始一次新的采樣,當(dāng)powersave_bias沒(méi)有設(shè)置時(shí),該函數(shù)返回前,所需要的新的目標(biāo)頻率會(huì)被設(shè)置,考慮到powersave_bias的設(shè)置情況,判斷一下如果freq_lo被設(shè)置,說(shuō)明需要用高低兩個(gè)頻率來(lái)模擬實(shí)際的目標(biāo)頻率,高頻率已經(jīng)在dbs_check_cpu返回前被設(shè)置(實(shí)際的設(shè)置工作是在od_check_cpu中),所以把sample_type設(shè)置為OD_SUB_SAMPLE,以便下一次運(yùn)行工作函數(shù)進(jìn)行采樣時(shí)可以設(shè)置低頻率運(yùn)行。最后,調(diào)度工作隊(duì)列在下一個(gè)采樣時(shí)刻再次運(yùn)行,這樣,cpu的工作頻率實(shí)現(xiàn)了在每個(gè)采樣周期,根據(jù)實(shí)際的負(fù)載情況,動(dòng)態(tài)地設(shè)定合適的工作頻率進(jìn)行運(yùn)行,既滿足了性能的需求,也降低了系統(tǒng)的功耗,達(dá)到了cpufreq系統(tǒng)的最終目的,整個(gè)流程可以參考下
圖 5.1 負(fù)載計(jì)算和頻率選擇
1.Performance governor: Highest frequency
設(shè)置當(dāng)前處理的頻率到最高頻率,后一直保持在這個(gè)頻率這個(gè)Governors目標(biāo)就是讓系統(tǒng)輸出最高performance2.Powersave Governor:Lowest Frequency
設(shè)置當(dāng)前處理器頻率在最低的頻率,然后保持在這一頻率目標(biāo)就是設(shè)置當(dāng)設(shè)置當(dāng)前處理器的頻率在最低的頻率,不管系統(tǒng)有多忙都不提高處理器頻率。這個(gè)Governor看著會(huì)省電,但實(shí)際并不會(huì)省。因?yàn)閜erformance降低,處理task時(shí)間變長(zhǎng),所以相比其他,其進(jìn)入睡眠的時(shí)間也最慢 3.Userspace governor: Manual frequencies允許用戶手動(dòng)選擇一組頻率這種模式通常與運(yùn)行在userspace的daemon進(jìn)程配合,去控制處理器頻率4.Ondemand governor: Frequency change based on processor use
在2.6.10內(nèi)核開(kāi)始使用,這也是第一個(gè)根據(jù)cpu使用狀況來(lái)動(dòng)態(tài)調(diào)整cpu頻率的governorOndemand governor會(huì)檢查當(dāng)前cpu使用情況,如果其load超過(guò)某一閾值(threshold),就會(huì)把當(dāng)前cpu頻率設(shè)定到最高的頻率。然后發(fā)現(xiàn)load低于閾值就會(huì)逐步降低cpu頻率。5.Conservative governor: A more gradual ondemand
這個(gè)governor基于ondemand governor,從2.6.12內(nèi)核開(kāi)始導(dǎo)入它會(huì)檢查cpu load高于或者低于某一閾值,如果cpu load比閾值高,則逐步提高cpu頻率,如果發(fā)現(xiàn)cpu load比閾值低,則逐步減小cpu頻率
6.interactive governor : 看后面具體的介紹
CPU Load計(jì)算方法:
load_freq = load * current frequencyif (load_freq > (up_threshold * current frequency)) next_freq = max_frequency (CPUFREQ_RELATION_H)if (load_freq < ((up_threshold - down_differential) * current frequency)) next_freq = load_freq / (up_threshold - down_differential) (CPUFREQ_RELATION_L)Ex) if load=30, current frequency=800MHz next_freq= (30 * 800MHz) / (80 - 10) = 342MHz CPUFREQ_RELATION_L means higher than or equal target frequency ? Select 500MHz
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注