記憶體熱插拔

本文件介紹了 Linux 對記憶體熱插拔的通用支援,重點是系統 RAM,包括 ZONE_MOVABLE 支援。

簡介

記憶體熱插拔允許在執行時增加和減少機器可用的物理記憶體大小。在最簡單的情況下,它包括在執行時物理地插入或拔出 DIMM,並與作業系統協調。

記憶體熱插拔用於各種目的

  • 機器可用的物理記憶體可以在執行時進行調整,從而升級或降級記憶體容量。這種動態記憶體大小調整,有時被稱為“按需容量”,經常用於虛擬機器和邏輯分割槽。

  • 更換硬體,例如 DIMM 或整個 NUMA 節點,而無需停機。一個例子是更換出現故障的記憶體模組。

  • 透過物理拔出記憶體模組或從邏輯上拔出 Linux 中的(部分)記憶體模組來降低能耗。

此外,Linux 中的基本記憶體熱插拔基礎設施現在也用於將持久記憶體、其他效能差異記憶體和保留記憶體區域作為普通系統 RAM 暴露給 Linux。

Linux 僅在選定的 64 位架構(例如 x86_64、arm64、ppc64 和 s390x)上支援記憶體熱插拔。

記憶體熱插拔粒度

Linux 中的記憶體熱插拔使用 SPARSEMEM 記憶體模型,該模型將物理記憶體地址空間劃分為大小相同的塊:記憶體節。記憶體節的大小取決於架構。例如,x86_64 使用 128 MiB,ppc64 使用 16 MiB。

記憶體節被組合成稱為“記憶體塊”的塊。記憶體塊的大小取決於架構,並對應於可以熱插拔的最小粒度。除非架構另有規定,否則記憶體塊的預設大小與記憶體節大小相同。

所有記憶體塊都具有相同的大小。

記憶體熱插拔階段

記憶體熱插拔包括兩個階段

  1. 將記憶體新增到 Linux

  2. 上線記憶體塊

在第一階段,分配並初始化元資料,例如記憶體對映(“memmap”)和直接對映的頁表,並建立記憶體塊;後者還建立 sysfs 檔案以管理新建立的記憶體塊。

在第二階段,新增的記憶體將暴露給頁面分配器。在此階段之後,記憶體將顯示在系統的記憶體統計資訊中,例如可用記憶體和總記憶體。

記憶體熱移除階段

記憶體熱移除包括兩個階段

  1. 下線記憶體塊

  2. 從 Linux 中移除記憶體

在第一階段,記憶體再次從頁面分配器中“隱藏”,例如,透過將繁忙記憶體遷移到其他記憶體位置並從頁面分配器中刪除所有相關的空閒頁面。在此階段之後,該記憶體不再顯示在系統的記憶體統計資訊中。

在第二階段,移除記憶體塊並釋放元資料。

記憶體熱插拔通知

Linux 有多種方式收到記憶體熱插拔事件的通知,以便它可以開始新增熱插拔記憶體。此描述僅限於支援 ACPI 的系統;不描述特定於其他韌體介面或虛擬機器的機制。

ACPI 通知

支援 ACPI 的平臺(例如 x86_64)可以透過 ACPI 支援記憶體熱插拔通知。

通常,支援記憶體熱插拔的韌體定義一個記憶體類物件 HID “PNP0C80”。當收到有關新記憶體裝置熱插拔的通知時,ACPI 驅動程式會將記憶體熱插拔到 Linux。

如果韌體支援 NUMA 節點的熱插拔,它將定義一個物件 _HID “ACPI0004”、“PNP0A05”或“PNP0A06”。當收到有關熱插拔事件的通知時,ACPI 驅動程式會將所有分配的記憶體裝置新增到 Linux。

同樣,Linux 可以透過 ACPI 收到有關熱移除記憶體裝置或 NUMA 節點的請求的通知。ACPI 驅動程式將嘗試下線所有相關的記憶體塊,如果成功,則從 Linux 中熱移除記憶體。

手動探測

在某些架構上,韌體可能無法通知作業系統有關記憶體熱插拔事件。相反,必須從使用者空間手動探測記憶體。

探測介面位於

/sys/devices/system/memory/probe

