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 旗標的別名,
因此驅動程序不應為了自己的目的使用該旗標。
... so concurrent isolation in several CPUs skip the page for isolation...
不是很確定這句話的意思。