CPU hotplug Supportin Linux(tm) Kernel
Maintainers:
CPUHotplug Core:
RustyRussell <[email protected]>
SrivatsaVaddagiri <[email protected]>
i386:
ZwaneMwaikambo <[email protected]>
ppc64:
NathanLynch <[email protected]>
JoelSchopp <[email protected]>
ia64/x86_64:
AshokRaj <[email protected]>
s390:
HeikoCarstens <[email protected]>
Authors: Ashok Raj<[email protected]>
Lots of feedback: Nathan Lynch<[email protected]>,
Joel Schopp [email protected]
翻译: Arethe Qin
引言
系统体系结构上的现代高级特性使处理器具备了错误报告与错误更正的能力。CPU体系结构支持分区,这使得单个CPU的计算资源也能够满足虚拟机的需要。一些OEM已经支持了NUMA硬件的热插拔,物理节点的插入与移除需要处理器热插拔技术的支持。
这种高级特性需要内核在必要时能移除正在使用的CPU.[provisioning reasons?]比如,为了RAS的需要,必须将一个执行恶意代码的CPU保持在系统执行路径之外。因此,在Linux内核中需要支持CPU热插拔技术。
一个更具新意的CPU热插拔的应用是对SMP系统的挂起/恢复的支持。多核或HT技术使得在一台笔记本上也能运行SMP内核,但是目前的支持挂起/恢复的SMP技术还在研发中。
CPU 热插拔的一般概况
命令行设置
maxcpus=n
在系统启动的CPU个数限制为n。如果你有4个CPU,但maxcpus=2,就只能启动2个CPU。但随后可以将更多的CPU加入到系统中,更多的信息可以在FAQ中获得。
additional_cpus=n (*)
这个选项可以限制可热插拔的CPU的个数。通过这个选项,我们可以计算出系统能够支持的CPU的最大数目:
cpu_possible_map = cpu_present_map +additional_cpus
cede_offline = {"off","on"}
在扩展的pseries平台上,此选项可以禁用/使能将一个离线处理器设为扩展的H_CEDE状态。如果没有特别说明,cede_offline默认被设为”on”。
加(*)的选项仅适用于以下平台:
-ia64
Ia64使用ACPI的MADT表中被禁用的局部apic的数量来确定潜在的可热插拔CPU的数量。在实现中,仅应使用此方法获取CPU数目,决对不能依赖于上述表格中描述被禁用APIC数目的apicid的值。不能在BIOS中将这些可热插拔的CPU设为不可用的设备。参数"additional_cpus=x"可以用来描述cpu_possible_map中可热插拔的CPU。
possible_cpus=n
[s390,x86_64]使用该选项来设置可热插拔的CPU。该选项设置了cpu_possible_map中的possible_cpu对应的位。因此,即使系统重启,也应保证此位图中的位的数量为常数。
CPU位图及相关的一些问题
[更多关于cpumaps及操作原语的信息,请关注include/linux/cpumask.h,该文件中包含了更多的信息。]
cpu_possible_map: 系统中可用CPU的位图(CPU不一定存在于系统中,也包括待插入的CPU)。在为每CPU变量分配启动时内存时,需要使用此位图,这些变量所占的内存在插入或溢出CPU时不会进行相应的扩展和释放。一旦在启动时的探测阶段[discovery phase]完成了对此位图的设置,它在整个系统执行期间都是静态的,也就是说在任何时候都无需设置或清除任何一位。如果此位图严格的符合系统的具体情况,便能节省一些启动时内存[boot time memory]。下面我们会详细的解说x86_64平台是如何检查此变量的。
cpu_online_map: 当前在用CPU的位图。__cpu_up()函数在某个CPU已经可以用于内核调度并能够接收设备中断时,可以设置此位图中相应的位。当使用__cpu_disable()函数禁用某个CPU时,在包括中断在内的所有的系统服务都被迁移到其它的CPU之前,需要清除此位图中相应的位。
cpu_present_map: 当前存在于系统中的CPU的位图。它们不一定在用[online]。当物理的热插拔操作被相关的子系统(如,ACPI)处理之后,需要根据热插拔的情况对改位图进行相应的修改。目前还没有加锁规则。该位图典型的应用是在启动时初始化拓扑结构,而此时热插拔是禁用的。
你真的无需操作系统中的任何CPU位图。在大多数情况下,它们都应是只读的。在设置一个每cpu变量时,几乎总是使用cpu_possible_map/for_each_possible_cpu()来进行循环。
只能使用cpumask_t来描述一个CPU位图。
#include<linux/cpumask.h>
for_each_possible_cpu - 遍历 cpu_possible_map
for_each_online_cpu - 遍历cpu_online_map
for_each_present_cpu - 遍历cpu_present_map
for_each_cpu_mask(x,mask) - 遍历描述CPU集合的位图:mask.
#include<linux/cpu.h>
get_online_cpus()and put_online_cpus(): [这两个函数实际上是对cpu_online_map的加解锁。]
上面的函数可以约束[inhibit]CPU的热插拔操作。这两个函数实际上是在操作cpu_hotplug.refcount。当cpu_hotplug.refcount非0时,不能改变cpu_online_map。如果仅仅需要避免CPU被禁用,也可以在临界区前后使用preempt_disable()/preempt_enable()。但是需要注意的是,临界区中不能调用任何能够引起睡眠或将此进程调度走的函数。只要用来关闭处理器的函数stop_machine_run()被调用,preempt_disable()就会执行。
CPU热插拔的FAQ
Q:如何使我的内核能够支持处理器热插拔?
A: 在makedefconfig时使能CPU热插拔的支持:
"Processortype and Features" -> Support for Hotpluggable CPUs
另外还需打开CONFIG_HOTPLUG和CONFIG_SMP选项。如果需要支持SMP的挂起/恢复,也需要打开CONFIG_HOTPLUG_CPU选项。
Q:哪些体系结构支持CPU热插拔?
A:在2.6.14内核中,如下的体系结构都支持CPU热插拔。
i386(Intel), ppc, ppc64, parisc, s390, ia64 and x86_64
Q: 如果测试一个新编译的内核是否支持热插拔?
A: 请注意/sys中的一个文件。
首先使用mount命令确定sysfs是否已挂载。请注意输出中是否有如下的语句。
....
noneon /sys type sysfs (rw)
....
这表明/sys尚未挂载,请执行如下操作。
#mkdir/sysfs
#mount-t sysfs sys /sys
现在可以看到与所有系统中已存在的CPU对应的文件夹,下面是一个8路系统中的例子。
#pwd
#/sys/devices/system/cpu
#ls-l
total0
drwxr-xr-x 10 root root 0 Sep 19 07:44 .
drwxr-xr-x 13 root root 0 Sep 19 07:45 ..
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu0
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu1
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu2
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu3
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu4
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu5
drwxr-xr-x 3 root root 0 Sep 19 07:44 cpu6
drwxr-xr-x 3 root root 0 Sep 19 07:48 cpu7
在每个文件夹下,都有名为“online”的文件,这是一个控制文件[control file. I like the word.],可以用来使能/禁用[online/offline]一个处理器。
Q:热插/热拔是否对应物理上对处理器的添加/移除?
A: 这里对热插/热拔的使用并不是与其字面上的意义完全一致。CONFIG_HOTPLUG_CPU使得内核能够进行逻辑上的使能与禁用。为了支持物理上的添加/移除,需要一些BIOS回调函数,并且还需要平台具有一些类似于PCI热插拔按钮之类的机制。CONFIG_ACPI_HOTPLUG_CPU使得ACPI能够支持CPU在物理上的添加/移除。
Q:如何在逻辑上禁用一个CPU?
A: 执行如下操作。
#echo 0 > /sys/devices/system/cpu/cpuX/online
如果逻辑上的禁用成功,检查
#cat/proc/interrupts
在此文件中,将看不到被移除的CPU对应的列了。当CPU被移除后,它的online文件为0,否则为1.
#Todisplay the current cpu state.
#cat/sys/devices/system/cpu/cpuX/online
Q: 为什么在一些系统中无法移除CPU0?
A:一些体系结构特别依赖于某个特殊的CPU。
比如在IA64平台上,我们能够将平台中断发送给OS。也就是平台错误更正中断[Corrected Platform Error Interrupts(CPEI)]。如果ACPI配置正确,我们没有办发改变目标CPU。因此,如果当前的ACPI版本不支持这样的重定向,这样的CPU就是不可移除的。[其实就是说某些中断只能发送给特定的CPU。]
这种情况下,你会发现cpu0没有online文件。
Q: 如果一个特殊的CPU不能被移除,我如何找出它?
A: 这个依赖于具体的实现方法,在一些体系结构上,我们找到这些CPU的“online”文件。如果我们能够提前获知此处理不能被移除,这种方法不错。
在某些情况下,可以在运行时进行检查,即,如果你希望移除最后一个CPU,这是不允许的。此时,“echo”命令会给出一个错误提示。
Q:当一个CPU在逻辑上被移除时,会发生什么?
A:将会发生下面的事情,排列是无序的 J。
- 内核中的模块会接收到一个通知[notification],对应的事件是CPU_DOWN_PREPARE 或者CPU_DOWN_PREPARE_FROZEN,具体是哪个事件则依赖于CPU被移除时,是否有任务被“冷冻”,任务被冷冻的原因是正在执行挂起操作。
- 此CPU上的所有进程都被迁移到新的CPU上。新CPU通过每个进程的当前处理器设置(cpuset)进行选择,这些设置可能是所有在用CPU的子集。
- 所有定向到此CPU上的中断都被迁移到新的CPU上。
- 定时器/BH/task lets也将被迁移到新的CPU上。
- 一旦所有的服务都被迁移了,内核便调用一个体系结构相关[arch specific]的例程__cpu_disable()来执行体系结构相关的清理工作。
- 如果上面的工作也完成了,一个代表清理成功的事件将被发送,此事件为CPU_DEAD。(如果存在冰冻任务,相应的事件则为CPU_DEAD_FROZEN。也就是说在移除CPU时,系统正在执行挂起操作。)
当CPU_DOWN_PREPARE通知链被调用时,所有服务都应该被清除。当CPU_DEAD被调用时,不应在有任何东西运行于被移除的CPU上。
Q: 如果我的内核代码需要能够感知CPU的到达和离开,我如何正确地安排通知链?
A: 下面的代码给出了你的内核代码在收到一个通知时应做的工作。
#include<linux/cpu.h>
staticint __cpuinit foobar_cpu_callback(struct notifier_block *nfb,
unsigned long action, void *hcpu)
{
unsignedint cpu = (unsigned long)hcpu;
switch(action) {
caseCPU_ONLINE:
caseCPU_ONLINE_FROZEN:
foobar_online_action(cpu);
break;
caseCPU_DEAD:
caseCPU_DEAD_FROZEN:
foobar_dead_action(cpu);
break;
}
returnNOTIFY_OK;
}
staticstruct notifier_block __cpuinitdata foobar_cpu_notifer =
{
.notifier_call = foobar_cpu_callback,
};
你需要在初始化函数中调用函数register_cpu_notifier()。初始化函数可能具有2种类型:
1. Early init: 仅在启动处理器在用时才调用的初始化函数。
2. Late init: 在所有的处理器都在用后才调用的初始化函数。
对第一种情况,你应该在初始化函数中加入下面的代码。
register_cpu_notifier(&foobar_cpu_notifier);
对第二种情况,你应该将下面的代码加在初始化函数中。
register_hotcpu_notifier(&foobar_cpu_notifier);
如果在准备资源时出了任何错误,你的PREPARE通知链都将出错。这将终止活动,并随后发送一个CANCELED事件。
CPU_DEAD不应该失败,它仅仅是在通知一个好消息。当某个通知链在执行时发出了一个BAD通知时,则意味着可能会发生坏事情。
Q: 我的代码被调用的次数貌似并不等于所有被使能或正在运行的CPU的个数?
A: 是的,CPU通知链仅在新的CPU被使能或禁用时才会被调用。如果你需要为系统中的每个CPU都执行一段程序,请参考下面的代码。
for_each_online_cpu(i){
foobar_cpu_callback(&foobar_cpu_notifier,CPU_UP_PREPARE, i);
foobar_cpu_callback(&foobar_cpu_notifier,CPU_ONLINE, i);
}
Q:如果我想为一种新的体系结构开发CPU热插拔的支持,如何做才能使工作量最小?
A: 要使CPU热插拔的基础框架能够正确工作,需要做以下工作。
- 确定在Kconfig中添加了使能CONFIG_HOTPLUG_CPU的选项。
- __cpu_up(): 使能一个CPU时,面向体系结构的接口。
- __cpu_disable(): 关闭一个CPU时,面向体系结构的接口,在此函数返回时,不在有中断会被内核处理。局部APIC定时器等设备也被关掉了。
- __cpu_die(): 此函数用来确认某个CPU是否真的被关掉了。最好还是去仔细看一下为其它体系结构而编写的热插拔代码。尤其是在这种体系结构上,如何将CPU中idle()循环中关掉。__cpu_die()通常是在等待某些每CPU状态被设置,以确保处理器关闭例程被正确地调用。
Q: 当一些与某个特殊的CPU相关的工作正在执行时,我需要确保这个特殊的CPU不会被移除。
A: 有2种方式,可以使一个CPU不会被移除。如果你的代码可以在中断上下文中执行,那么就使用函数smp_call_function_single(),否则就使用work_on_cpu()。需要注意的是,work_on_cpu()执行的很慢,而且可能由于内存不够而失败。
intmy_func_on_cpu(int cpu)
{
interr;
get_online_cpus();
if(!cpu_online(cpu))
err= -EINVAL;
else
#if NEEDS_BLOCKING
err= work_on_cpu(cpu, __my_func_on_cpu, NULL);
#else
smp_call_function_single(cpu,__my_func_on_cpu, &err,
true);
#endif
put_online_cpus();
returnerr;
}
Q:我们如何确定有多少个CPU可以热插拔?
A: 至今,ACPI也没能明确地给出解决方法。Unisys的Natalie指出,ACPI的MADT(MultipleAPIC Description Tables)可以将系统中可用的CPU标记为禁用状态。
Andi实现了一些简单的启发式方法,可以统计出MADT表中被禁用的CPU的个数,这些CPU就是可以用于热插拔的CPU。在没有被禁用的CPU的情况下,我们假设当前可用CPU的一半可以用于热插拔。
忠告:当前的ACPI MADT仅能提供256个表项,因为MADT中的apicid字段仅有8位。
用户空间的通知链
在而今的Linux中,对设备热插拔的支持已经相当普通了。我们可以利用设备热插拔机制来自动配置网络,USB以及PCI设备。一个热插拔事件可以唤醒一个代理脚本,用以执行配置任务。
你可以通过创建文件/etc/hotplug/cpu.agent在用户空间处理热插拔事件。
#!/bin/bash
#$Id: cpu.agent
#Kernel hotplug params include:
#ACTION=%s[online or offline]
#DEVPATH=%s
#
cd/etc/hotplug
../hotplug.functions
case$ACTION in
online)
echo`date` ":cpu.agent" add cpu >> /tmp/hotplug.txt
;;
offline)
echo`date` ":cpu.agent" remove cpu >>/tmp/hotplug.txt
;;
*)
debug_mesgCPU $ACTION event not supported
exit 1
;;
esac