只能探測完整的記憶體塊。透過提供記憶體塊的物理起始地址來探測各個記憶體塊

% echo addr > /sys/devices/system/memory/probe

這將導致建立範圍為 [addr, addr + memory_block_size) 的記憶體塊。

注意

不鼓勵使用探測介面,因為它很容易使核心崩潰,因為 Linux 無法驗證使用者輸入;此介面將來可能會被刪除。

記憶體塊的上線和下線

建立記憶體塊後,必須指示 Linux 實際使用該記憶體:必須“上線”記憶體塊。

在可以移除記憶體塊之前,Linux 必須停止使用記憶體塊的任何記憶體部分:必須“下線”記憶體塊。

可以將 Linux 核心配置為自動上線新增的記憶體塊,並且驅動程式在嘗試熱移除記憶體時自動觸發記憶體塊的下線。只有在下線成功後才能移除記憶體塊,並且驅動程式可能會在嘗試熱移除記憶體時觸發記憶體塊的下線。

手動上線記憶體塊

如果未啟用記憶體塊的自動上線,則使用者空間必須手動觸發記憶體塊的上線。通常,使用 udev 規則來自動化使用者空間中的此任務。

可以透過以下方式觸發記憶體塊的上線

% echo online > /sys/devices/system/memory/memoryXXX/state

或者,也可以

% echo 1 > /sys/devices/system/memory/memoryXXX/online

核心將自動選擇目標區域,具體取決於配置的 online_policy

可以顯式請求將離線記憶體塊與 ZONE_MOVABLE 關聯,方法是

% echo online_movable > /sys/devices/system/memory/memoryXXX/state

或者,可以顯式請求核心區域(通常是 ZONE_NORMAL),方法是

% echo online_kernel > /sys/devices/system/memory/memoryXXX/state

在任何情況下,如果上線成功,則記憶體塊的狀態將更改為“online”。如果失敗,記憶體塊的狀態將保持不變,並且上述命令將失敗。

自動上線記憶體塊

可以將核心配置為嘗試自動上線新新增的記憶體塊。如果停用此功能,則記憶體塊將保持離線狀態,直到從使用者空間顯式上線。

可以透過以下方式觀察配置的自動上線行為

% cat /sys/devices/system/memory/auto_online_blocks

可以透過將 onlineonline_kernelonline_movable 寫入該檔案來啟用自動上線,例如

% echo online > /sys/devices/system/memory/auto_online_blocks

與手動上線類似,使用 online,核心將自動選擇目標區域,具體取決於配置的 online_policy

修改自動上線行為只會影響所有後續新增的記憶體塊。

注意

在某些極端情況下,自動上線可能會失敗。核心不會重試。請注意,在預設配置中,自動上線不會失敗。

注意

ppc64 上的 DLPAR 忽略 offline 設定,並且仍將上線新增的記憶體塊;如果上線失敗,記憶體塊將再次被移除。

下線記憶體塊

在當前實現中,Linux 的記憶體下線將嘗試將所有可移動頁面從受影響的記憶體塊中遷移出去。由於大多數核心分配(例如頁表)是不可移動的,因此頁面遷移可能會失敗,因此會阻止記憶體下線成功。

讓 ZONE_MOVABLE 管理記憶體塊提供的記憶體可以顯著提高記憶體下線的可靠性;但是,在某些極端情況下,記憶體下線仍然會失敗。

此外,記憶體下線可能會重試很長時間(甚至永遠),直到使用者中止為止。

可以透過以下方式觸發記憶體塊的下線

% echo offline > /sys/devices/system/memory/memoryXXX/state

或者,也可以

% echo 0 > /sys/devices/system/memory/memoryXXX/online

如果下線成功,則記憶體塊的狀態將更改為“offline”。如果失敗,記憶體塊的狀態將保持不變,並且上述命令將失敗,例如,透過

bash: echo: write error: Device or resource busy

或透過

bash: echo: write error: Invalid argument

觀察記憶體塊的狀態

可以透過以下方式觀察記憶體塊的狀態(線上/離線/正在離線)

% cat /sys/devices/system/memory/memoryXXX/state

或者,也可以透過 (1/0)

