iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Software Development

閱讀 Linux Kernel 文件系列 第 28

# Day 28 Page Migration (三)

文件

Non-LRU 分頁遷移
================

雖然遷移旨在減少 NUMA 系統中記憶體存取的延遲,
想要創建高層級分頁的記憶體壓縮功能也是主要顧客。

當前實作的問題是它僅被設計來遷移 *LRU* 分頁。
然而,在驅動程式中,存在著可能可以遷移的 non-lru 分頁,
例如 zsmalloc、virtio-balloon 分頁。

對於 virtio-balloon 分頁,遷移程式碼路徑的某些部分已被接上
並添加了 virtio-balloon 特定的函數來攔截、接下遷移的邏輯。
這些已屬於特定的驅動程式,所以其他想要使分頁面也能移動的驅動程式
必須在遷移程式碼路徑中添加自己特定的 hook。

為了克服這個問題,VM 支援 non-LRU 分頁遷移,
它提供了 non-LRU 的可移動頁面一些通用函數,
且遷移程式碼路徑並不含有驅動程式特定的 hook 。

如果驅動程式想讓自己的分頁可移動,
它應該定義三個函數,
分別是 struct address_space_operations 的函數指標。

1. ``bool (*isolate_page) (struct page *page, isolate_mode_t mode);``

   VM 對驅動程式的 isolate_page 函數的期望是
   如果驅動程序成功隔離分面,則返回 *true*。
   返回 true 時,VM 會將分頁標記為 PG_isolated 
   所以在多個 CPU 中的併發隔離的處理跳過該分頁。
   如果驅動程式無法隔離分頁,則應返回 *false*。
   
   成功隔離分頁後,VM 會使用 page.lru 欄位,
   因此驅動程式不應該期望保留該欄位中的值。

2. ``int (*migratepage) (struct address_space *mapping,``
|   ``struct page *newpage, struct page *oldpage, enum migrate_mode);``

   隔離後,VM 呼叫擁有隔離分頁驅動程式的 migratepage。
   migratepage 的作用是將舊分頁的內容移動到新分頁
   並設置 struct page newpage 的欄位。
   請記住,如果您成功遷移了舊分頁並返回 MIGRATEPAGE_SUCCESS,
   則您應該在 page_lock 的保護下,
   透過 __ClearPageMovable() 向 VM 指示舊分頁不再可移動;
   如果驅動程式當下無法遷移頁面,驅動程式可以返回-EAGAIN。
   收到 -EAGAIN,VM 將在短時間內重新嘗試分頁遷移,
   因為 VM 將 -EAGAIN 解釋為 “臨時遷移失敗”。
   如果返回除了 -EAGAIN 之外的任何錯誤,VM 將放棄頁面遷移而不重試。

   驅動程式不應存取 VM 在函數中使用的 page.lru 欄位。

3. ``void (*putback_page)(struct page *);``

   如果隔離分頁遷移失敗,VM 應歸還隔離分頁至驅動程序,
   因此 VM 使用驅動程式的 putback_page 來處理遷移失敗分頁。
   在這個函數中,驅動程式應該把隔離分頁放回自己的資料結構中。

4. non-lru 可移動分頁旗標
   
   有兩個分頁旗標用於支援 non-lru 的可移動頁面。

   * PG_movable
   
     驅動程式應使用以下函數使分頁在 page_lock 下可移動::

    void __SetPageMovable(struct page *page, struct address_space *mapping)
     
     它需要 address_space 的參數來註冊
     會被 VM 使用的遷移系列的函數。
     確切地說,PG_movable 並不是一個 struct page 的真正旗標。
     而是,VM 重用 page->mapping 的低位元來表示它。
::
    #define PAGE_MAPPING_MOVABLE 0x2
    page->mapping = page->mapping | PAGE_MAPPING_MOVABLE;
      
     所以驅動程式不應該直接存取 page->mapping。
     而是應該在 page_lock 下,使用 page_mapping,
     被屏蔽掉低二位元的 page->mapping,
     以便獲得正確的 struct address_space。
     
     對於是否為 non-lru 可移動分頁的測試,
     VM 提供了 __PageMovable 函數。
     但它不能保證能識別 non-lru 可移動頁面,
     因為 page->mapping 欄位與 struct page 中的其他變數是統合的。
     同樣,如果驅動程式釋放被 VM 隔離後的分頁,
     page->mapping 並不會有一個穩定的值,
     雖然它有 PAGE_MAPPING_MOVABLE(參閱 __ClearPageMovable)。
     但是一旦分頁被隔離則 __PageMovable 能不花成本的
     確認分頁是否是 LRU 或 non-LRU 可移動的。
     因為 LRU 分頁在 page->mapping 中永遠不會有 PAGE_MAPPING_MOVABLE。
     這適合在 lock_page() 下更耗費成本的確認方式之前,
     先在 pfn 掃描選擇受害者,測試是否為 non-lru 可移動分頁。
    
     為了保證確認是否為 non-lru 可移動頁面,
     VM 提供了 PageMovable 函數。
     與 __PageMovable 不同,PageMovable 函數在 lock_page() 下
     驗證 page->mapping 和 mapping->a_ops->lock_page。
     lock_page() 用以防止 page->mapping 突然的被破壞。
     
     使用 __SetPageMovable 的驅動程式應該在 page_lock() 下透過
     __ClearMovablePage 在釋放頁面之前清除旗標。

   * PG_isolated
     
     為了防止多個 CPU 之間同時進行隔離,
     VM 在 lock_page() 下標記了隔離分頁為 PG_isolated。
     所以如果 CPU 遇到 PG_isolated non-lru 可移動頁面,
     它是可以跳過的。
     驅動程式不需要操控旗標,因為 VM 會自動設置/清除它。
     請記住,如果驅動程式看到 PG_isolated 分頁,
     表示該分頁面已被 VM 隔離,所以它不應存取 page.lru 欄位。
     PG_isolated 是 PG_reclaim 旗標的別名,
     因此驅動程序不應為了自己的目的使用該旗標。

我的理解

  • kernel 中,管理記憶體的模組會以2的冪次大小來紀錄可用的記憶體區塊,high-order 分頁指的是較高冪次大小的分頁。
  • 這裡的 VM 感覺指的像是 Virtual Machine (因為提到 memory ballooning),但好像也沒有那麼確定 ((也有可能是 virtual memory ?

後記

  • ... so concurrent isolation in several CPUs skip the page for isolation... 不是很確定這句話的意思。
  • 提到了 page_lock() 和 lock_page(),查了一下,page_lock 應該是一個變數,而 lock_page()才是 function,所以 page_lock() 看起來也是個 typo。

參考資料

延伸閱讀


上一篇
# Day 27 Page Migration (二)
下一篇
# Day 29 Page Migration (四)
系列文
閱讀 Linux Kernel 文件30

尚未有邦友留言

立即登入留言