Linux内核对CPU热插拔的支持

CPU hotplug Support in Linux(tm) Kernel

 

                   Maintainers:

                   CPU Hotplug Core:

                            Rusty Russell

                            Srivatsa Vaddagiri

                   i386:

                            Zwane Mwaikambo

                   ppc64:

                            Nathan Lynch

                            Joel Schopp

                   ia64/x86_64:

                            Ashok Raj

                   s390:

                            Heiko Carstens

 

Authors: Ashok Raj

Lots of feedback: Nathan Lynch ,

              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。如果你有4CPU,但maxcpus=2,就只能启动2CPU。但随后可以将更多的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使用ACPIMADT表中被禁用的局部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

         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

         get_online_cpus() and put_online_cpus():  [这两个函数实际上是对cpu_online_map的加解锁。]

         上面的函数可以约束[inhibit]CPU的热插拔操作。这两个函数实际上是在操作cpu_hotplug.refcount。当cpu_hotplug.refcount0时,不能改变cpu_online_map。如果仅仅需要避免CPU被禁用,也可以在临界区前后使用preempt_disable()/preempt_enable()。但是需要注意的是,临界区中不能调用任何能够引起睡眠或将此进程调度走的函数。只要用来关闭处理器的函数stop_machine_run()被调用,preempt_disable()就会执行。

 

 

CPU热插拔的FAQ

Q:如何使我的内核能够支持处理器热插拔?

A:     make defconfig时使能CPU热插拔的支持:

         "Processor type and Features" -> Support for Hotpluggable CPUs

         另外还需打开CONFIG_HOTPLUGCONFIG_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是否已挂载。请注意输出中是否有如下的语句。

         ....

         none on /sys type sysfs (rw)

         ....

这表明/sys尚未挂载,请执行如下操作。

         #mkdir /sysfs

         #mount -t sysfs sys /sys

         现在可以看到与所有系统中已存在的CPU对应的文件夹,下面是一个8路系统中的例子。

         #pwd

         #/sys/devices/system/cpu

         #ls -l

         total 0

         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.

         #To display 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

         static int __cpuinit foobar_cpu_callback(struct notifier_block *nfb,

                                                   unsigned long action, void *hcpu)

         {

                   unsigned int cpu = (unsigned long)hcpu;

 

                   switch (action) {

                   case CPU_ONLINE:

                   case CPU_ONLINE_FROZEN:

                            foobar_online_action(cpu);

                            break;

                   case CPU_DEAD:

                   case CPU_DEAD_FROZEN:

                            foobar_dead_action(cpu);

                            break;

                   }

                   return NOTIFY_OK;

         }

 

         static struct 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是否真的被关掉了。最好还是去仔细看一下为其它体系结构而编写的热插拔代码。尤其是在这种体系结构上,如何将CPUidle()循环中关掉。__cpu_die()通常是在等待某些每CPU状态被设置,以确保处理器关闭例程被正确地调用。

 

Q  当一些与某个特殊的CPU相关的工作正在执行时,我需要确保这个特殊的CPU不会被移除。

A:     2种方式,可以使一个CPU不会被移除。如果你的代码可以在中断上下文中执行,那么就使用函数smp_call_function_single(),否则就使用work_on_cpu()。需要注意的是,work_on_cpu()执行的很慢,而且可能由于内存不够而失败。

         int my_func_on_cpu(int cpu)

         {

                   int err;

                   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();

                   return err;

         }

 

Q:我们如何确定有多少个CPU可以热插拔?

A:     至今,ACPI也没能明确地给出解决方法。UnisysNatalie指出,ACPIMADT(Multiple APIC 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_mesg CPU $ACTION event not supported

        exit 1

        ;;

         esac

你可能感兴趣的:(Linux内核对CPU热插拔的支持)