RFC-0068:網頁權威提示

RFC-0068:網頁移除提示
狀態已接受
區域
  • 核心
說明

使用者空間應用程式的機制,可根據使用者分頁器備份記憶體的撤銷順序提示核心。

問題
變更
  • 468630
作者
審查人員
提交日期 (年/月)2021-01-08
審查日期 (年/月)2021-02-10

摘要

這個 RFC 描述了一種機制,可讓使用者空間應用程式向核心發出提示,指出使用者分頁程式支援記憶體的相對重要性,這樣核心就能在決定記憶體壓力而剔除哪些頁面時,將這些提示納入考量。

提振精神

Fuchsia 中的大多數執行檔是透過 blobf 提供。blobf 是一種不可變更的檔案系統,會經由使用者呼叫器依需求從磁碟讀取分頁。在使用者呼叫器的記憶體中填入 blob 後,我們就能在系統遇到記憶體壓力時剔除頁面;當使用者再次存取這些網頁時,使用者呼叫器只會將 12 資料讀回。

變更會產生效能成本,但是在使用者呼叫器提供頁面錯誤之前,必須先封鎖頁面,此執行緒會涉及內容切換和磁碟 IO。為了嘗試降低重新分頁的效能影響,核心會使用最近最少使用的配置,尋找要剔除的網頁。

這個移除機制不一定是最佳選擇,有時可能會使我們剔除長時間未存取但對效能關鍵路徑 (例如音訊堆疊) 的網頁。另一方面,我們擁有應用程式不再需要的頁面,而且可以安全清空頁面,例如不含任何用戶端的「閒置」blob。這些閒置頁面會與使用中的 blob 連結,而這些 blob 目前含有用戶端,但已經一段時間沒有存取。把這些閒置網頁移到開頭處就能剔除。

設計

總覽

存取 blob 的使用者空間應用程式,以及提供這些 blob 的 blobf 都具有比頁面相對重要性的核心更高的背景資訊。他們可以將這項額外資訊做為移除提示,傳送至核心。

這個 RFC 建議提供一個 API,可用於進行兩個提示:「先考慮將這些網頁移除」和「防止這些網頁遭到移除」。 以之前的範例來說,已停用的 blob 屬於先前的類別,而著重效能工作所需的頁面則歸類在後者之下。

Hinting API

  • zx_vmo_op_range() Syscall 將擴充以支援兩項新作業。ZX_VMO_OP_DONT_NEED 提示:指定範圍不再需要,並應先考慮移除。ZX_VMO_OP_ALWAYS_NEED 提示:指定範圍很重要,請盡可能避免移除這類提示。只有在系統因 OOM 而即將重新啟動時,核心才會考慮剔除標有 ZX_VMO_OP_ALWAYS_NEED 提示的頁面。(「替代選項」一節將詳細說明運算名稱背後的動機。)

  • 同樣地,zx_vmar_op_range() 系統呼叫將會擴充以支援作業 ZX_VMAR_OP_DONT_NEEDZX_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 旗標不會影響頁面掃描器如何旋轉佇列。這有助於保留已標記 always_need 頁面是否有效的相關資訊,所以如果需要剔除最舊的頁面以防止 OOM,我們只會清除最舊的頁面,避免記憶體回收作業造成過多的干擾。

    • always_need 旗標只會影響在非 OOM 條件下遭到撤銷的網頁 - 當核心在記憶體壓力下清除網頁時,只會略過有此標記組合的頁面。always_need 旗標「不會」以移除以外的方式 (例如取消修訂版本、VMO 大小調整或 VMO 刪除) 來阻止頁面釋放。

  • OP_ALWAYS_NEED 會將指定範圍內的所有頁面移至第一個 LRU 頁面佇列。新修訂的頁面仍會從第一個 LRU 佇列開始。至於已修訂的網頁,這項運算會計為新的存取權,以便讓行為與新修訂的頁面保持一致。

  • 已設定 always_need 的網頁只會在核心嘗試防止 OOM 時視為移除。這種方法有助於達到良好的平衡,確保頁面剔除不會在系統正常運作期間降低效能,同時不會鎖定記憶體,進而提高 OOM 速率。

