英語

1999 年 11 月由 Kanoj Sarcar 啟動 <kanoj@sgi.com>

什麼是 NUMA?

可以從幾個角度回答這個問題:硬體視角和 Linux 軟體視角。

從硬體角度來看,NUMA 系統是一個計算機平臺,它包含多個元件或元件集合,每個元件或元件集合可能包含 0 個或多個 CPU、本地記憶體和/或 IO 匯流排。 為了簡潔起見,並將這些物理元件/元件集合的硬體檢視與它們的軟體抽象區分開來,我們在本文件中將這些元件/元件集合稱為“單元”。

每個“單元”都可以被視為系統的 SMP [對稱多處理器] 子集 - 儘管獨立 SMP 系統所需的一些元件可能未在任何給定單元上填充。 NUMA 系統的單元透過某種系統互連連線在一起 - 例如,縱橫開關或點對點鏈路是常見的 NUMA 系統互連型別。 這兩種型別的互連都可以聚合以建立具有單元的 NUMA 平臺,這些單元與其他單元之間的距離不同。

對於 Linux,感興趣的 NUMA 平臺主要是所謂的快取一致性 NUMA 或 ccNUMA 系統。 對於 ccNUMA 系統,所有記憶體對連線到任何單元的任何 CPU 都是可見和可訪問的,並且快取一致性由處理器快取和/或系統互連在硬體中處理。

記憶體訪問時間和有效記憶體頻寬因進行記憶體訪問的 CPU 或 IO 匯流排所在的單元與包含目標記憶體的單元之間的距離而異。 例如,連線到同一單元的 CPU 對記憶體的訪問將比對其他遠端單元上的記憶體的訪問體驗更快的訪問時間和更高的頻寬。 NUMA 平臺可以具有與任何給定單元相距多個遠端距離的單元。

平臺供應商構建 NUMA 系統不僅僅是為了讓軟體開發人員的生活變得有趣。 相反,這種架構是一種提供可擴充套件記憶體頻寬的方法。 但是,為了實現可擴充套件的記憶體頻寬,系統和應用程式軟體必須安排將大部分記憶體引用[快取未命中]到“本地”記憶體 - 如果有的話,則在同一單元上的記憶體 - 或到最近的具有記憶體的單元。

這引出了 NUMA 系統的 Linux 軟體檢視

Linux 將系統的硬體資源劃分為多個稱為“節點”的軟體抽象。 Linux 將節點對映到硬體平臺的物理單元上,從而為某些架構抽象出一些細節。 與物理單元一樣,軟體節點可能包含 0 個或多個 CPU、記憶體和/或 IO 匯流排。 並且,同樣,對“更近”節點上的記憶體的訪問 - 對映到更近單元的節點 - 通常會比對更遠端單元的訪問體驗更快的訪問時間和更高的有效頻寬。

對於某些架構(例如 x86),Linux 將“隱藏”任何表示未連線記憶體的物理單元的節點,並將連線到該單元的任何 CPU 重新分配給表示具有記憶體的單元的節點。 因此,在這些架構上,不能假定 Linux 與給定節點關聯的所有 CPU 都將看到相同的本地記憶體訪問時間和頻寬。

此外,對於某些架構(同樣,x86 是一個例子),Linux 支援模擬其他節點。 對於 NUMA 模擬,linux 會將現有節點 - 或非 NUMA 平臺的系統記憶體 - 劃分為多個節點。 每個模擬的節點將管理底層單元物理記憶體的一部分。 NUMA 模擬可用於在非 NUMA 平臺上測試 NUMA 核心和應用程式功能,並且在與 cpusets 一起使用時,作為一種記憶體資源管理機制。 [參見 CPUSETS]

對於每個具有記憶體的節點,Linux 構建一個獨立的記憶體管理子系統,其中包含其自己的空閒頁面列表、正在使用的頁面列表、使用情況統計資訊和用於協調訪問的鎖。 此外,Linux 為每個記憶體區域[DMA、DMA32、NORMAL、HIGH_MEMORY、MOVABLE 中的一個或多個]構建一個有序的“區域列表”。 區域列表指定在選定的區域/節點無法滿足分配請求時要訪問的區域/節點。 當區域沒有可用記憶體來滿足請求時,這種情況稱為“溢位”或“回退”。

