RFC-0068:網頁權威提示

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

使用者空間應用程式機制,可向核心提供使用者分頁器支援記憶體的相對淘汰順序。

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

摘要

這份 RFC 說明瞭一種機制,可讓使用者空間應用程式向核心提供使用者分頁支援記憶體的相對重要性,以便核心在決定記憶體壓力下要淘汰哪些頁面時,考量這些提示。

提振精神

Fuchsia 中的大多數可執行檔都是由 blobfs 提供,這是一種不可變動的檔案系統,會使用使用者分頁器,以便根據需求從磁碟讀取頁面。讓使用者分頁器在記憶體中填入 Blob,可讓我們在系統面臨記憶體壓力時,將頁面淘汰;當這些頁面再次存取時,使用者分頁器只需將其讀回即可。

不過,重新頁面化會造成效能成本 - 需要頁面的執行緒必須阻斷,直到使用者分頁器處理頁面錯誤為止,這會涉及內容切換和磁碟 I/O。為了盡量減少重新分頁對效能的影響,核心會使用最近最少使用方案來找出要淘汰的頁面。

這種淘汰機制不一定是最佳做法,有時可能會導致我們淘汰已一段時間未被存取,但在效能關鍵路徑 (例如音訊堆疊) 上需要的頁面。另一方面,我們也有應用程式不再需要的頁面,可以安全地淘汰,例如沒有任何用戶端的「非活動」Blob。這些停用頁面會與來自有效 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_NEEDZX_VMAR_OP_ALWAYS_NEED 作業。

  • 這些提示僅適用於核心可在記憶體壓力下回收頁面的 VMOs 和對應項目,目前僅限使用者分頁器支援的 VMOs。隨著日後新增更多可回收的 VMO 類型 (例如可捨棄的記憶體),提示 API 也能擴充涵蓋這些類型。這些稍微通用的作業名稱可讓日後擴充至不嚴格從記憶體中移除網頁的情況。舉例來說,我們可能會考慮在日後使用提示 API 來壓縮匿名記憶體,這樣就能簡單地在記憶體中進行壓縮。

  • 對於不支援核心回收的 VMOs 和對應項目,剔除提示將不會執行任何操作。在一般匿名 VMO 或實體 VMO 上提供提示,不會改變其網頁的提交或取消提交方式。這樣一來,用戶端就能輕鬆使用,不必事先決定 VMO 類型。由於這個 API 只用於傳遞提示,並未要求核心提供具體保證,因此核心可以選擇忽略不適用的提示。

目前的淘汰策略

核心會使用一組 4 個 LRU 頁面佇列,追蹤使用者分頁器支援的 VMOs 中已提交的頁面。頁面首次提交時,會從佇列 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 佇列中開始。對於已提交的網頁,這個作業會計為新的存取作業,以便與新提交的網頁保持一致。

  • 只有在核心嘗試防止 OOM 時,才會考慮是否要將設定了 always_need 的頁面逐出。這種做法有助於取得良好的平衡,確保頁面淘汰不會在系統正常運作期間影響效能,同時不會鎖定記憶體,進而提高 OOM 率。

與 VMO 複本互動

提示作業只會套用至分頁器支援階層中根 VMO 的頁面,因為根 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_ALWAYS_NEED 會在 OP_DONT_NEED 後方,將頁面從非活動頁面佇列移至第一個 LRU 佇列,並設定 always_need 標記。

處理 op_range 系統呼叫的權限

提示操作不需要 VMO / VMAR 句柄的任何特定權限;系統會在任何權限 (或無權限) 下執行系統呼叫。不過,核心可以根據句柄權限、基礎 VMO 類型、備用頁面來源、對應權限等組合,自由地忽略無意義的提示。換句話說,系統呼叫一律會成功,但在某些情況下,提示可能會有效地不執行。

這種做法可讓我們靈活運用實作方式,日後也能更輕鬆地擴充至更多 VMO 類型。這也符合在未支援的情況下,將提示做為無作業方式實作,而非讓系統呼叫失敗的更大意圖。用戶端可以隨時提供提示,核心會決定如何解讀該提示,甚至可以選擇忽略該提示。

與可捨棄記憶體的關係

可捨棄的記憶體是另一種用戶空間影響核心記憶體回收策略的方式。這項功能可讓用戶端建立匿名 VMOs,並標示為「可捨棄」,並且鎖定和解鎖,以表示 VMOs 分別處於使用中或可回收的狀態。雖然淘汰提示與可捨棄記憶體的目標相同,也就是為核心提供更多記憶體回收相關資訊,但兩者仍有幾項重要差異。

  • 可捨棄的記憶體只適用於匿名 VMOs。本 RFC 中提出的淘汰提示適用於 pager 支援的 VMOs。

  • 與可捨棄的 VMOs 搭配使用的鎖定 / 解鎖作業 (zx_vmo_op_rangeZX_VMO_OP_LOCK/UNLOCK) 具有更嚴格的語意。如果 VMO 已鎖定,核心就無法丟棄其頁面。另一方面,即使 VMO 的頁面已標示為 OP_ALWAYS_NEED,如果記憶體壓力條件嚴重,核心仍可選擇將其淘汰。這是因為 VMO 是透過分頁器支援,且可視需要重新填入已棄用的頁面。

  • 鎖定可視為與提示完全不同的作業;兩者無法互換,但可以共存。如果淘汰提示日後擴充以支援可捨棄的記憶體,鎖定仍會是用於用戶端指出 VMO 使用情形的手段,禁止核心捨棄該記憶體。系統可以將淘汰提示層疊在最上層,僅在適用時表示回收作業的相對優先順序 - 標記為 OP_DONT_NEED 的解鎖可捨棄 VMOs 可在標記為 OP_ALWAYS_NEED 的 VMOs 之前捨棄。

實作

這是新的 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。

要找出要鎖定的確切網頁也相當困難。在多數情況下,客戶可能會採取較保守的做法,鎖定的頁面會比必要的多。這麼做可能會造成過高的記憶體成本,甚至會讓原本的記憶體需求分頁功能無法發揮效益。

特定的 Eviction 作業名稱

我們可以使用更具體的操作名稱,例如 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 純粹視為提示來建模,也可讓核心具備彈性,在必要時可輕鬆略過提示。我們可以採取一些措施,例如在 always_need 標記的網頁上略過超過特定限制的 OP_ALWAYS_NEED 提示,或是在淘汰期間略過特定次數的網頁。在極端情況下,我們也可以將提示運算轉換為無運算,藉此完全淘汰提示運算。

日後的作業

如先前所述,提示作業可擴充至適用於使用者分頁器支援的 VMOs 以外的用途。其他類型的可回收記憶體 (例如可捨棄的記憶體) 似乎是自然的延伸。我們也可以將其擴展至一般匿名記憶體,並用於驅動嚴格淘汰以外的動作。

隨著 VM 系統日趨成熟,OP_DONT_NEEDOP_ALWAYS_NEED 之間的互動 (以及 always_need 標記的黏滯性) 可能會在未來有所變化。目前的選擇是基於目前用途所帶來的簡化實作方式,而非提示 API 本身的基本要求。

OP_DONT_NEEDOP_ALWAYS_NEED 會保留空間給中點 OP_WILL_NEED,可做為預先擷取提示,表示未來需要範圍,但不一定需要在該點之後受到保護,以免遭到淘汰。使用者分頁器可使用這項提示,提前讀取頁面。

既有技術與參考資料

在 Linux 上,madvise 支援 MADV_WILLNEEDMADV_DONTNEED