如何开启linux hotplug 机制cpu hot plug

博客访问: 155506
博文数量: 166
博客积分: 10
博客等级: 民兵
技术积分: 250
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
原文地址: 作者:
CPU hotplug Support in Linux(tm) Kernel
Linux支持CPU的热插拔功能
&&&&&&&&&&&&&&&&&&
Maintainers:
&&&&&&&&&&&&&&&&&&
CPU Hotplug Core:
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Rusty Russell &.au&
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Srivatsa Vaddagiri &vatsa@&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Zwane Mwaikambo &zwane@arm.linux.org.uk&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Nathan Lynch &nathanl@&
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Joel Schopp &jschopp@&
&&&&&&&&&&&&&&&&&&
ia64/x86_64:
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Ashok Raj &ashok.&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&& Heiko Carstens &heiko.carstens@&
Authors: Ashok Raj &ashok.&
Lots of feedback: Nathan Lynch &nathanl@&,
&&&&&&&&&&&&&& Joel Schopp &jschopp@&
Introduction
Modern advances in system architectures have introduced advanced error
reporting and correction capabilities in processors. CPU architectures permit
partitioning support, where compute resources of a single CPU could be made
available to virtual machine environments. There are couple OEMS that
support NUMA hardware which are hot pluggable as well, where physical
node insertion and removal require support for CPU hotplug.
Such advances require CPUs available to a kernel to be removed either for
provisioning reasons, or for RAS purposes to keep an offending CPU off
system execution path. Hence the need for CPU hotplug support in the
Linux kernel.
A more novel use of CPU-hotplug support is its use today in suspend
resume support for SMP. Dual-core and HT support makes even
a laptop run SMP kernels which didn't support these methods. SMP support
for suspend/resume is a work in progress.
在,在一些先进的体系结构中处理器引进了一些先进的错误报告和错误修正能力。CPU体系结构上允许CPU分割,把分割的一部分CPU作为一个可用的虚拟机
器环境。有很多NUAM结构的硬件设备支持设备热插拔功能,比如说NUMA结构的计算机一个物理节点的插入和移出需要CPU热插拔功能的支持。
如此先进的功能需要操作系统能够提供必要条件满足CPU的热插拔功能的实现。或者因为远程访问服务器有意去使某个CPU不在系统的执行路径上(即这个CPU不能再次被使用)。这些都要求Linux操作系统支持CPU的热插拔功能。
现在很多对称多处理器的机器(SMP)使用CPU热插拔功能。双核和高可用技术使得便携式电脑都能使用SMP内核的操作系统,但是不能实现CPU热插拔功能。SMP支持CPU暂停或恢复当作一个任务在系统运行过程中。
General Stuff about CPU Hotplug(关于CPU热插拔常常碰到的一些技术与问题)
--------------------------------
Command Line Switches(命令行开关,就是通过命令行的执行,对一些数据项和操作的开\关设置)
---------------------
maxcpus=n&&&& Restrict boot time cpus to n. Say if you have 4 cpus, using
&&&&&&&&&&&&
maxcpus=2 will only boot 2. You can choose to bring the
&&&&&&&&&&&&
other cpus later online, read FAQ's for more info.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&这个数据项用来限制机器启动时启动CPU的数目。如果你的机器有4个CPU,你将maxcpus设置为2,那么在机器启动时只能启动2个CPU。你可以在机器完全启动后,可以将剩下的CPU热添加进系统。
additional_cpus=n (*)&&&&&&
Use this to limit hotpluggable cpus. This option sets
&&&&&&&&&&&&&&&&&&&&&&&&&& cpu_possible_map = cpu_present_map + additional_cpus
&&&&&&&&&&&&&&&&&&&&&&&&&&
使用这个命令行可以限制可以热添加的CPU数目。下面等式表示系统中几个描述CPU位图之间的关系。
&&&&&&&&&&&&
系统中可以允许最大数目CPU=系统中目前现有的CPU数目+添加进去的CPU数目
(*) Option valid only for following architectures
- x86_64, ia64(只针对X86_64和ia64体系结构的CPU)
ia64 and x86_64 use the number of disabled local apics in ACPI tables MADT
to determine the number of potentially hot-pluggable cpus. The implementation
should only rely on this to count the # of cpus, but *MUST* not rely on the
apicid values in those tables for disabled apics. In the event BIOS doesn't
mark such hot-pluggable cpus as disabled entries, one could use this
parameter "additional_cpus=x" to represent those cpus in the cpu_possible_map.
ia64和x86_64芯片使用ACPI的MADT表中大量的被禁用的局部APICs(Advanced Programmable Interrupt Controllers,高级可编程中断控制器)决定这些CPU作为潜在的热插拔CPU。
possible_cpus=n&&&&&&&&&&&&&&&&
[s390 only] use this to set hotpluggable cpus.
&&&&&&&&&&&&&&&&&&&&&&&&&&&& This option sets possible_cpus bits in
&&&&&&&&&&&&&&&&&&&&&&&&&&&& cpu_possible_map. Thus keeping the numbers of bits set
&&&&&&&&&&&&&&&&&&&&&&&&&&&& constant even if the machine gets rebooted.
CPU maps and such
-----------------
[More on cpumaps and primitive to manipulate, please check
cpu_possible_map: Bitmap of possible CPUs that can ever be available in the
system. This is used to allocate some boot time memory for per_cpu variables
that aren't designed to grow/shrink as CPUs are made available or removed.
Once set during boot time discovery phase, the map is static, i.e no bits
are added or removed anytime.&& Trimming it accurately for your system needs
upfront can save some boot time memory. See below for how we use heuristics
in x86_64 case to keep this under check.
cpu_possible_map:
系统中可能所有可用的CPU的位图结构变量。一般在系统引导启动的时候为每个可能存在的CPU分配内存给per_cpu变量。这些不是用来描述CPU的热
添加或热移出操作。这个位图是个静态变量,在系统引导启动时候生成后,就不能修改了。
cpu_online_map: Bitmap of all CPUs currently online. Its set in __cpu_up()
after a cpu is available for kernel scheduling and ready to receive
interrupts from devices. Its cleared when a cpu is brought down using
__cpu_disable(), before which all OS services including interrupts are
migrated to another target CPU.
cpu_online_map:
系统当前online的CPU。这个位图在函数__cpu_up()中被设置,这个位图描述了系统有哪些CPU可以用来调度执行任务和可以接受设备中断。
当执行函数__cpu_disable()时,清除指定CPU在位图cpu_online_map对应的位,这就意味着指定的CPU不可用了,不过这个函
数执行之前,要将指定CPU上所有的服务和中断迁移到其他CPU上执行。
cpu_present_map: Bitmap of CPUs currently present in the system. Not all
of them may be online. When physical hotplug is processed by the relevant
subsystem (e.g ACPI) can change and new bit either be added or removed
from the map depending on the event is hot-add/hot-remove. There are currently
no locking rules as of now. Typical usage is to init topology during boot,
at which time hotplug is disabled.
cpu_present_map:
当前系统存在的CPU。但是,并不是所有存在的CPU都处于online。在物理上的CPU热插拔依据相关的子系统处理,会改变位图
cpu_present_map相应位的值。该位图经典地用于在系统启动时描述系统CPU拓扑结构,并且那时候CPU的hotplug功能被禁用了。
You really dont need to manipulate any of the system cpu maps. They should
be read-only for most use. When setting up per-cpu resources almost always use
cpu_possible_map/for_each_possible_cpu() to iterate.
你并不需要熟练使用系统地CPU这些位图。因为他们通常只读。通过cpu_possible_map/for_each_possible_cpu()反复使用为每个CPU建立必需资源。
Never use anything other than cpumask_t to represent bitmap of CPUs.
在系统中所有的CPU位图都是cpumask_t类型的数据结构。
#include &linux/cpumask.h&
for_each_possible_cpu&&&&
- Iterate over cpu_possible_map
for_each_online_cpu&&&&&&
- Iterate over cpu_online_map
for_each_present_cpu&&&&&& - Iterate over cpu_present_map
for_each_cpu_mask(x,mask) - Iterate over some random collection of cpu mask.
#include &linux/cpu.h&
get_online_cpus() and put_online_cpus():
The above calls are used to inhibit cpu hotplug operations. While the
cpu_hotplug.refcount is non zero, the cpu_online_map will not change.
If you merely need to avoid cpus going away, you could also use
preempt_disable() and preempt_enable() for those sections.
Just remember the critical section cannot call any
function that can sleep or schedule this process away. The preempt_disable()
will work as long as stop_machine_run() is used to take a cpu down.
面提到的所有函数(例程)用于阻止CPU的热插拔操作。当cpu_hotplug的引用计数不为0时,位图cpu_online_map不会发生改变。如
果你仅仅只是需要避免CPU的消失,你同样可以调用preempt_disable()和preempt_enable()。记住在临界区不能调用任何函
数因为那样可能会被睡眠或者中断。当执行stop_machine_run()函数使一个CPU不可用时,一定会调用执行
preempt_disable()。
CPU Hotplug - Frequently Asked Questions.(CPU热插拔常见问题)
Q: How to enable my kernel to support CPU hotplug?该怎样激活我的操作系统内核才能支持CPU热插拔?
A: When doing make defconfig, Enable CPU hotplug support
"Processor type and Features" -& Support for Hotpluggable CPUs
Make sure that you have CONFIG_HOTPLUG, and CONFIG_SMP turned on as well.
You would need to enable CONFIG_HOTPLUG_CPU for SMP suspend/resume support
在你配置内核选项时,即在终端输入“make menuconfig”,将会出现一个蓝色界面,将需要的选项打开即可。
阅读(92) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。Linux CPU core的电源管理(5)_cpu control及cpu hotplug
由“”的描述可知,kernel cpu control位于“.\kernel\cpu.c”中,是一个承上启下的模块,负责屏蔽arch-dependent的实现细节,向上层软件提供控制CPU core的统一API(主要包括cpu_up/cpu_down等接口的实现)。本文将基于这些API,从上到下,分析CPU core从启动到关闭的整个过程(主要是CPU hotplug),进一步理解系统运行过程中CPU core电源管理相关的行为。
注1:其实这一部分已经不属于电源管理的范畴了,而是系统级的软件行为(boot、调度、电源管理等等),之所以放到这里讲述,主要原因是,这些复杂行为的背后,目的只有一个----节电。因此,本文只会focus在CPU core power状态切换的过程上,涉及到得其它知识,如进程调度,只会一笔带过。
2. possible/present/active/online cpus
前面文章提到过,kernel使用4个bitmap,来保存分别处于4种状态的CPU core:possible、present、active和online。这四个状态的意义到底是什么?下面我们根据相关的代码逻辑,来解答这个问题。
开始之前,先看一下kernel中对他们的注释:
1: /* include/linux/cpumask.h */
* The following particular system cpumasks and operations manage
* possible, present, active and online cpus.
cpu_possible_mask- has bit 'cpu' set iff cpu is populatable
cpu_present_mask - has bit 'cpu' set iff cpu is populated
cpu_online_mask
- has bit 'cpu' set iff cpu available to scheduler
cpu_active_mask
- has bit 'cpu' set iff cpu available to migration
If !CONFIG_HOTPLUG_CPU, present == possible, and active == online.
The cpu_possible_mask is fixed at boot time, as the set of CPU id's
that it is possible might ever be plugged in at anytime during the
life of that system boot.
The cpu_present_mask is dynamic(*),
representing which CPUs are currently plugged in.
cpu_online_mask is the dynamic subset of cpu_present_mask,
indicating those CPUs available for scheduling.
If HOTPLUG is enabled, then cpu_possible_mask is forced to have
all NR_CPUS bits set, otherwise it is just the set of CPUs that
ACPI reports present at boot.
If HOTPLUG is enabled, then cpu_present_mask varies dynamically,
depending on what ACPI reports as currently plugged in, otherwise
cpu_present_mask is just a copy of cpu_possible_mask.
大意是这样的:
possible状态的CPU意味着是“populatable(觉得这个单词还没有possible易懂)”的,可理解为存在这个CPU资源,但还没有纳入Kernel的管理范围;
present状态的CPU,是已经“populated”的CPU,可理解为已经被kernel接管;
online状态的CPU,表示可以被调度器使用;
active状态的CPU,表示可以被migrate(什么意思?);
如果系统没有使能CPU Hotplug功能,则present等于possible,active等于online。
还真不是很容易理解,不急,我们一个一个分析。
2.1 possible CPU
possible的CPUs,代表了系统中可被使用的所有的CPU,在boot阶段确定之后,就不会再修改。以ARM64为例,其初始化的过程如下。
1)系统上电后,boot CPU启动,执行start_kernel(init/main.c),并分别调用boot_cpu_init和setup_arch两个接口,进行possible CPU相关的初始化。
2)boot_cpu_init负责将当前的boot CPU放到possible CPU的bitmap中,同理,boot CPU也是present、oneline、active CPU(因此,后续的描述,都是针对非boot CPU的)。如下:
1: /* init/main.c */
3: static void __init boot_cpu_init(void)
int cpu = smp_processor_id();
/* Mark the boot cpu "present", "online" etc for SMP and UP case */
set_cpu_online(cpu, true);
set_cpu_active(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
smp_processor_id用于获取当前的CPU id;
set_cpu_xxx接口,可以将指定的CPU设置为(或者清除)指定的状态。
3)setup_arch负责根据MPIDR寄存器,以及DTS配置,解析并设置其它的possible CPU,如下:
1: /* arch/arm64/kernel/setup.c */
3: void __init setup_arch(char **cmdline_p)
cpu_logical_map(0) = read_cpuid_mpidr() & MPIDR_HWID_BITMASK;
cpu_read_bootcpu_ops();
8: #ifdef CONFIG_SMP
smp_init_cpus();
smp_build_mpidr_hash();
11: #endif
3a)cpu_logical_map数组
kernel使用一个整形数组(cpu_logical_map,定义如下),保存物理CPU(由ID标示)和逻辑CPU(数组的index)之间的映射,该数组的长度由NR_CPUS决定。
1: /* arch/arm64/include/asm/smp_plat.h */
* Logical CPU mapping.
6: extern u64 __cpu_logical_map[NR_CPUS];
7: #define cpu_logical_map(cpu)
__cpu_logical_map[cpu]
上面setup_arch代码的第六行,通过read_cpuid_mpidr接口,读取当前CPU(boot CPU)的ID(物理ID),并保存在map表的第一个位置。
3b)smp_init_cpus
如果使能了SMP,则调用smp_init_cpus接口,完成如下事情:
从DTS中解析其它CPU的HW ID(通过‘reg’关键字,如下),并保存在cpu_logical_map数组中;
对所有cpu_logical_map数组中的CPU,执行set_cpu_possible操作,将它们设置为possible状态。
#address-cells = ;
#size-cells = ;
device_type = "cpu";
compatible = "arm,cortex-a53", "arm,armv8";
enable-method = "psci";
cpu-idle-states = ;
device_type = "cpu";
compatible = "arm,cortex-a53", "arm,armv8";
enable-method = "psci";
cpu-idle-states = ;
CPU DTS文件示例。
对ARM64来说,possible的CPU,就是在DTS中指定了的,物理存在的CPU core。
2.2 present CPU
还是以ARM64为例,“start_kernel—>setup_arch”成功执行之后,继续执行“start_kernel—>rest_init—>kernel_init(pid 1,init task)—>kernel_init_freeable”,在kernel_init_freeable中会调用arch-dependent的接口:smp_prepare_cpus,该接口主要的主要功能有两个:
1)构建系统中CPU的拓扑结构,具体可参考“”。
2)拓扑结构构建完成后,根据CPU的拓扑,初始化系统的present CPU mask,代码如下:
1: void __init smp_prepare_cpus(unsigned int max_cpus)
/* Don't bother if we're effectively UP */
if (max_cpus <= 1)
* Initialise the present map (which describes the set of CPUs
* actually populated at the present time) and release the
* secondaries from the bootloader.
* Make sure we online at most (max_cpus - 1) additional CPUs.
max_cpus--;
for_each_possible_cpu(cpu) {
if (max_cpus == 0)
if (cpu == smp_processor_id())
if (!cpu_ops[cpu])
err = cpu_ops[cpu]->cpu_prepare(cpu);
set_cpu_present(cpu, true);
max_cpus--;
4~6行:当然,如果CPU个数不大于1,则不是SMP系统,就没有后续的概念,直接返回。
16~32行,轮询所有的possible CPU,如果某个CPU core满足一些条件,则调用set_cpu_present,将其设置为present CPU,满足的条件包括:具备相应的cpu_ops指针(有关cpu ops请参考“”);cpu ops的.cpu_prepare回调成功执行。
由“”中有关CPU ops的解释可知,.cpu_prepare回调主要用于检查某个CPU是否具备执行的条件。如果.cpu_prepare执行成功,则说明该CPU是可以启动的。因此,present CPU的意义是:
该CPU已经被kernel识别到,并且具备执行代码的条件,后续可以在需要的时候(如hotpulg的时候),启动该CPU。
2.3 online CPU
由前面present CPU可知,如果某个CPU是present的,则说明该CPU具备boot的条件,但是否已经boot还是未知数。
由“”的介绍可知,所谓的CPU boot,就是让CPU执行(取指译码/执行)代码(这里为linux kernel)。而CPU是否boot,则反映到online mask上,已经boot的CPU,会在secondary_start_kernel中,调用set_cpu_online接口,将其设置为online状态。反之,会在__cpu_disable中将其从online mask中清除。
有关CPU boot的流程,请参考下面的介绍。
2.4 active CPU
在单核时代,调度器(scheduler)的职责很单纯:主要负责管理、调教一帮调皮捣蛋的task,尽量以“公平公正”的原则,为它们分配有限的CPU资源。
但在SMP系统中,特别是支持CPU hotplug的系统中,调度器需要多操一份心,即:
CPU资源可以在任何时候增加或者删除。增加的时候,需要将新增的资源分配给等待的task。删除的时候,需要将那些运行在这些CPU上的task,转移到其它尚存的CPU上(这个过程称作migration)。
要达到上面的目的,调度器需要监视CPU hotplug有关的每一个风吹草动。由于调度器和两个独立的模块,kernel通过notifier机制(“”中有提及,但没有过多介绍)实现这一功能。
简言之,每当系统的CPU资源有任何变动,模块就会通知调度器,调度器根据相应的event(CPU_DOWN_FAILED、CPU_DOWN_PREPARE等),调用set_cpu_active接口,将某个CPU添加到active mask或者移出active mask。这就是active CPU的意义:
从调度器的角度,CPU的状态,即是否对调度器可见,或者说,调度器是否可以把task分配到这个CPU上运行。
注2:由此可知,active状态,只是为了方便调度器操作,抽象出的状态,和CPU电源管理之间没有耦合,后面就不在涉及这部分内容。
3. CPU的控制流程
CPU的控制流程,可以总结为up和down两种行为(和“.\kernel\cpu.c”中的cpu_up、cpu_down两个接口对应),up指CPU的启动过程,down指相反地过程。
根据CPU的发展过程,up和down的行为又可以分为三类:单核CPU的up/down;多核CPU的up/down;hotplugable CPU的up/down。下面让我们对这几种情况做一下简单的介绍。
3.1 单核CPU的控制流程
单核时代,只有一个CPU core,因此CPU的up/down,就是软件的整个生命周期(也就无所谓up/down了),如下:
1)系统上电,CPU从ROM代码执行,经bootloader(非必须),将控制权交给linux kernel。这就是cpu up的过程。
2)系统运行(一大堆省略号)。
3)由linux kernel及其进程调度算法所决定,不允许系统在没有CPU资源的情况下运行(这也是boot CPU的由来),所以系统的整个运行过程中,CPU都是up状态。
4)系统关闭,cpu down。
3.2 多核CPU的控制流程
linux kernel对待SMP系统的基本策略是:指定一个boot CPU,完成系统的初始化,然后再启动其它CPU。过程如下:
1)boot CPU启动,其up/down的控制流程和生命周期,和单核CPU一样。
2)boot CPU启动的过程中,调用cpu_up接口,启动其它CPU(称作secondary CPUs),使它们变成online状态(具体可参考“”)。这就是secondary CPUs的up过程。
3)由于CPU不支持hotplug功能,因此所有CPU只能up,不能down。直到系统关闭,才是cpu down。
3.3 CPU hotplug的控制流程
对于支持CPU hotplug功能的平台来说,可以在系统启动后的任意时刻,关闭任意一个secondary CPU(对ARM平台来说,CPU0或者说boot CPU,是不可以被关闭的),并在需要的时候,再次打开它。因此,相应的CPU控制流程如下:
1)boot CPU启动,其up/down的控制流程和生命周期,和单核CPU一样。
2)boot CPU启动的过程中,调用cpu_up接口,启动secondary CPU,使它们变成online状态,这是secondary CPUs的up过程的一种。
<font color="#)在系统负荷较低、或者不需要使用的时候,调用cpu_down接口,关闭不需要使用的secondary CPU,这是secondary CPUs的down过程。
<font color="#)在需要的时候,再次调用cpu_up接口,启动处于down状态的CPU,这是secondary CPUs的up过程的另一种。
有关CPU hotplug的具体说明,可参考后面描述。
4. CPU hotplug
4.1 CPU hotplug的时机
在kernel/cpu.c中,cpu_up接口,只会在使能了CONFIG_SMP配置项(意味着是SMP系统)后才会提供。而cpu_down接口,则只会在使能了CONFIG_HOTPLUG_CPU配置项(意味着支持CPU hotplug)后才会提供。
在当前kernel实现中,只支持通过sysfs的形式,关闭或打开CPU(当然,如果需要可以自定义一些方法,实现动态开关核的功能,本文就不在描述了),例如:
echo 0 > /sys/devices/system/cpu/cpuX/online&#160;&#160;&#160;&#160;&#160; # 关闭CPU
echo 1 > /sys/devices/system/cpu/cpuX/online&#160;&#160;&#160;&#160;&#160; # 打开CPU
另外,CPU hotplug还受“maxcpus”命令行参数影响:
系统启动的时候,可以通过命令行参数“maxcpus”,告知kernel本次启动所使用的CPU个数,该个数可以小于等于possible CPU的个数。系统初始化时,只会把“maxcpus”所指定个数的CPU置为present状态,具体可参考上面2.2小节所描述的smp_prepare_cpus的代码实现。
因此,CPU hotplug只能管理“maxcpus”所指定个数的CPU,具体可参考后面_cpu_up的流程分析。
注3:蜗蜗对这部分的理解,和“Documentation\cpu-hotplug.txt”中的描述有出入,文档是这样描述的:
maxcpus=n&#160;&#160;&#160; Restrict boot time cpus to n. Say if you have 4 cpus, using
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; maxcpus=2 will only boot 2. You can choose to bring the
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; other cpus later online, read FAQ's for more info.
它说其它CPU可以在后边被online,但从代码逻辑来说,没有机会online啊!先存疑吧!!
4.2 CPU hotplug的过程分析
CPU online的软件流程如下:
echo 0 > /sys/devices/system/cpu/cpuX/online
&#160;&#160;&#160;&#160;&#160;&#160;&#160; online_store(drivers/base/core.c)
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; device_online(drivers/base/core.c)
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; cpu_subsys_online(drivers/base/cpu.c)
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; cpu_up(kernel/cpu.c)
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; _cpu_up(kernel/cpu.c)
CPU offline的流程和online类似,不再详细介绍。这两个操作,最终是由cpu_up/cpu_down(也即_cpu_up/_cpu_down)两个接口实现的,下面我们重点分析这两个接口。
注4:内核中经常有这样的函数,xxx、_xxx或者__xxx,区别是一个或者两个下划线,其中的含义是:
xxx接口,通常需要由某个锁保护,一般提供给其它模块调用。它会直接调用_xxx接口;
_xxx接口,则不需要保护,一般由模块内部在确保安全的情况下调用。有时,外部模块确信可行(不需要保护),也可能会直接调用;
__xxx接口,一般提供给arch-dependent的软件层实现,比如这里的arch/arm64/kernel/xxx.c。
理解这些含义后,会加快我们阅读代码的速度,另外,如果直接写代码,也尽量遵守这样的原则,以便使自己的代码更规范、更通用。
4.3 cpu_up流程分析
cpu_up的基本流程如下所示:
其要点包括:
1)up前后,发送PREPARE、ONLINE、STARTING等notify,以便让关心者作出相应的动作,例如调度器、RCU、workqueue等模块,都需要关注CPU的hotplug动作,以便进行任务的重新分配等操作。
2)执行Arch-specific相关的boot操作,将CPU boot起来,最终通过secondary_start_kernel接口,停留在per-CPU的idle线程上。
下面我们结合代码,对上述过程做一个简单的分析。
4.3.1 per-CPU的idle线程
我们在“”的系列文章中,已经分析过linux cpuidle有关的工作原理,但却没有提及cpuidle的源头,这里我们补充回来。
首先,boot CPU在执行初始化动作的时候,会通过“smp_init—>idle_threads_init—>idle_init”的调用,为每个CPU创建一个idle线程,如下:
1: /* kernel/smpboot.c */
2: static inline void idle_init(unsigned int cpu)
struct task_struct *tsk = per_cpu(idle_threads, cpu);
if (!tsk) {
tsk = fork_idle(cpu);
if (IS_ERR(tsk))
pr_err("SMP: fork_idle() failed for CPU %u\n", cpu);
per_cpu(idle_threads, cpu) =
该接口的本质是,为每个CPU fork一个idle thread(由struct task_struct结构表示),并保存在一个per-CPU的全局变量(idle_threads)中。
此时,idle thread只是一个task结构,并没有执行。那它最终怎么执行的呢?我们继续往后面看。
4.3.2 arch-specific CPU boot
_cpu_up接口会在完成一些准备动作之后,调用平台相关的__cpu_up接口,由平台代码完成具体的up操作,如下:
1: static int _cpu_up(unsigned int cpu, int tasks_frozen)
int ret, nr_calls = 0;
void *hcpu = (void *)(long)
unsigned long mod = tasks_frozen ? CPU_TASKS_FROZEN : 0;
struct task_struct *
cpu_hotplug_begin();
if (cpu_online(cpu) || !cpu_present(cpu)) {
ret = -EINVAL;
idle = idle_thread_get(cpu);
if (IS_ERR(idle)) {
ret = PTR_ERR(idle);
ret = smpboot_create_threads(cpu);
ret = __cpu_notify(CPU_UP_PREPARE | mod, hcpu, -1, &nr_calls);
if (ret) {
nr_calls--;
pr_warn("%s: attempt to bring up CPU %u failed\n",
__func__, cpu);
/* Arch-specific enabling code. */
ret = __cpu_up(cpu, idle);
if (ret != 0)
BUG_ON(!cpu_online(cpu));
/* Wake the per cpu threads */
smpboot_unpark_threads(cpu);
/* Now call notifier in preparation. */
cpu_notify(CPU_ONLINE | mod, hcpu);
45: out_notify:
if (ret != 0)
__cpu_notify(CPU_UP_CANCELED | mod, hcpu, nr_calls, NULL);
cpu_hotplug_done();
准备动作包括:
1)获取idle thread的task指针,该指针最终会以参数的形式传递给arch-specific代码。
2)创建一个用于管理CPU hotplug动作的线程(smpboot_create_threads),该线程的具体意义,后面会再说明。
3)发送CPU_UP_PREPARE notify。
以ARM64为例,__cpu_up的内部实现如下:
1: /* arch/arm64/kernel/smp.c */
2: int __cpu_up(unsigned int cpu, struct task_struct *idle)
* We need to tell the secondary core where to find its stack and the
* page tables.
secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
__flush_dcache_area(&secondary_data, sizeof(secondary_data));
* Now bring the CPU into our world.
ret = boot_secondary(cpu, idle);
if (ret == 0) {
* CPU was successfully started, wait for it to come online or
* time out.
wait_for_completion_timeout(&cpu_running,
msecs_to_jiffies(1000));
if (!cpu_online(cpu)) {
pr_crit("CPU%u: failed to come online\n", cpu);
ret = -EIO;
pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
secondary_data.stack = NULL;
该接口以idle thread的task指针为参数,完成如下动作:
1)将idle线程的堆栈,保存在一个名称为secondary_data的全局变量中(这地方很重要,后面再介绍其中的奥妙)。
2)执行boot_secondary接口,boot CPU,具体的流程,可参考“”中的描述。
3)boot_secondary返回后,等待对应的CPU切换为online状态。
4.3.3 secondary_startup
“” 4.1小节,分析了使用SPIN TABLE cpu ops的情况下,boot_secondary到secondary_startup的流程(其它cpu ops类似),本文继续secondary_startup的分析。
该接口位于arch/arm64/kernel/head.S中,负责secondary CPU启动后的后期操作,如下:
1: ENTRY(secondary_startup)
* Common entry point for secondary CPUs.
x22, midr_el1
// x22=cpuid
lookup_processor_type
// x23=current cpu_table
x23, __error_p
// invalid processor (x23=0)?
x25, x26, x28
// x25=TTBR0, x26=TTBR1
x12, [x23, #CPU_INFO_SETUP]
x12, x12, x28
// __virt_to_phys
// initialise processor
x21, =secondary_data
x27, =__secondary_switched
// address to jump to after enabling the MMU
__enable_mmu
19: ENDPROC(secondary_startup)
21: ENTRY(__secondary_switched)
// get secondary_data.stack
secondary_start_kernel
26: ENDPROC(__secondary_switched)
我们重点关注上面16~17行,以及21~26行的__secondary_switched,__secondary_switched会将保存在secondary_data全局变量中的堆栈取出,保存在该CPU的SP中,并跳转到secondary_start_kernel继续执行。思考一下其中的意义:
我们都知道,CPU启动后,需要先配置好堆栈,才能进行后续的函数调用,这里使用的是该CPU idle thread的堆栈。就这么简单吗?当然不是,看一下kernel中“current”指针(获取当前task结构的宏定义)的实现方法:
1: #define current get_current()
2: #define get_current() (current_thread_info()->task)
4: static inline struct thread_info *current_thread_info(void)
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
有没有豁然开朗的感觉?通过CPU的SP指针,是可以获得CPU的当前task的(这和linux kernel进程管理的实现有关,我们不深究)。也就是说,当CPU SP被赋值为idle thread的堆栈的那一瞬间,当前的上下文已经是idle thread了!!
至于后面的secondary_start_kernel,就比较简单了,使能GIC、Timers,设置CPU为online状态,使能本地IRQ中断。等等。最后,调用cpu_startup_entry,进入cpuidle的loop中,已经和“”中描述接上了,自此,CPU UP完成。
4.4 cpu_down流程
cpu_down是cpu_up的反过程,用于将某个CPU从系统中移出。从表面上看,它和cpu_up的过程应该类似,但实际上它的处理过程却异常繁琐、复杂,同时牵涉到非常多的进程调度的知识,鉴于篇幅,本文就不再继续分析了。如果有机会,后面再专门用一篇文章分析这个过程。
另外,前面提到的smpboot有关的内容,也和cpu_down的过程有关,也就不再介绍了。
由本文的分析可知,cpu control有关的过程,其本身的逻辑比较简单,复杂之处在于:与此相关的系统服务(任务、中断、timer等等)的迁移。如果要理解这个过程,就必须有深厚的进程调度、中断管理等背景知识作支撑。不着急,来日方长,有机会我们继续分析。
原创文章,转发请注明出处。蜗窝科技,。

我要回帖

更多关于 cpu hotplug 的文章

 

随机推荐