% cat /sys/devices/system/memory/memoryXXX/online

對於線上記憶體塊,可以透過以下方式觀察管理區域

% cat /sys/devices/system/memory/memoryXXX/valid_zones

配置記憶體熱插拔

系統管理員可以透過多種方式配置記憶體熱插拔並與記憶體塊互動,特別是上線記憶體塊。

透過 Sysfs 配置記憶體熱插拔

可以透過 sysfs 在以下位置配置或檢查一些記憶體熱插拔屬性

/sys/devices/system/memory/

當前定義了以下檔案

auto_online_blocks

讀寫:設定或獲取新記憶體塊的預設狀態;配置自動上線。

預設值取決於 CONFIG_MHP_DEFAULT_ONLINE_TYPE 核心配置選項。

有關詳細資訊,請參閱記憶體塊的 state 屬性。

block_size_bytes

只讀:記憶體塊的大小(以位元組為單位)。

probe

只寫:透過提供物理起始地址從使用者空間手動新增(探測)選定的記憶體塊。

可用性取決於 CONFIG_ARCH_MEMORY_PROBE 核心配置選項。

uevent

讀寫:裝置子系統的通用 udev 檔案。

crash_hotplug

只讀:當由於記憶體的熱插拔/移除而導致系統記憶體對映發生更改時,如果核心更新 kdump 捕獲核心記憶體對映本身(透過 elfcorehdr 和其他相關的 kexec 段),則此檔案包含“1”,如果使用者空間必須更新 kdump 捕獲核心記憶體對映,則包含“0”。

可用性取決於 CONFIG_MEMORY_HOTPLUG 核心配置選項。

注意

啟用 CONFIG_MEMORY_FAILURE 核心配置選項後,將提供兩個附加檔案 hard_offline_pagesoft_offline_page 來觸發頁面的 hwpoisoning,例如,用於測試目的。請注意,此功能實際上與記憶體熱插拔或記憶體塊的實際下線無關。

透過 Sysfs 配置記憶體塊

每個記憶體塊都表示為一個可以上線或下線的記憶體塊裝置。所有記憶體塊的裝置資訊都位於 sysfs 中。每個存在的記憶體塊都列在 /sys/devices/system/memory 下,如下所示

/sys/devices/system/memory/memoryXXX

其中 XXX 是記憶體塊 ID;位數是可變的。

存在的記憶體塊表示該範圍內存在一些記憶體;但是,記憶體塊可能會跨越記憶體空洞。跨越記憶體空洞的記憶體塊無法下線。

例如,假設 1 GiB 記憶體塊大小。起始於 0x100000000 的記憶體的裝置是 /sys/devices/system/memory/memory4

(0x100000000 / 1Gib = 4)

此裝置覆蓋地址範圍 [0x100000000 ... 0x140000000)

當前定義了以下檔案

online

讀寫:簡化的介面,用於觸發上線/下線並觀察記憶體塊的狀態。上線時,會自動選擇區域。

phys_device

只讀:僅在 s390x 上使用的舊版介面,用於公開覆蓋的儲存增量。

phys_index

只讀:記憶體塊 ID (XXX)。

removable

只讀:舊版介面,指示記憶體塊是否可能可以離線。如今,當且僅當核心支援記憶體下線時,核心才會返回 1

state

讀寫:高階介面,用於觸發上線/下線並觀察記憶體塊的狀態。

寫入時,支援 onlineofflineonline_kernelonline_movable

online_movable 指定上線到 ZONE_MOVABLE。online_kernel 指定上線到記憶體塊的預設核心區域,例如 ZONE_NORMAL。online 讓核心自動選擇區域。

讀取時,可能會返回 onlineofflinegoing-offline

uevent

讀寫:裝置的通用 uevent 檔案。

valid_zones

只讀:當塊線上時,顯示它所屬的區域;當塊離線時,顯示當塊上線時哪個區域將管理它。

對於線上記憶體塊,可能會返回 DMADMA32NormalMovablenonenone 表示記憶體塊提供的記憶體由多個區域管理或跨越多個節點;此類記憶體塊無法下線。Movable 表示 ZONE_MOVABLE。其他值表示核心區域。

