頁面遷移¶
頁面遷移允許在程序執行時,在 NUMA 系統中的節點之間移動頁面的物理位置。 這意味著程序看到的虛擬地址不會改變。 但是,系統會重新排列這些頁面的物理位置。
另請參閱 異構記憶體管理 (HMM) 以將頁面遷移到或從裝置專用記憶體遷移。
頁面遷移的主要目的是透過將頁面移動到訪問該記憶體的程序正在執行的處理器附近,來減少記憶體訪問的延遲。
透過在透過 mbind() 設定新的記憶體策略時使用 MF_MOVE 和 MF_MOVE_ALL 選項,頁面遷移允許程序手動重新定位其頁面所在的節點。 程序的頁面也可以使用 sys_migrate_pages() 函式呼叫從另一個程序重新定位。 migrate_pages() 函式呼叫接受兩組節點,並將程序的頁面從源節點移動到目標節點。 頁面遷移函式由 Andi Kleen 的 numactl 軟體包提供(需要 0.9.3 或更高版本。 從 https://github.com/numactl/numactl.git 獲取)。 numactl 提供 libnuma,它提供了一個類似於其他 NUMA 功能的頁面遷移介面。 cat /proc/<pid>/numa_maps 允許輕鬆檢視程序的頁面所在的位置。 另請參閱 proc(5) 手冊頁中的 numa_maps 文件。
如果排程器已將程序重新定位到遠端節點上的處理器,則手動遷移非常有用。 批處理排程器或管理員可能會檢測到這種情況,並將程序的頁面移動到更靠近新處理器的位置。 核心本身僅提供手動頁面遷移支援。 可以透過移動頁面的使用者空間程序來實現自動頁面遷移。 一個特殊的函式呼叫“move_pages”允許在程序中移動單個頁面。 例如,NUMA 分析器可以獲取顯示頻繁的異地節點訪問的日誌,並且可以使用結果將頁面移動到更有利的位置。
較大的安裝通常使用 cpusets 將系統劃分為節點 sections。 Paul Jackson 為 cpusets 配備了在任務移動到另一個 cpuset 時移動頁面的能力(參見 CPUSETS)。 Cpusets 允許程序本地化的自動化。 如果任務移動到新的 cpuset,那麼它的所有頁面也會隨之移動,以便程序的效能不會急劇下降。 此外,如果更改了 cpuset 的允許記憶體節點,則 cpuset 中程序的頁面也會被移動。
頁面遷移允許為所有遷移技術保留節點組中頁面的相對位置,即使在遷移程序之後,這些技術也將保留特定的記憶體分配模式。 這對於保持記憶體延遲是必要的。 遷移後,程序將以相似的效能執行。
頁面遷移分幾個步驟進行。 首先,是為那些試圖從核心使用 migrate_pages() 的人提供的高階描述(對於使用者空間用法,請參閱上面提到的 Andi Kleen 的 numactl 軟體包),然後是關於底層細節如何工作的低階描述。
在核心中使用 migrate_pages()¶
從 LRU 中刪除頁框。
要遷移的頁框列表是透過掃描頁框並將它們移動到列表中生成的。 這是透過呼叫
folio_isolate_lru()完成的。 呼叫folio_isolate_lru()會增加對頁框的引用,以便在頁框遷移發生時它不會消失。 它還可以防止交換器或其他掃描遇到頁框。我們需要一個 new_folio_t 型別的函式,它可以傳遞給 migrate_pages()。 此函式應確定如何為舊頁框分配正確的新頁框。
呼叫 migrate_pages() 函式,它嘗試執行遷移。 它將呼叫該函式來為每個被考慮移動的頁框分配新的頁框。
migrate_pages() 的工作原理¶
migrate_pages() 在其頁框列表上執行多次傳遞。 如果在某個時間點可以刪除對頁框的所有引用,則會移動頁框。 頁框已經透過 folio_isolate_lru() 從 LRU 中刪除,並且引用計數已增加,因此在頁框遷移發生時無法釋放頁框。
步驟
鎖定要遷移的頁面。
確保回寫已完成。
鎖定我們要移動到的新頁面。 鎖定它的目的是為了在移動過程中,立即阻止對這個(尚未更新)頁面的訪問。
對頁面的所有頁表引用都將轉換為遷移條目。 這會減少頁面的 mapcount。 如果生成的 mapcount 不為零,那麼我們不遷移該頁面。 所有嘗試訪問該頁面的使用者空間程序現在將等待頁面鎖,或者等待遷移頁表條目被刪除。
獲取 i_pages 鎖。 這將導致所有嘗試透過對映訪問頁面的程序在自旋鎖上阻塞。
檢查頁面的引用計數,如果仍然存在引用,則返回。 否則,我們知道我們是唯一引用此頁面的人。
檢查 radix 樹,如果它不包含指向此頁面的指標,那麼我們返回,因為其他人修改了 radix 樹。
使用舊頁面中的一些設定準備新頁面,以便對新頁面的訪問將發現一個具有正確設定的頁面。
更改 radix 樹以指向新頁面。
舊頁面的引用計數被刪除,因為地址空間引用已消失。 建立對新頁面的引用,因為地址空間引用了新頁面。
釋放 i_pages 鎖。 這樣,就可以再次在對映中進行查詢。 程序將從在鎖上自旋轉移到在新鎖定的頁面上休眠。
頁面內容被複制到新頁面。
剩餘的頁面標誌被複制到新頁面。
舊頁面標誌被清除,以表明該頁面不再提供任何資訊。
觸發新頁面上的排隊回寫。
如果遷移條目已插入到頁表中,則用實際的 pte 替換它們。 這樣做將允許使用者空間程序訪問,而不是已經等待頁面鎖的程序。
從舊頁面和新頁面中刪除頁面鎖。 等待頁面鎖的程序將重做它們的頁面錯誤,並將到達新頁面。
新頁面被移動到 LRU,並且可以再次被交換器等掃描。
非 LRU 頁面遷移¶
雖然遷移最初旨在減少 NUMA 的記憶體訪問延遲,但壓縮也使用遷移來建立高階頁面。 出於壓縮的目的,能夠移動非 LRU 頁面(例如 zsmalloc 和 virtio-balloon 頁面)也很有用。
如果驅動程式想要使其頁面可移動,它應該定義一個 struct movable_operations。 然後,它需要在它可能能夠移動的每個頁面上呼叫 __SetPageMovable()。 這使用 page->mapping 欄位,因此該欄位不適用於驅動程式將其用於其他目的。
監視遷移¶
以下事件(計數器)可用於監視頁面遷移。
PGMIGRATE_SUCCESS:正常的頁面遷移成功。 每個計數意味著一個頁面被遷移。 如果該頁面是非 THP 和非 hugetlb 頁面,則此計數器增加一。 如果該頁面是 THP 或 hugetlb,則此計數器增加 THP 或 hugetlb 子頁面的數量。 例如,遷移具有 4KB 大小基本頁面(子頁面)的單個 2MB THP 將導致此計數器增加 512。
PGMIGRATE_FAIL:正常的頁面遷移失敗。 與上面的 PGMIGRATE_SUCCESS 相同的計數規則:如果它是 THP 或 hugetlb,這將增加子頁面的數量。
THP_MIGRATION_SUCCESS:THP 已被遷移,而沒有被拆分。
THP_MIGRATION_FAIL:THP 無法遷移,也無法拆分。
THP_MIGRATION_SPLIT:THP 已被遷移,但不是以這種方式:首先,必須拆分 THP。 拆分後,其子頁面使用遷移重試。
THP_MIGRATION_* 事件也會更新適當的 PGMIGRATE_SUCCESS 或 PGMIGRATE_FAIL 事件。 例如,THP 遷移失敗將導致 THP_MIGRATION_FAIL 和 PGMIGRATE_FAIL 都增加。
Christoph Lameter,2006 年 5 月 8 日。Minchan Kim,2016 年 3 月 28 日。
-
struct movable_operations¶
驅動程式頁面遷移
定義:
struct movable_operations {
bool (*isolate_page)(struct page *, isolate_mode_t);
int (*migrate_page)(struct page *dst, struct page *src, enum migrate_mode);
void (*putback_page)(struct page *);
};
成員
isolate_pageVM 呼叫此函式以準備要移動的頁面。 頁面被鎖定,驅動程式不應解鎖它。 如果頁面是可移動的,驅動程式應返回
true,如果當前不可移動,則返回false。 在此函式返回後,VM 使用 page->lru 欄位,因此驅動程式必須保留通常儲存在此處的任何資訊。migrate_page隔離後,VM 使用隔離的 src 頁面呼叫此函式。 驅動程式應將 src 頁面的內容複製到 dst 頁面,並設定 dst 頁面的欄位。 這兩個頁面都被鎖定。 如果頁面遷移成功,驅動程式應呼叫 __ClearPageMovable(src) 並返回 MIGRATEPAGE_SUCCESS。 如果驅動程式此時無法遷移頁面,它可以返回 -EAGAIN。 VM 將此解釋為臨時遷移失敗,並將在以後重試。 任何其他錯誤值都是永久遷移失敗,並且不會重試遷移。 在 migrate_page() 函式中,驅動程式不應觸控 src->lru 欄位。 它可以寫入 dst->lru。
putback_page如果隔離頁面上的遷移失敗,VM 透過呼叫此函式通知驅動程式該頁面不再是遷移的候選頁面。 驅動程式應將隔離的頁面放回其自己的資料結構中。