Linux CPU core的电源管理(1)_概述

2019-07-13 21:40发布

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抽象出了下面的软件框架: linux cpu core pm 软件框架包括arch-dependent和arch-independent两部分。 对ARM64而言,arch-dependent部分位于“archarm64kernel”,负责提供平台相关的控制操作,包括:
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位于“.kernelcpu.c”中,是一个承上启下的模块,负责屏蔽arch-dependent的实现细节,向上层软件提供CPU core控制的统一API。主要功能包括: 1)将CPU core抽象为possible、present、online和active四种状态,并以bitmap的形式,在模块内部维护所有CPU core的状态,同时以cpumask的形式向其它模块提供状态查询、状态修改的API。相关的API如下: 1: /* kernel/cpu.c */ 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: /* include/linux/cpumask.h */ 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: /* Wrappers for arch boot code to manipulate normally-constant masks */ 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: /* include/linux/cpu.h */ 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 /* !CONFIG_PM_SLEEP_SMP */ 5: static inline int disable_nonboot_cpus(void) { return 0; } 6: static inline void enable_nonboot_cpus(void) {} 7: #endif /* !CONFIG_PM_SLEEP_SMP */
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代码中,承担承上启下的角 {MOD},主要提供两类功能: 1)arch-dependent的SMP初始化、CPU core控制等操作(本文需要关注的功能)。 2)IPI(Inter-Processor Interrupts)相关的支持(具体可参考本站“中断子系统”有关的文章)。 SMP初始化操作,主要负责从DTS中解析CPU core信息,并获取必要的信息以及操作函数集,由smp_init_cpus接口实现,并在setup_arch(archarm64kernelsetup.c)中调用。 CPU core控制相关的接口包括: 1: /* arch/arm64/include/asm/smp.h */ 2:  3: /* 4: * Called from the secondary holding pen, this is the secondary CPU entry point. 5: */ 6: asmlinkage void secondary_start_kernel(void); 7:  8: /* 9: * Initial data for bringing up a secondary CPU. 10: */ 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: /* arch/arm64/include/asm/cpu_ops.h */ 2:  3: /** 4: * struct cpu_operations - Callback operations for hotplugging CPUs. 5: * 6: * @name: Name of the property as appears in a devicetree cpu node's 7: * enable-method property. 8: * @cpu_init: Reads any data necessary for a specific enable-method from the 9: * devicetree, for a given cpu node and proposed logical id. 10: * @cpu_init_idle: Reads any data necessary to initialize CPU idle states from 11: * devicetree, for a given cpu node and proposed logical id. 12: * @cpu_prepare: Early one-time preparation step for a cpu. If there is a 13: * mechanism for doing so, tests whether it is possible to boot 14: * the given CPU. 15: * @cpu_boot: Boots a cpu into the kernel. 16: * @cpu_postboot: Optionally, perform any post-boot cleanup or necesary 17: * synchronisation. Called from the cpu being booted. 18: * @cpu_disable: Prepares a cpu to die. May fail for some mechanism-specific 19: * reason, which will cause the hot unplug to be aborted. Called 20: * from the cpu to be killed. 21: * @cpu_die: Makes a cpu leave the kernel. Must not fail. Called from the 22: * cpu being killed. 23: * @cpu_kill: Ensures a cpu has left the kernel. Called from another cpu. 24: * @cpu_suspend: Suspends a cpu and saves the required context. May fail owing 25: * to wrong parameters or error conditions. Called from the 26: * CPU being suspended. Must be called with IRQs disabled. 27: */ 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的电源管理(3)_cpu ops,分析cpu operations抽象和物理意义; 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动态频率/电压调整相关的实现。