對於離線記憶體塊,第一列顯示核心在不進一步指定區域的情況下立即上線記憶體塊時將選擇的區域。

可用性取決於 CONFIG_MEMORY_HOTREMOVE 核心配置選項。

注意

如果啟用了 CONFIG_NUMA 核心配置選項,也可以透過位於 /sys/devices/system/node/node* 目錄中的符號連結訪問 memoryXXX/ 目錄。

例如

/sys/devices/system/node/node0/memory9 -> ../../memory/memory9

還將建立一個反向連結

/sys/devices/system/memory/memory9/node0 -> ../../node/node0

命令列引數

一些命令列引數會影響記憶體熱插拔處理。以下命令列引數是相關的

memhp_default_state

透過有效地設定 /sys/devices/system/memory/auto_online_blocks 來配置自動上線。

movable_node

在使用 contig-zones 上線策略時,配置核心中的自動區域選擇。設定後,核心在上線記憶體塊時將預設為 ZONE_MOVABLE,除非其他區域可以保持連續。

有關這些命令列引數的更通用描述,請參閱核心的命令列引數

模組引數

現在,memory_hotplug 子系統為模組引數提供了一個專用名稱空間,而不是額外的命令列引數或 sysfs 檔案。可以透過在命令列中使用 memory_hotplug. 來設定模組引數,例如

memory_hotplug.memmap_on_memory=1

可以透過以下方式觀察(甚至在執行時修改)它們

/sys/module/memory_hotplug/parameters/

當前定義了以下模組引數

memmap_on_memory

讀寫:從新增的記憶體塊本身為 memmap 分配記憶體。即使啟用,實際支援也取決於各種其他系統屬性,應僅視為是否需要該行為的提示。

雖然從記憶體塊本身分配 memmap 可以降低記憶體熱插拔失敗的可能性,並且在任何情況下都將 memmap 保留在同一 NUMA 節點上,但它會以一種較大的粒度上的巨大頁面無法在熱插拔記憶體上形成的方式來分割物理記憶體。

使用值“force”可能會由於 memmap 大小限制而導致記憶體浪費。例如,如果記憶體塊的 memmap 需要 1 MiB,但 pageblock 大小為 2 MiB,則將浪費 1 MiB 的熱插拔記憶體。請注意,在某些情況下仍然無法強制執行該功能:例如,如果 memmap 小於單個頁面,或者如果架構不支援所有配置中的強制模式。

online_policy

讀寫:設定用於自動區域選擇的基本策略,在上線記憶體塊而不指定目標區域時使用。在新增此引數之前,contig-zones 一直是核心預設值。配置線上策略並上線記憶體後,不應再更改該策略。

設定為 contig-zones 時,核心將嘗試保持區域連續。如果記憶體塊與多個區域或沒有區域相交,則行為取決於 movable_node 核心命令列引數:如果設定,則預設為 ZONE_MOVABLE,如果未設定,則預設為適用的核心區域(通常是 ZONE_NORMAL)。

設定為 auto-movable 時,如果根據配置和記憶體裝置詳細資訊,核心將嘗試上線記憶體塊到 ZONE_MOVABLE。使用此策略,可以避免在最終熱插拔大量記憶體後出現區域不平衡,並且仍然希望能夠儘可能可靠地熱移除記憶體,這在虛擬化環境中非常理想。此策略忽略 movable_node 核心命令列引數,並且並不真正適用於需要它的環境(例如,具有可熱移除節點的裸機),在這種環境中,熱插拔記憶體可能會在啟動期間透過韌體提供的記憶體對映早期暴露給系統,而不是在啟動期間稍後檢測、新增和上線(例如由 virtio-mem 或一些實現模擬 DIMM 的虛擬機器監控程式完成)。例如,熱插拔 DIMM 將完全上線到 ZONE_MOVABLE 或完全上線到 ZONE_NORMAL,而不是混合上線。再舉一個例子,儘可能多的屬於 virtio-mem 裝置的記憶體塊將上線到 ZONE_MOVABLE,特殊情況下一次只能一起熱移除的記憶體塊單元。此策略不防止 ZONE_MOVABLE 的問題設定,並且不會在記憶體塊上線後動態更改其區域。

auto_movable_ratio

