空閒頁跟蹤

動機

空閒頁跟蹤功能允許跟蹤工作負載正在訪問哪些記憶體頁以及哪些是空閒的。這些資訊可用於估算工作負載的工作集大小,這反過來又可在配置工作負載引數、設定記憶體 cgroup 限制或決定將工作負載放置在計算叢集的何處時予以考慮。

透過 CONFIG_IDLE_PAGE_TRACKING=y 啟用此功能。

使用者 API

空閒頁跟蹤 API 位於 /sys/kernel/mm/page_idle。目前,它僅包含一個讀寫檔案:/sys/kernel/mm/page_idle/bitmap

該檔案實現了一個位圖,其中每個位對應一個記憶體頁。點陣圖由一個 8 位元組整數陣列表示,PFN #i 處的頁對映到陣列元素 #i/64 的位 #i%64,位元組順序為本機位元組序。如果某個位被設定,則對應的頁為空閒狀態。

如果一個頁自被標記為空閒以來未被訪問過(關於“訪問”的具體含義,請參閱實現細節部分),則該頁被視為空閒。要將一個頁標記為空閒,必須透過寫入檔案來設定對應於該頁的位。寫入檔案的值將與當前點陣圖值進行 OR 運算。

只跟蹤使用者記憶體頁的訪問。這些包括對映到程序地址空間的頁、頁快取和緩衝區頁、交換快取頁。對於其他頁型別(例如 SLAB 頁),嘗試將頁標記為空閒會被靜默忽略,因此這些頁永遠不會被報告為空閒。

對於大頁,空閒標誌只在頭部頁上設定,因此必須讀取 /proc/kpageflags 才能正確計算空閒大頁的數量。

如果讀寫 /sys/kernel/mm/page_idle/bitmap 不是從 8 位元組邊界開始,或者讀寫的大小不是 8 位元組的倍數,則會返回 -EINVAL。寫入此檔案超出最大 PFN 會返回 -ENXIO。

也就是說,為了估算工作負載未使用的頁數量,應該:

  1. 透過設定 /sys/kernel/mm/page_idle/bitmap 中對應的位,將所有工作負載的頁標記為空閒。如果工作負載由一個程序表示,可以透過讀取 /proc/pid/pagemap 找到這些頁;如果工作負載放置在記憶體 cgroup 中,則可以透過 /proc/kpagecgroup 過濾掉無關的頁。

  2. 等待工作負載訪問其工作集。

  3. 讀取 /sys/kernel/mm/page_idle/bitmap 並計算設定的位數。如果想忽略某些型別的頁,例如因為它們不可回收而忽略已鎖定的頁,可以使用 /proc/kpageflags 過濾掉它們。

tools/mm 目錄中的 page-types 工具可以協助完成此操作。如果該工具最初執行時帶有適當的選項,它將把所有查詢的頁標記為空閒。後續執行該工具可以顯示哪些頁的空閒標誌在此期間已被清除。

有關 /proc/pid/pagemap/proc/kpageflags/proc/kpagecgroup 的更多資訊,請參見檢查程序頁表

實現細節

核心內部跟蹤使用者記憶體頁的訪問,以便在記憶體不足時首先回收未引用的頁。如果一個頁最近透過程序地址空間被訪問過,則認為該頁已被引用,在這種情況下,它所對映的一個或多個 PTE 將設定 Accessed 位,或者由核心顯式標記為已訪問(參見 mark_page_accessed())。後者發生在以下情況:

  • 使用者空間程序使用系統呼叫(例如 read(2) 或 write(2))讀寫一個頁

  • 一個用於儲存檔案系統緩衝區的頁被讀寫,因為程序需要儲存在其中的檔案系統元資料(例如列出目錄樹)

  • 一個頁被裝置驅動程式使用 get_user_pages() 訪問

當一個髒頁由於記憶體回收或超出髒記憶體限制而被寫入交換區或磁碟時,它不會被標記為已引用。

空閒記憶體跟蹤功能增加了一個新的頁標誌,即空閒標誌(Idle flag)。此標誌透過寫入 /sys/kernel/mm/page_idle/bitmap 手動設定(參見使用者 API 部分),並在頁被如上定義為已引用時自動清除。

當一個頁被標記為空閒時,它所對映的所有 PTE 中的 Accessed 位必須被清除,否則我們將無法檢測到來自程序地址空間對該頁的訪問。為了避免與回收器(如上所述,回收器使用 Accessed 位來提升活躍引用的頁)發生衝突,引入了另一個頁標誌:Young 標誌。當由於設定或更新頁的 Idle 標誌而清除 PTE Accessed 位時,該頁的 Young 標誌被設定。回收器將 Young 標誌視為額外的 PTE Accessed 位,因此會將此類頁視為已引用。

由於空閒記憶體跟蹤功能基於記憶體回收器邏輯,它只適用於 LRU 列表上的頁,其他頁會被靜默忽略。這意味著它會忽略一個使用者記憶體頁(如果它被隔離),但由於這類頁通常不多,因此不會顯著影響總體結果。為了避免阻塞空閒頁點陣圖的掃描,鎖定的頁也可能被跳過。