與 VMO 本機副本互動

提示作業僅適用於在 Pager 備份階層中根 VMO 中的頁面,因為根 VMO 是唯一會由分頁器來源直接支援的頁面。本機副本會從根 VMO 取得初始內容。凡是在本機副本中提交的頁面,都是該本機副本擁有的分支副本,無法撤銷。因此,將提示作業用於 VMO 本機副本 (或對應 VMO 本機副本的對應) 時,提示將套用至根 VMO 中的頁面,且該副本可在指定範圍內看到,也就是尚未在複製中使用分支的頁面。

兩個提示運算之間的互動

OP_ALWAYS_NEED 提示的優先順序高於 OP_DONT_NEEDalways_need 旗標是固定式,且在設定後無法取消。這可以防止來自本機副本的 OP_DONT_NEED 因其他本機副本覆寫 OP_ALWAYS_NEED

  • 根據上方作業的說明,位於 OP_ALWAYS_NEED 後方的 OP_DONT_NEED 會將頁面移至非使用中的佇列,但因為已設定 always_need 標記,並不會移除。

  • OP_DONT_NEED 後方的 OP_ALWAYS_NEED,會將頁面從閒置頁面佇列移至第一個 LRU 佇列,並設定 always_need 標記。

處理 op_range syscalls 的權限

提示作業不需要 VMO / VMAR 處理程序的任何特定權限;Syscall 會以任何權利或無權限成功執行。不過,核心可以自由忽略無意義的提示,例如處理權限、基礎 VMO 類型、幕後頁面來源、對應權限等。換句話說,系統一律都能成功執行系統呼叫,但在某些情況下,提示其實可以有效免人工管理。

這種做法讓我們在實作時更有彈性,日後也能更輕鬆地將其擴展到更多 VMO 類型。此做法也會配合較大的意圖,在不支援的情況時將提示實作為免人工管理,而不是導致系統呼叫失敗。用戶端隨時可以提示:核心將決定如何解讀提示,甚至選擇忽略該提示。

與可捨棄記憶體的關係

可捨棄的記憶體是使用者空間影響核心記憶體回收策略的另一種方法。可讓用戶端建立標示為「discardable」的匿名 VMO,並且會鎖定並解鎖,以表示其使用中或何時符合資格。雖然移除提示的功用與可捨棄記憶體類似,也就是為核心提供有關記憶體回收的詳細資訊,但兩者之間有一些主要差異。

  • 可捨棄的記憶體僅適用於匿名 VMO。這份 RFC 中所述的移除提示適用於分頁式 VMO。

  • 與可捨棄的 VMO 搭配使用的鎖定 / 解鎖作業 (zx_vmo_op_range 搭配 ZX_VMO_OP_LOCK/UNLOCK) 的語意較為嚴格。如果 VMO 處於鎖定狀態,核心「無法」捨棄其頁面。另一方面,即使 VMO 頁面顯示為 OP_ALWAYS_NEED,即使記憶體壓力狀況不佳,核心仍可以選擇清除這些頁面。這是因為 VMO 採用分頁程序支援,而且可以視需求重新填入捨棄的頁面。

  • 鎖定可以視為完全獨立的作業與提示完全分開;兩者無法互換,且可以共存。如果日後會延長移除提示,以便支援可捨棄的記憶體,鎖定功能仍會保留對用戶端指出 VMO 是否使用中的方式,導致核心無法捨棄該 VM。移除提示之後,只有在適用情況下可疊加在上方,表達重新修復的相對優先順序。已解鎖的可捨棄 VMO 標記有 OP_DONT_NEED 標記,然後才捨棄標記。OP_ALWAYS_NEED

實作

這是新的 API,因此現階段沒有任何依附元件。核心端實作可獨立完成。實作 API 後 使用者空間用戶端就能開始採用

效能

OP_ALWAYS_NEED 會改善目前已遭剔除的網頁分頁效能,對可觀測的使用者造成影響。現今的已知用途是在移除後播放音訊,而這有時會因分頁活動而出現故障。