讀寫:為 auto-movable 線上策略設定最大 MOVABLE:KERNEL 記憶體比率(以 % 為單位)。該比率是否僅適用於跨所有 NUMA 節點的系統,還是也適用於每個 NUMA 節點,取決於 auto_movable_numa_aware 配置。

所有核算都基於區域中的存在記憶體頁面,並結合每個記憶體裝置的核算。專用於 CMA 分配器的記憶體被視為 MOVABLE,即使駐留在核心區域之一上。可能的比率取決於實際工作負載。核心預設值為“301”%,例如,允許為 8 GiB VM 熱插拔 24 GiB,並在許多設定中自動將所有熱插拔記憶體上線到 ZONE_MOVABLE。額外的 1% 處理某些頁面不存在的情況,例如,由於某些韌體分配。

請注意,一個記憶體裝置提供的 ZONE_NORMAL 記憶體不允許另一個記憶體裝置擁有更多的 ZONE_MOVABLE 記憶體。例如,將熱插拔 DIMM 的記憶體上線到 ZONE_NORMAL 不允許另一個熱插拔 DIMM 自動上線到 ZONE_MOVABLE。相反,透過 virtio-mem 裝置熱插拔到 ZONE_NORMAL 的記憶體將允許在同一 virtio-mem 裝置中擁有更多的 ZONE_MOVABLE 記憶體。

auto_movable_numa_aware

讀寫:配置 auto-movable 線上策略中的 auto_movable_ratio 是否除了適用於跨所有 NUMA 節點的整個系統外,還適用於每個 NUMA 節點。核心預設值為“Y”。

當處理應完全可熱移除的 NUMA 節點時,停用 NUMA 感知可能很有用,如果可能,自動將記憶體完全上線到 ZONE_MOVABLE。

引數可用性取決於 CONFIG_NUMA。

ZONE_MOVABLE

ZONE_MOVABLE 是一種更可靠的記憶體下線的重要機制。此外,讓 ZONE_MOVABLE 而不是核心區域之一管理系統 RAM 可以增加可能的透明巨頁和動態分配的巨頁的數量。

大多數核心分配是不可移動的。重要的例子包括記憶體對映(通常是 1/64 的記憶體)、頁表和 kmalloc()。此類分配只能從核心區域提供。

大多數使用者空間頁面(例如匿名記憶體)和頁面快取頁面是可移動的。此類分配可以從 ZONE_MOVABLE 和核心區域提供。

只有可移動分配是從 ZONE_MOVABLE 提供的,從而導致不可移動分配被限制在核心區域。如果沒有 ZONE_MOVABLE,則絕對不能保證記憶體塊可以成功下線。

區域不平衡

讓 ZONE_MOVABLE 管理太多的系統 RAM 稱為區域不平衡,這會損害系統或降低效能。例如,核心可能會崩潰,因為它為不可移動分配耗盡了可用記憶體,儘管 ZONE_MOVABLE 中仍有大量可用記憶體。

通常,高達 3:1 甚至 4:1 的 MOVABLE:KERNEL 比率是可以的。由於記憶體對映的開銷,63:1 的比率絕對不可能。

實際的安全區域比率取決於工作負載。極端情況(例如頁面過度長期固定)可能根本無法處理 ZONE_MOVABLE。

注意

核心區域的 CMA 記憶體本質上表現得像 ZONE_MOVABLE 中的記憶體,並且適用類似的考慮因素,尤其是在將 CMA 與 ZONE_MOVABLE 結合使用時。

ZONE_MOVABLE 大小調整注意事項

我們通常期望可用系統 RAM 的很大一部分實際上將被使用者空間消耗,無論是直接地還是間接地透過頁面快取。在正常情況下,可以在分配此類頁面時很好地使用 ZONE_MOVABLE。

