| RFC-0068:網頁移除提示 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 使用者空間應用程式可透過這項機制,向核心提示使用者分頁備份記憶體的相對逐出順序。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-01-08 |
| 審查日期 (年-月-日) | 2021-02-10 |
摘要
本 RFC 說明使用者空間應用程式向核心提示使用者分頁備份記憶體相對重要性的機制,以便核心在記憶體壓力下決定要逐出哪些頁面時,可將這些提示納入考量。
提振精神
Fuchsia 中的大多數可執行檔都是從 blobfs (不可變更的檔案系統) 提供,這個檔案系統會使用使用者分頁程式,視需要從磁碟讀取頁面。使用者分頁器在記憶體中填入 Blob 後,我們就能在系統面臨記憶體壓力時逐出頁面;當這些頁面再次遭到存取時,使用者分頁器只會將其讀回。
不過,重新分頁會造成效能成本,因為需要頁面的執行緒必須封鎖,直到使用者分頁程式處理頁面錯誤為止,這會涉及內容切換和磁碟 I/O。為盡量減少重新分頁對效能的影響,核心會使用最近最少使用的配置,找出要逐出的頁面。
這種逐出機制不一定最佳,有時可能會導致我們逐出一段時間未存取,但效能關鍵路徑 (例如音訊堆疊) 需要的頁面。另一方面,有些頁面已不再為應用程式所用,可以安全地逐出,例如沒有任何用戶的「閒置」Blob。這些閒置頁面會與目前有用戶但一段時間未存取的有效 Blob 頁面混在一起。將這些閒置頁面移至待逐出佇列的前端,對我們有益。
設計
總覽
存取 Blob 的使用者空間應用程式,以及提供這些 Blob 的 BlobFS,比核心更瞭解網頁的相對重要性。他們可以將這項額外資訊當做逐出提示傳遞至核心。
這項 RFC 建議使用 API,以兩種方式提供提示:「優先考慮清除這些網頁」和「保護這些網頁,避免遭到清除」。以前述範例來說,閒置 Blob 屬於前一類,而執行重要工作時必須使用的頁面則屬於後一類。
Hinting API
zx_vmo_op_range()系統呼叫將擴充,支援兩項新作業。ZX_VMO_OP_DONT_NEED提示指定的範圍已不再需要,應優先考慮逐出。ZX_VMO_OP_ALWAYS_NEED提示指定範圍很重要,應盡可能避免遭到逐出。當系統即將因 OOM 而重新啟動時,核心只會考慮清除標記ZX_VMO_OP_ALWAYS_NEED提示的頁面。(替代方案一節會更詳細說明運算子名稱背後的動機)。同樣地,
zx_vmar_op_range()系統呼叫也會擴充,以支援作業ZX_VMAR_OP_DONT_NEED和ZX_VMAR_OP_ALWAYS_NEED。這些提示只會套用至核心可在記憶體壓力下回收頁面的 VMO 和對應項,目前僅限使用者分頁程式支援的 VMO。日後如果新增更多可回收的 VMO 類型 (例如可捨棄的記憶體),提示 API 也會擴充,涵蓋這些類型。這些名稱較為通用,日後可擴充至網頁並非嚴格從記憶體中逐出的情況。舉例來說,我們可能會考慮在日後使用提示 API,處理可壓縮的匿名記憶體,屆時回收作業就只是記憶體內壓縮。
對於不支援核心回收的 VMO 和對應,剔除提示會是無運算。在一般匿名 VMO 或實體 VMO 上提示,不會改變其頁面的提交或取消提交方式。這樣一來,用戶端就能輕鬆使用,不必事先判斷 VMO 類型。由於這個 API 僅用於傳遞提示,且核心並無具體保證要求,因此核心可以選擇忽略不適用的提示。
目前的逐出策略
核心會使用一組 4 個 LRU 頁面佇列,追蹤使用者分頁備份 VMO 中已提交的頁面。頁面首次提交時,會先進入佇列 1。每 10 秒,頁面掃描器就會輪流處理佇列,將網頁從第 i 個佇列移至第 i+1 個佇列。當系統面臨記憶體壓力時,核心會從第 4 個佇列中逐出頁面。只要存取任一佇列中的頁面,該頁面就會移至佇列 1 的開頭。佇列 1 會追蹤最近使用的頁面。
OP_DONT_NEED
系統會導入額外的
inactive分頁佇列,以追蹤 LRU 順序中的閒置頁面。這個佇列不屬於現有的 LRU 頁面佇列,因此頁面掃描器不會將頁面輪流移入或移出這個佇列。它只會依 LRU 順序追蹤閒置頁面,但不會納入所有使用者分頁支援頁面的較大 LRU 配置。OP_DONT_NEED會將指定範圍內的所有已提交頁面移至「inactive」佇列。記憶體壓力啟動時,核心會先考慮從inactive佇列中撤銷頁面,再移至最舊的 LRU 頁面佇列。如果存取
inactive佇列中的頁面,該頁面會移出佇列,進入第一個 LRU 頁面佇列,並失去先前顯示的OP_DONT_NEED提示。從那時起,它會像其他「有效」頁面一樣,移至其他 LRU 佇列。確保OP_DONT_NEED提示不會錯誤地覆寫網頁的實際存取模式。
OP_ALWAYS_NEED
指定範圍內的頁面會提交,且對應的
vm_page_t中會設定新的always_need旗標。always_need旗標不會影響頁面掃描器輪替佇列的方式。這有助於保留有關標記頁面活動程度的資訊,因此如果需要逐出頁面來防止 OOM,我們只會逐出最舊的頁面,避免記憶體回收作業過於中斷。always_needalways_need標記只會影響在非 OOM 情況下,因逐出而移除頁面的作業,也就是當核心在記憶體壓力下逐出頁面時,會直接略過已設定此標記的頁面。always_need標記不會防止頁面透過其他方式釋出 (例如取消認可、VMO 大小調整或 VMO 毀損)。
OP_ALWAYS_NEED會將指定範圍內的所有頁面移至第一個 LRU 頁面佇列。新提交的頁面一律會從第一個 LRU 佇列開始。對於已提交的頁面,這項作業會計為新的存取權,以確保行為與新提交的頁面一致。如果網頁設有
always_need,只有在核心嘗試避免 OOM 時,才會考慮將網頁逐出。這種做法有助於達到良好的平衡,確保頁面逐出作業不會在系統正常運作期間影響效能,同時也不會鎖定記憶體,進而提高 OOM 率。
與 VMO 副本互動
由於只有根 VMO 直接由分頁器來源支援,因此提示作業只會套用至分頁器支援階層中的根 VMO 頁面。複製項目會從根 VMO 取得初始內容。在副本中提交的任何頁面都是副本擁有的分叉副本,無法逐出。因此,當提示作業用於 VMO 副本 (或對應 VMO 副本的對應項) 時,提示會套用至副本可在指定範圍內查看的根 VMO 頁面,也就是副本中未分叉的頁面。
這兩個提示作業之間的互動
OP_ALWAYS_NEED 提示的優先順序高於 OP_DONT_NEED。always_need 旗標具有黏性,設定後就無法取消設定。這樣可避免來自複製項目的 OP_DONT_NEED 覆寫來自不同複製項目的 OP_ALWAYS_NEED。
如上述作業說明所述,
OP_DONT_NEED後方若有OP_ALWAYS_NEED,網頁就會移至非使用中佇列,但由於已設定always_need旗標,因此不會遭到逐出。OP_DONT_NEED後的OP_ALWAYS_NEED會將網頁從非使用中網頁佇列移至第一個 LRU 佇列,並設定always_need旗標。
處理 op_range 系統呼叫的權限
提示作業不需要 VMO / VMAR 控制代碼的任何特定權利;系統呼叫會成功,無論是否有權利都一樣。不過,核心可根據控制代碼權限、基礎 VMO 類型、支援頁面來源、對應權限等因素,自由忽略沒有意義的提示。換句話說,系統呼叫一律會成功,但提示在某些情況下可能無效。
這種做法可讓我們彈性地進行實作,且日後更容易擴充至更多 VMO 類型。這也符合實作提示的更大意圖,也就是針對不支援的情況將提示實作為無作業,而不是讓系統呼叫失敗。用戶端隨時可以提供提示,但核心會決定如何解讀提示,甚至可能選擇忽略。
與可捨棄記憶體的關係
可捨棄的記憶體是使用者空間影響核心記憶體回收策略的另一種方式。用戶端可藉此建立標示為「可捨棄」的匿名 VMO,並鎖定及解除鎖定這些 VMO,分別表示 VMO 是否正在使用中或符合回收資格。雖然逐出提示與可捨棄記憶體的作用類似,都是為核心提供更多記憶體回收相關資訊,但兩者之間仍存在一些主要差異。
可捨棄的記憶體僅適用於匿名 VMO。本 RFC 建議的逐出提示適用於分頁支援的 VMO。
搭配可捨棄 VMO 使用的鎖定 / 解除鎖定作業 (
zx_vmo_op_range搭配ZX_VMO_OP_LOCK/UNLOCK) 具有更嚴格的語意。如果 VMO 遭到鎖定,核心無法捨棄其頁面。另一方面,即使 VMO 的頁面標示為OP_ALWAYS_NEED,如果記憶體壓力狀況嚴重,核心仍可選擇將其逐出。這是因為 VMO 是由分頁支援,且捨棄的頁面可以視需要重新填入。鎖定可視為與提示完全不同的作業,兩者不可互換,但可以共存。如果日後擴充驅逐提示以支援可捨棄的記憶體,鎖定仍會是客戶端指出 VMO 何時在使用的手段,禁止核心捨棄 VMO。然後,系統可以疊加驅逐提示,僅在適用時表示回收的相對優先順序 - 標記為
OP_DONT_NEED的可捨棄 VMO 可在標記為OP_ALWAYS_NEED的 VMO 之前捨棄。
實作
這是新的 API,因此目前沒有任何依附元件。核心端實作可以獨立完成。API 實作完成後,使用者空間用戶端即可開始採用。
效能
OP_ALWAYS_NEED,可改善路徑上的效能,目前路徑中遭逐出的網頁會導致使用者體驗受到明顯影響。目前已知的使用情況是在驅逐後播放音訊,但有時會因分頁活動而導致故障。
OP_DONT_NEED 會導致指定頁面提早遭到逐出,因此稍後分頁時會發生延遲。不過,這是預期行為,客戶應瞭解這項影響。此外,這些頁面今天可能也會遭到逐出。OP_DONT_NEED 旨在明確指出非使用中的網頁,這些網頁無論如何都不會存取,因此符合清除資格。
提示作業的目的是讓記憶體回收系統更強大,讓遭逐出的頁面更可能長時間保持逐出狀態。系統的記憶體健康狀態應會因此改善。
安全性考量
無。
隱私權注意事項
無。
測試
- 從多個 VMO 複製項目執行新 API 的核心測試 / 單元測試。
- 單元測試,用於驗證核心端的逐出行為。
說明文件
Zircon 系統呼叫說明文件需要更新,才能納入新的 API。
缺點、替代方案和未知事項
鎖定,而非 OP_ALWAYS_NEED
使用 zx_vmo_op_range() 搭配 ZX_VMO_OP_LOCK,可做為使用 ZX_VMO_OP_ALWAYS_NEED 的替代方案。不過,核心必須提供更強大的鎖定保證,也就是將已提交的頁面固定在記憶體中,防止核心在頁面解除鎖定前回收這些頁面。這可能會對系統造成額外的記憶體壓力,導致系統更快發生 OOM 錯誤。
要確切找出應鎖定的網頁,也相當有挑戰性。在多數情況下,客戶可能會比較保守,鎖定的網頁會超出必要範圍。這項作業的記憶體成本可能高得令人望之卻步,而且如果做到極致,可能會抵銷需求分頁機制帶來的記憶體效益。
清除特定作業名稱
我們可以改用更具體的作業名稱,例如 ZX_VMO_OP_EVICT_FIRST/LAST 和 ZX_VMO_OP_RECLAIM_FIRST/LAST,更精確地描述相關的逐出行為。不過,這些技術無法擴展到更通用的未來應用程式。RECLAIM_FIRST/LAST 可能比 EVICT_FIRST/LAST 更廣泛,且可套用至「回收」的各種定義,例如記憶體內壓縮,這不會嚴格逐出頁面,但仍會將作業與記憶體回收的概念綁在一起。
OP_ALWAYS_NEED 和 OP_DONT_NEED 可讓我們更準確地掌握使用者意圖,而不必定義預期會發生的相關核心動作。這可讓這些作業在未來有更大的解讀彈性。此外,這也讓我們能保留與開發人員可能熟悉的 madvise 相似之處。
可能濫用提示
用戶端可能會濫用 API,不慎或刻意使用 OP_ALWAYS_NEED 標記,試圖防止大量網頁遭到逐出。雖然系統仍會回收這類記憶體,避免發生 OOM 錯誤,
但其他時間可能會對系統造成額外的記憶體壓力。
不過,這類風險與用戶端目前建立大型 VMO 並提交所有網頁的情況類似。日後若有一般記憶體用量控管政策 (例如空間庫),我們就能將相同政策饋送至提示邏輯。
將 API 純粹當做提示來建立模型,也能讓核心彈性地忽略提示 (如有需要)。舉例來說,我們可能會忽略超過特定限制的 OP_ALWAYS_NEED 提示,或是在逐出期間僅略過特定次數的 always_need 標記網頁。在極端情況下,我們也可以將提示作業完全淘汰,改為無作業。
之後的作業
如先前所述,提示作業可擴充,適用於使用者分頁支援 VMO 以外的用途。其他可回收的記憶體類型 (例如可捨棄的記憶體) 似乎是自然延伸。我們也可以將其擴充至一般匿名記憶體,並用於驅動嚴格逐出以外的動作。
隨著 VM 系統日趨成熟,OP_DONT_NEED 和 OP_ALWAYS_NEED 之間的互動 (以及 always_need 旗標的黏著性) 可能會在未來有所演變。目前的選擇是根據目前的用途簡化實作方式,並非提示 API 本身的基本需求。
OP_DONT_NEED 和 OP_ALWAYS_NEED 之間會保留中點 OP_WILL_NEED,可做為預先擷取提示,指出未來需要範圍,但不一定需要保護該點以外的逐出作業。使用者分頁器可使用這項提示預先讀取網頁。
既有技術和參考資料
在 Linux 上,madvise 支援 MADV_WILLNEED 和 MADV_DONTNEED。