OP_DONT_NEED 會導致指定網頁更快遭到移除,而當這些頁面之後分頁時就會延遲。不過這是正常現象,用戶端應該也注意到這個影響。此外,這些網頁可能也已經遭到剔除。OP_DONT_NEED 的用途是明確指出閒置頁面不會遭到存取,且無法存取網頁,因此符合移除資格。

提示作業的目的是讓記憶體回收系統更穩固,因為遭到剔除的網頁較有可能長時間消失。系統的記憶體健康狀態應該會因此提高。

安全性考量

無。

隱私權注意事項

無。

測試

  • 核心測試 / 單元測試,可從多個 VMO 副本執行新 API。
  • 驗證核心端清除行為的單元測試。

說明文件

Zircon syscall 說明文件必須更新才能納入新的 API。

缺點、替代方案和未知

鎖定,而非 OP_ALWAYS_NEED

zx_vmo_op_range()ZX_VMO_OP_LOCK 搭配使用也可做為使用 ZX_VMO_OP_ALWAYS_NEED 的替代方案。但是,核心的鎖定措施必須更嚴格執行鎖定;承諾的頁面需要固定在記憶體中,以防止核心在解鎖之前收回這些頁面。這可能會釋放系統的額外記憶體壓力,進而加快 OOM 的速率。

確切指出要鎖定哪些頁面可能也相當困難。在多數情況下,用戶端較可能保守,鎖定的頁面數量卻超過規定。這可能會使記憶體成本遭到禁止,而在極大的情況下,可能會損害一開始進行需求分頁所帶來的記憶體優勢。

撤銷特定運算名稱

我們可以使用更具體的運算名稱,例如 ZX_VMO_OP_EVICT_FIRST/LASTZX_VMO_OP_RECLAIM_FIRST/LAST,後者能更精確地描述相關清除行為。不過,這些項目無法擴充至較一般的未來應用程式。RECLAIM_FIRST/LAST 可能比 EVICT_FIRST/LAST 更大,也可以套用至各種「重新剪輯」定義,例如記憶體內壓縮,不會嚴格剔除頁面,但仍會將作業與記憶體收回的概念。

OP_ALWAYS_NEEDOP_DONT_NEED 可以讓我們更精確地擷取使用者意圖,而不用定義預期會發生的相關核心動作。這能在未來為這些運算作業提供更高的彈性。也可讓我們保留與開發人員熟悉的 madvise 類似的相似度。

可能濫用提示

用戶端可能會不小心或刻意濫用 API,藉此嘗試及防止使用 OP_ALWAYS_NEED 未經重新訓練的大量網頁遭到移除。儘管這個記憶體仍會收回以防止 OOM,但在其他時間可能會加開系統的額外記憶體壓力。

不過,這種風險跟目前建立大型 VMO 並提交所有頁面的客戶一樣。日後我們針對控制一般記憶體用量 (例如空間銀行) 制定了一項政策後,即可將相同的政策動態饋給納入提示邏輯中。

完全以提示為 API 建立模型也能讓核心享有彈性,讓他們在需要時直接忽略提示。我們可以忽略 OP_ALWAYS_NEED 提示,並在 always_need 標記的頁面上超過特定限制,或是在僅移除特定次數時略過頁面。在極端情況下,也可以將提示運算轉變成免人工管理,藉此完全淘汰提示作業。

未來的工作

如先前所述,提示運算可以延伸至使用者呼叫器支援 VMO 以外的用途。其他類型的可收回記憶體 (例如免除式記憶體) 看起來像是很自然的擴充方式。我們還可以將其擴充為一般的匿名記憶體,並用於執行嚴格移除以外的動作。

由於 VM 系統更加成熟,OP_DONT_NEEDOP_ALWAYS_NEED 之間的互動 (以及 always_need 旗標設為固定) 未來可能會發展。目前選擇是基於目前用途的簡化實作,而非提示 API 本身的基本需求。

OP_DONT_NEEDOP_ALWAYS_NEED 會預留一個中點 OP_WILL_NEED 空間,用來做為預先擷取提示,表示未來需要範圍,但不一定需要避免在該點之後遭到剔除。使用者呼叫器可以使用這個提示朗讀內容。

先前的圖片和參考資料

Linux madvise 支援 MADV_WILLNEEDMADV_DONTNEED