由於某些節點包含多個包含不同型別記憶體的區域,因此 Linux 必須決定是否對區域列表進行排序,以便分配回退到不同節點上的相同區域型別,或回退到同一節點上的不同區域型別。 這是一個重要的考慮因素,因為某些區域(例如 DMA 或 DMA32)代表相對稀缺的資源。 Linux 選擇預設的節點排序區域列表。 這意味著它會嘗試從同一節點回退到其他區域,然後再使用按 NUMA 距離排序的遠端節點。

預設情況下,Linux 將嘗試從執行請求的 CPU 分配到的節點滿足記憶體分配請求。 具體來說,Linux 將嘗試從請求來源的節點的適當區域列表中的第一個節點進行分配。 這稱為“本地分配”。 如果“本地”節點無法滿足請求,則核心將檢查所選區域列表中其他節點的區域,以查詢列表中可以滿足請求的第一個區域。

本地分配將傾向於使隨後對已分配記憶體的訪問“本地”於底層物理資源並遠離系統互連 - 只要核心代表其分配了一些記憶體的任務後來沒有從該記憶體遷移開。 Linux 排程器瞭解平臺的 NUMA 拓撲 - 體現在“排程域”資料結構中[參見 排程器域] - 並且排程器嘗試最小化任務遷移到遠距離排程域。 但是,排程器不會直接考慮任務的 NUMA 足跡。 因此,在足夠的不平衡下,任務可以在節點之間遷移,遠離其初始節點和核心資料結構。

系統管理員和應用程式設計人員可以使用各種 CPU 親和性命令列介面(例如 taskset(1) 和 numactl(1))和程式介面(例如 sched_setaffinity(2))來限制任務的遷移,以提高 NUMA 區域性性。 此外,可以使用 Linux NUMA 記憶體策略來修改核心的預設本地分配行為。 [參見 NUMA 記憶體策略]。

系統管理員可以使用控制組和 CPUsets 來限制非特權使用者可以在排程或 NUMA 命令和函式中指定的 CPU 和節點的記憶體。 [參見 CPUSETS]

在不隱藏無記憶體節點的架構上,Linux 將僅將具有記憶體的區域[節點]包含在區域列表中。 這意味著對於無記憶體節點,“本地記憶體節點” - CPU 節點區域列表中的第一個節點的節點 - 將不是節點本身。 相反,它將是核心在構建區域列表時選擇的作為最近的具有記憶體的節點。 因此,預設情況下,本地分配將成功,核心將提供最近的可用記憶體。 這是允許此類分配在包含記憶體的節點溢位時回退到其他附近節點的同一機制的結果。

某些核心分配不希望或不能容忍此分配回退行為。 相反,他們想確保他們從指定的節點獲取記憶體,或者收到該節點沒有可用記憶體的通知。 這通常是在子系統分配每個 CPU 記憶體資源時的情況,例如。

進行此類分配的典型模型是使用核心的 numa_node_id() 或 CPU_to_node() 函式之一獲取連線到“當前 CPU”的節點的節點 ID,然後僅從返回的節點 ID 請求記憶體。 當此類分配失敗時,請求子系統可能會恢復到其自己的回退路徑。 slab 核心記憶體分配器就是一個例子。 或者,子系統可能會選擇在分配失敗時停用或不啟用自身。 核心效能分析子系統就是一個例子。

如果架構支援 - 不隱藏 - 無記憶體節點,那麼連線到無記憶體節點的 CPU 總是會產生回退路徑開銷,或者如果某些子系統嘗試僅從沒有記憶體的節點分配記憶體,則會初始化失敗。 為了透明地支援此類架構,核心子系統可以使用 numa_mem_id() 或 cpu_to_mem() 函式來定位呼叫或指定 CPU 的“本地記憶體節點”。 同樣,這是將嘗試預設本地頁面分配的同一節點。