天天看點

Linux核心對CPU熱插拔的支援

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

繼續閱讀