考慮到這一點,我們讓 ZONE_MOVABLE 管理系統 RAM 的很大一部分是有意義的。但是,在使用 ZONE_MOVABLE 時,尤其是在微調區域比率時,需要考慮一些事項

  • 擁有大量的離線記憶體塊。即使離線記憶體塊也會消耗記憶體以用於直接對映中的元資料和頁表;但是,擁有大量的離線記憶體塊並不是典型情況。

  • 沒有氣球壓縮的記憶體氣球與 ZONE_MOVABLE 不相容。只有一些實現(例如 virtio-balloon 和 pseries CMM)完全支援氣球壓縮。

    此外,可能會停用 CONFIG_BALLOON_COMPACTION 核心配置選項。在這種情況下,氣球膨脹只會執行不可移動分配並靜默地建立區域不平衡,通常由來自虛擬機器監控程式的膨脹請求觸發。

  • 超大頁面是不可移動的,導致使用者空間消耗大量的不可移動記憶體。

  • 當架構不支援巨頁遷移時,巨頁是不可移動的,導致與超大頁面類似的問題。

  • 頁表是不可移動的。過多的交換、對映極大的檔案或 ZONE_DEVICE 記憶體可能會出現問題,儘管這僅在極端情況下才真正相關。當我們管理大量已交換出或從檔案/持久記憶體/... 中提供的使用者空間記憶體時,我們仍然需要大量的頁表來管理使用者空間訪問該記憶體後的記憶體。

  • 在某些 DAX 配置中,裝置記憶體的記憶體對映將從核心區域分配。

  • KASAN 可能具有大量的記憶體開銷,例如,消耗總系統記憶體大小的 1/8 作為(不可移動)跟蹤元資料。

  • 頁面的長期固定。依賴於長期固定的技術(尤其是 RDMA 和 vfio/mdev)在根本上與 ZONE_MOVABLE 以及記憶體下線存在問題。固定的頁面不能駐留在 ZONE_MOVABLE 上,因為這會使這些頁面不可移動。因此,必須在固定時將它們從該區域遷移出去。即使 ZONE_MOVABLE 中有足夠的可用記憶體,固定頁面也可能失敗。

    此外,使用 ZONE_MOVABLE 可能會由於頁面遷移開銷而使頁面固定更加昂貴。

預設情況下,在啟動時配置的所有記憶體都由核心區域管理,並且未使用 ZONE_MOVABLE。

要啟用 ZONE_MOVABLE 以包括啟動時存在的記憶體並控制可移動區域和核心區域之間的比率,有兩個命令列選項:kernelcore=movablecore=。有關其描述,請參閱核心的命令列引數

記憶體下線和 ZONE_MOVABLE

即使使用 ZONE_MOVABLE,在某些極端情況下,下線記憶體塊也可能會失敗

  • 具有記憶體空洞的記憶體塊;這適用於啟動期間存在的記憶體塊,並且可以應用於透過 XEN 氣球和 Hyper-V 氣球熱插拔的記憶體塊。

  • 單個記憶體塊中的混合 NUMA 節點和混合區域會阻止記憶體下線;這僅適用於啟動期間存在的記憶體塊。

  • 系統阻止下線的特殊記憶體塊。示例包括 arm64 上啟動期間可用的任何記憶體或跨越 s390x 上的 crashkernel 區域的記憶體塊;這通常僅適用於啟動期間存在的記憶體塊。

  • 與 CMA 區域重疊的記憶體塊無法下線,這僅適用於啟動期間存在的記憶體塊。

  • 在同一物理記憶體區域上執行的併發活動(例如分配超大頁面)可能會導致臨時的下線失敗。

  • 在溶解巨頁時記憶體不足,尤其是在啟用了 HugeTLB Vmemmap 最佳化 (HVO) 時。

    下線程式碼可能能夠遷移巨頁內容,但可能無法溶解源巨頁,因為它無法為 vmemmap 分配(不可移動)頁面,因為系統可能沒有核心區域中的剩餘可用記憶體。

    依賴於記憶體下線來成功可移動區域的使用者應仔細考慮,從此功能獲得的記憶體節省是否值得在某些情況下可能無法下線記憶體的風險。

此外,當在遷移頁面時遇到記憶體不足的情況,或者在ZONE_MOVABLE中仍然遇到永久無法移動的頁面(-> BUG)時,記憶體離線將不斷重試,直到最終成功。

當從使用者空間觸發離線時,可以透過傳送訊號來終止離線上下文。基於超時的離線可以透過以下方式輕鬆實現

% timeout $TIMEOUT offline_block | failure_handling