Linux CPU core的電源管理(1)_概述
作者:wowo 釋出于:2015-4-30 21:20
分類:電源管理子系統
1. 前言
在SMP(Symmetric Multi-Processing)流行起來之前的很長一段時間,Linux kernel的電源管理工作主要集中在外部裝置上,和CPU core相關的,頂多就是CPU idle。但随着SMP的普及,一個系統中可用的CPU core越來越多,這些core的頻率越來越高,處理能力越來越強,功耗也越來越大。是以,CPU core有關的電源管理,在系統設計中就成為必不可少的一環,與此有關的思考包括: 對消費者(一些專業應用除外)而言,這種暴增的處理能力,是一種極大的浪費,他們很少(或者從不)有如此高的性能需求。但商家對此卻永遠樂此不疲,原因無外乎:
1)硬體成本越來越低。
2)營銷的噱頭。
3)軟體設計者的不思進取(臃腫的Android就是典型的例子),導緻軟體效率低下,硬體資源浪費嚴重。以至于優化幾行代碼的難度,甚至比增加幾個cpu核還困難。
在這種背景下,CPU core的電源管理邏輯,就非常直接了:根據系統的負荷,關閉“多餘的CPU性能”,在滿足使用者需求的前提下,盡可能的降低CPU的功耗。但CPU的控制粒度不可能無限小,目前主要從兩個角度實作CPU core的電源管理功能:
1)在SMP系統中,動态的關閉或者打開CPU core(本文重點介紹的功能)。
2)CPU運作過程中,動态的調整CPU core的電壓和頻率(将在其它文章中單獨分析)。
本文将以ARM64為例,介紹linux kernel CPU core相關的電源管理設計。
2. 功能說明
在linux kernel中,CPU core相關的電源管理實作,并不是單純的電源管理行為,它會涉及到系統初始化、CPU拓撲結構、程序排程、CPU hotplug、memory hotplug等知識點。總的來說,它主要完成如下功能: 1)系統啟動時,CPU core的初始化、資訊擷取等。
2)系統啟動時,CPU core的啟動(enable)。
3)系統運作過程中,根據目前負荷,動态的enable/disable某些CPU core,以便在性能和功耗之間平衡。
4)CPU core的hotplug支援。所謂的hotplug,是指可以在系統運作的過程中,動态的增加或者減少CPU core(可以是實體上,也可以是邏輯上)。
5)系統運作過程中的CPU idle管理(具體可參考“Linux cpuidle framework系列文章”)。
6)系統運作過程中,根據目前負荷,動态的調整CPU core的電壓和頻率,以便在性能和功耗之間平衡。
3. 軟體架構
為了實作上面的功能,linux kernel抽象出了下面的軟體架構:
軟體架構包括arch-dependent和arch-independent兩部分。
對ARM64而言,arch-dependent部分位于“arch\arm64\kernel”,負責提供平台相關的控制操作,包括: CPU資訊的擷取(cpuinfo);
CPU拓撲結構的擷取(cpu topology);
底層的CPU操作(init、disable等)的實作,cpu ops(在ARM32中是以smp ops的形式存在的);
SMP相關的初始化(smp);
等等。
arch-independent負責實作平台無關的抽象,包括: CPU control子產品,屏蔽底層平台相關的實作細節,提供控制CPU(enable、disable等)的統一API,供系統啟動、程序排程等子產品調用;
CPU subsystem driver,向使用者空間提供CPU hotplug有關的功能;
cpuidle,處理CPU idle有關的邏輯,具體可參考“cpuidle framework”相關的文章;
cpufreq,處理CPU frequency調整有關的邏輯,具體可參考後續的文章;
等等。
4. 軟體子產品的功能及API描述
4.1 kernel cpu control
kernel cpu control位于“.\kernel\cpu.c”中,是一個承上啟下的子產品,負責屏蔽arch-dependent的實作細節,向上層軟體提供CPU core控制的統一API。主要功能包括:
1)将CPU core抽象為possible、present、online和active四種狀态,并以bitmap的形式,在子產品内部維護所有CPU core的狀态,同時以cpumask的形式向其它子產品提供狀态查詢、狀态修改的API。相關的API如下:
1:
2:
3: #ifdef CONFIG_INIT_ALL_POSSIBLE
4: static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly
5: = CPU_BITS_ALL;
6: #else
7: static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;
8: #endif
9: const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits);
10: EXPORT_SYMBOL(cpu_possible_mask);
11:
12: static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;
13: const struct cpumask *const cpu_online_mask = to_cpumask(cpu_online_bits);
14: EXPORT_SYMBOL(cpu_online_mask);
15:
16: static DECLARE_BITMAP(cpu_present_bits, CONFIG_NR_CPUS) __read_mostly;
17: const struct cpumask *const cpu_present_mask = to_cpumask(cpu_present_bits);
18: EXPORT_SYMBOL(cpu_present_mask);
19:
20: static DECLARE_BITMAP(cpu_active_bits, CONFIG_NR_CPUS) __read_mostly;
21: const struct cpumask *const cpu_active_mask = to_cpumask(cpu_active_bits);
22: EXPORT_SYMBOL(cpu_active_mask);
bitmap的定義是:
#define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)]
其本質上是一個long型的數組,數組中每一個bit,代表一個CPU core的狀态。例如long的長度為64位的系統中,如果有8個CPU core,則可以使用長度為1的數組的前8個bit,代表這個8個core的狀态。
這裡一共有4種狀态需要表示:
cpu_possible_bits,系統中包含的所有的可能的CPU core,在系統初始化的時候就已經确定。對于ARM64來說,DTS中所有格式正确的CPU core,都屬于possible的core;
cpu_present_bits,系統中所有可用的CPU core(具備online的條件,具體由底層代碼決定),并不是所有possible的core都是present的。對于支援CPU hotplug的形态,present core可以動态改變;
cpu_online_bits,系統中所有運作狀态的CPU core(後面會詳細說明這個狀态的意義);
cpu_active_bits,有active的程序正在運作的CPU core。
另外,在使用bitmap表示這4種狀态的同時,還提供了4個cpumask,用于對外提供接口。cpumask的本質也是bitmap(多一層封裝而已),隻是kernel提供了一些友善的API,可以以CPU編号為機關,操作cpumask(具體可參考include/linux/cpumask.h)。
注1:這裡有一個關于constant變量的經典例子,大家可以學習一下。
毫無疑問,CPU core的這些狀态,是相當重要的一些狀态,是以kernel希望它們對外(除cpu control外)是read only的,對内又是writeable的。怎麼辦呢?
這裡的設計很巧妙,對内使用4個static的bitmap變量,是以是writeable的。而對外呢,使用4個constant類型的cpumask指針(指針readonly,值readonly),是以是readonly的。
外部子產品read時,通過一層轉換,從static的bitmap中擷取實際的值。是不是很有意思?
下面是這幾個變量有關的操作函數:
1:
2:
3: #define num_online_cpus() cpumask_weight(cpu_online_mask)
4: #define num_possible_cpus() cpumask_weight(cpu_possible_mask)
5: #define num_present_cpus() cpumask_weight(cpu_present_mask)
6: #define num_active_cpus() cpumask_weight(cpu_active_mask)
7: #define cpu_online(cpu) cpumask_test_cpu((cpu), cpu_online_mask)
8: #define cpu_possible(cpu) cpumask_test_cpu((cpu), cpu_possible_mask)
9: #define cpu_present(cpu) cpumask_test_cpu((cpu), cpu_present_mask)
10: #define cpu_active(cpu) cpumask_test_cpu((cpu), cpu_active_mask)
11:
12:
13: #define for_each_possible_cpu(cpu) for_each_cpu((cpu), cpu_possible_mask)
14: #define for_each_online_cpu(cpu) for_each_cpu((cpu), cpu_online_mask)
15: #define for_each_present_cpu(cpu) for_each_cpu((cpu), cpu_present_mask)
16:
17:
18: void set_cpu_possible(unsigned int cpu, bool possible);
19: void set_cpu_present(unsigned int cpu, bool present);
20: void set_cpu_online(unsigned int cpu, bool online);
21: void set_cpu_active(unsigned int cpu, bool active);
22: void init_cpu_present(const struct cpumask *src);
23: void init_cpu_possible(const struct cpumask *src);
24: void init_cpu_online(const struct cpumask *src);
2)提供CPU core的up/down操作,以及up/down時的notifier機制
通俗地講,所謂的CPU core up,就是将某一個CPU core“運作”起來。何為運作呢?回憶一下單核CPU的啟動,就是讓該CPU core在指定的memory位址處取指執行。是以該功能隻對SMP系統有效(使能了CONFIG_SMP)。而CPU core down,就是讓CPU core儲存現場(後面可以繼續執行)後,停止取指,隻有在CPU hotplug功能使能(CONFIG_HOTPLUG_CPU)時有效。這兩個功能對應的API為:
1:
2:
3: int cpu_up(unsigned int cpu);
4:
5: int cpu_down(unsigned int cpu);
同時,提供了CPU up/down時的通知API,具體請參考“include/linux/cpu.h"。
3)提供SMP PM有關的操作
系統休眠過程中,将noboot的CPU禁用,并在系統恢複時恢複(可參考“Linux電源管理(6)_Generic PM之Suspend功能”中的有關描述)。
1: #ifdef CONFIG_PM_SLEEP_SMP
2: extern int disable_nonboot_cpus(void);
3: extern void enable_nonboot_cpus(void);
4: #else
5: static inline int disable_nonboot_cpus(void) { return 0; }
6: static inline void enable_nonboot_cpus(void) {}
7: #endif
4.2 cpu subsystem driver
cpu subsystem driver位于“drivers/base/cpu.c”中,從裝置模型的角度,抽象CPU core裝置,并通過sysfs提供CPU core狀态查詢、hotplug控制等接口。具體如下:
1)注冊一個名稱為“bus”的subsystem(在sysfs中目錄為“/sys/devices/system/cpu/”,有關subsystem的描述,可參考“Linux裝置模型(6)_Bus”)。
2)使用struct cpu抽象CPU core device(見“include/linux/cpu.h”)。
4)從裝置模型的角度,提供CPU core device的register/unregister等接口,并在系統初始化的時候根據CPU core的個數,将這些device注冊到kernel中。同時根據kernel配置,注冊相關的CPU attribute。
1: extern int register_cpu(struct cpu *cpu, int num);
2: extern struct device *get_cpu_device(unsigned cpu);
3: extern bool cpu_is_hotpluggable(unsigned cpu);
4: extern bool arch_match_cpu_phys_id(int cpu, u64 phys_id);
5: extern bool arch_find_n_match_cpu_physical_id(struct device_node *cpun,
6: int cpu, unsigned int *thread);
7:
8: extern int cpu_add_dev_attr(struct device_attribute *attr);
9: extern void cpu_remove_dev_attr(struct device_attribute *attr);
10:
11: extern int cpu_add_dev_attr_group(struct attribute_group *attrs);
12: extern void cpu_remove_dev_attr_group(struct attribute_group *attrs);
13:
14: #ifdef CONFIG_HOTPLUG_CPU
15: extern void unregister_cpu(struct cpu *cpu);
16: extern ssize_t arch_cpu_probe(const char *, size_t);
17: extern ssize_t arch_cpu_release(const char *, size_t);
18: #endif
最終在sysfs中的目錄結構如下:
# ls /sys/devices/system/cpu/
autoplug/ cpu2/ cpuidle/ offline power/
cpu0/ cpu3/ kernel_max online present
具體會在後續的文章中詳細說明。
4.3 smp
smp位于“arch/arm64/kernel/smp.c”,在arch-dependent代碼中,承擔承上啟下的角色,主要提供兩類功能:
1)arch-dependent的SMP初始化、CPU core控制等操作(本文需要關注的功能)。
2)IPI(Inter-Processor Interrupts)相關的支援(具體可參考本站“中斷子系統”有關的文章)。
SMP初始化操作,主要負責從DTS中解析CPU core資訊,并擷取必要的資訊以及操作函數集,由smp_init_cpus接口實作,并在setup_arch(arch\arm64\kernel\setup.c)中調用。
CPU core控制相關的接口包括:
1:
2:
3:
6: asmlinkage void secondary_start_kernel(void);
7:
8:
11: struct secondary_data {
12: void *stack;
13: };
14: extern struct secondary_data secondary_data;
15: extern void secondary_entry(void);
16:
17: extern void arch_send_call_function_single_ipi(int cpu);
18: extern void arch_send_call_function_ipi_mask(const struct cpumask *mask);
19:
20: extern int __cpu_disable(void);
21:
22: extern void __cpu_die(unsigned int cpu);
23: extern void cpu_die(void);
secondary_start_kernel、secondary_entry,是那些 noboot CPU的入口,具體後面再詳細介紹;
__cpu_disable、__cpu_die、cpu_die等函數負責disable某個CPU core,它們不會直接操作硬體,而是通過下層的cpu_ops控制具體的CPU core,具體請參考4.4小節的說明。
4.4 cpu ops
由于SMP架構比較複雜,特别是對ARM64而言,又涉及到虛拟化等安全特性,ARM便将CPU core的up/down等電源管理操作,封裝起來(例如封裝到secure mode下,特權級别的OS代碼通過一些指令碼與其互動,具體請參考後續的文章)。在ARM64中,這種封裝便是通過cpu os結構(struct cpu_operations)展現的,如下:
1:
2:
3:
28: struct cpu_operations {
29: const char *name;
30: int (*cpu_init)(struct device_node *, unsigned int);
31: int (*cpu_init_idle)(struct device_node *, unsigned int);
32: int (*cpu_prepare)(unsigned int);
33: int (*cpu_boot)(unsigned int);
34: void (*cpu_postboot)(void);
35: #ifdef CONFIG_HOTPLUG_CPU
36: int (*cpu_disable)(unsigned int cpu);
37: void (*cpu_die)(unsigned int cpu);
38: int (*cpu_kill)(unsigned int cpu);
39: #endif
40: #ifdef CONFIG_ARM64_CPU_SUSPEND
41: int (*cpu_suspend)(unsigned long);
42: #endif
43: };
ARM architecture提供多種可選的cpu ops實作,如spin-table、PSCI(Power State Coordination Interface)等(具體會在後續的文章中較長的描述),開發者可以根據需求,選擇一種。4.3節所描述的smp初始化時,會解析DTS,并填充cpu ops變量。
這裡以PSCI為例,簡單了解一下這些接口的含義(具體說明請參考後續的文章)。
cpu_boot: Boots a cpu into the kernel. 其實就是将啟動函數(secondary_entry)的實體位址,給到CPU core執行,具體要看spec
cpu_disable: Prepares a cpu to die.
cpu_die: Makes a cpu leave the kernel.
cpu_suspend: Suspends a cpu and saves the required context.
4.5 cpu topology
本文提到了很多諸如SMP、CPU core之類的字眼,相應讀者可能看的不太明白,這和CPU的拓撲結構有關。程序排程、cpufreq等子產品,可能需要根據具體的拓撲結構,制定相應的政策。
ARM64的topology在“./arch/arm64/include/asm/topology.h”中定義,如下:
1: struct cpu_topology {
2: int thread_id;
3: int core_id;
4: int cluster_id;
5: cpumask_t thread_sibling;
6: cpumask_t core_sibling;
7: };
8:
9: extern struct cpu_topology cpu_topology[NR_CPUS];
10:
11: #define topology_physical_package_id(cpu) (cpu_topology[cpu].cluster_id)
12: #define topology_core_id(cpu) (cpu_topology[cpu].core_id)
13: #define topology_core_cpumask(cpu) (&cpu_topology[cpu].core_sibling)
14: #define topology_thread_cpumask(cpu) (&cpu_topology[cpu].thread_sibling)
15:
16: void init_cpu_topology(void);
17: void store_cpu_topology(unsigned int cpuid);
18: const struct cpumask *cpu_coregroup_mask(int cpu);
topology的實作位于“arch/arm64/kernel/topology.c ”中,負責在系統初始化的時候,由boot cpu讀取DTS,填充每個CPU core的struct cpu_topology變量。同時,該檔案提供一些通用的宏定義,用于擷取執行CPU core的資訊,例如該CPU core的package id、core id等。
topology的具體描述,請參考下一篇文章。
4.6 cpu info及其它
cpu info位于“arch/arm64/kernel/cpuinfo.c”,負責在初始化的時候将ARM CPU core有關的資訊,從寄存器中讀出,緩存在struct cpuinfo_arm64類型的變量中,以便後面使用。具體不再較長的描述。
5. 結束語
本文簡單的介紹了CPU core電源管理相關的軟體組成,并認識了cpu ops、cpu topology等概念,後續将通過以下的文章進行更為詳細的分析:
Linux CPU core的電源管理(2)_cpu topology,認識并了解ARM處理器的拓撲結構,以及cluster(socket)、core、thread等處理器結構相關的概念;
Linux CPU core的電源管理(4)_PSCI,分析ARM PSCI(Power State Coordination Interface)接口;
Linux CPU core的電源管理(5)_cpu control,從系統的角度,分析系統初始化、程序排程、CPU hotplug等場景下,CPU up/down等操作的流程;
Linux cpufreq framework系列文章,分析CPU動态頻率/電壓調整相關的實作。
原創文章,轉發請注明出處。蝸窩科技,www.wowotech.net。
評論:
2016-05-27 09:13
hi,wowo,
我們經常所說的DVFS,狹隘地是指interactive這個governor嗎?還有其他地方有涉及嗎?
啥時候有空把interactive governor分析一下啊?
謝謝
2016-05-27 10:55
@koala:可以這麼了解,不過你也可以設計自己的政策。
有時間可以分析一下interactive governor,多謝關注~
2016-01-27 19:56
寫的很好,才發現有這麼好的一個地方,謝謝! 希望能快點學習,以後我也能在這裡寫一篇文章。 :)
2016-01-28 08:57
@archer:希望不止有一篇哈,等着你:-)
YANG23
2015-09-17 15:35
非常期待 電源管理PSCI相關的文章,希望作者盡快發表啊,另外能不能對比一下 PSCI和SMP SPIN TABLE優劣主要對功耗的影響呢 期待作者
2015-09-18 12:20
@YANG23:多謝關注,其實一般情況下,不會深入到PSCI的,會用就可以了。
PSCI主要的特點,就是支援CPU hotplug。對功耗而言,PSCI的ops可能會單獨core的power,因而肯定比SPIN table好。
2015-08-30 20:25
非常期待 Linux CPU core的電源管理(5)_cpu control
這篇文章.
現在移植一個cpuidle功能到高版本核心, bootloader一走完, 就hang住了.
我猜和PM與SMP初始化哪裡出錯了. 很多代碼不是我寫的, 現在正在看.
請教lz, PM和smp初始化相關的代碼有哪些呢?
2015-08-31 09:05
@firo:解決問題的時候,看代碼不是最有效的方法,你可以用一些收到,查一查hang在哪裡了。
另外,這個時候和PM沒有關系吧?SMP的話,可以去“arch/xxx/kernel/smp.c”看看。
2015-08-31 10:42
@wowo:感謝.
核心什麼資訊都沒列印出來,就hang了.
因為我是隻把上個版本cpuidle & suspend相關的代碼和進來.系統就不能啟動了. 是以和PM關系非常大.
2015-09-01 09:45
@firo:可以把選項CONFIG_DEBUG_LL(kernel low-level debugging functions)打開,即使console還沒有初始化,也可以用printascii等函數加入列印資訊,這樣就可以定位具體挂在哪裡了?這個功能非常實用。
2015-09-01 11:13
@snail:感謝!
昨天用printascii看到4個core都走到了cpu_startup_entry.看上去系統正常啟動了.
1. 可能是console沒有正常啟動是以看不到printk之類的資訊, 機率較小, 因為支部分代碼沒動過.
2. wowo知道用什麼方法可以, 把printk在系統啟動時打到console裡面的資訊用printascii 或者其他方法輸出出來, 看看使用者态是否起來了? 排除是否隻是console有問題.
3. 還有一個問題, arm多核cpu_do_idle是用宏定義成processor._do_idle(), 我沒有找到這個_do_idle是在哪裡初始化的.
在setup_processor隻看到了processor的整體指派語句
list = lookup_processor_type(read_cpuid_id());
...
processor = *list->proc;
我昨天用printascii列印的時候隻列印到
static void default_idle(void)
{
if (arm_pm_idle)
arm_pm_idle();
else
cpu_do_idle(); //這是個宏
local_irq_enable();
}接下來不知道怎麼走了.
2015-09-01 12:26
@firo:2. 很久以前我們做過,方法有多種,比如重寫printk,調用printascii 輸出。
3. processor的_do_idle,一般都是發指令到CPU core中了,不同平台不一樣。一般情況下,跟到這裡面,就沒有方法接着跟了。
2015-08-20 14:51
cpu_online_bits,系統中所有運作狀态的CPU core(後面會詳細說明這個狀态的意義);
系統是如果确定運作狀态呢?
cpu_active_bits,有active的程序正在運作的CPU core。
這個online 與active 如何差別呢?
2015-08-20 17:11
@tigger:不好意思,一直空接着寫這一塊的東西:
cpu_online_bits比較容易了解,CPU啟動的話(也即CPU運作了kernel代碼,secondary_start_kernel),就認為是online的,會通過set_cpu_online接口設定這個bit mask。
而cpu_active_bits,則表示有任務在這個CPU上排程,由kenrel sched子產品,調用set_cpu_active設定。
online和active的差別是,隻要active,一定是online,是以online更多的代碼了CPU的“實體狀态”,而active,更多的是OS排程層面的抽象,CPU本身沒有這一個狀态。
buhui
2015-08-08 15:19
好文章,讀這樣的文章,就像饑餓的人撲到了面包上。比陳浩強的文章有天壤之别。
2015-05-10 14:57
高端手機平台現在都是cpuidle + scheduler(HMP) 做省功耗方案。hotplug隻在thermal控制中使用了。
2015-05-11 10:29
@schedule:多謝分享,第一次聽說HMP的概念,要了解了解。
2015-05-24 22:42
@wowo:1、是的,64bit 4核以上arm cpu方案基本都采用HMP(GTS) + Interactive(DVFS)組合,但功耗調試需要花時間。
2015-05-25 11:05
@zzq57683968:原來HMP就是big·little模式的multi-processor,我對HMP這個詞不太了解。big·little是ARM為了性能和功耗平衡搞出來的一個東西,和CPU topology有關。
DVFS還沒有深入了解,就是我們項目加上去過一段時間,後來又去掉了,據說不太穩定,呵呵。
2015-05-10 14:38
CPU/GPU 絕對耗電大戶啊
發表評論:
昵稱
郵件位址 (選填)
個人首頁 (選填)