RFC-0012:Zircon 可捨棄記憶體 | |
---|---|
狀態 | 已接受 |
領域 |
|
說明 | 說明使用者空間應用程式機制,向核心表示特定記憶體緩衝區符合重組資格。 |
問題 | |
毛皮變化 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2020-10-27 |
審查日期 (年-月-日) | 2020-12-02 |
摘要
此 RFC 描述了使用者空間應用程式 特定記憶體緩衝區可進行回收作業的核心。核心 也可視需要捨棄這些緩衝區 可用的記憶體大小
提振精神
在 Zircon、 能讓使用者應用程式分配比目前可能更多的記憶體 發布的內容方法是使用虛擬記憶體 延遲由實體頁面備份的物件 (VMO),在物件中當做一部分 就會受到攻擊
針對任何時間點,高估要使用的實體記憶體量 若是無法根據使用者需求進一步分配記憶體 可用的記憶體容量這會影響效能,許多記憶體都可能受到影響 以便快取用途另一方面 使用中的可用記憶體容量 可能會導致我們快速用完所有 否則會發生「記憶體不足」(OOM) 問題。 此外,「免費」記憶體本身相當複雜
Zircon 核心會監控可用實體記憶體量,並 記憶體壓力信號這些信號的用途 可讓使用者空間應用程式縮減 (或增加) 記憶體用量 以整個系統的可用記憶體等級為基礎雖然這種做法能避免系統 在記憶體不足時,這些信號的發起者就會分離 ( 回應者 (使用者應用程式) 發出的核心) 並不理想。處理 記憶體壓力並未提供足夠的背景資料 這些元件應釋出空間更能掌握全域記憶體用量 系統也可能會考慮其他形式的 可收回的記憶體,例如可剔除的使用者呼叫器支援的記憶體。
這個 RFC 提議讓核心直接 在記憶體不足的情況下,收回使用者空間記憶體緩衝區。以下幾方面 優點:
- 可進一步控制撤銷的記憶體數量;核心可以 查看可用記憶體水平,然後只清除所需的記憶體。
- 核心可以使用 LRU 配置捨棄記憶體,最好在 可配合記憶體中的目前工作集。
- 使用者空間有時可能會因為記憶體壓力而減慢記憶體 信號在某些情況下,系統復原可能會太晚。
- 為因應記憶體壓力,使用者空間用戶端有時會喚醒 需要更多記憶體
設計
總覽
可捨棄的記憶體通訊協定大致上的運作方式如下:
- 使用者空間程序會建立 VMO 並標示為「discardable」。
- 直接 (
zx_vmo_read
/zx_vmo_write
) 或首次存取 VMO 前 透過位址空間中的對應 (zx_vmar_map
) 進行對應,這項程序「鎖定」 表示 VMO 正在使用中 - 程序完成後,程序就會「解鎖」VMO,表示已不在 VM 上 。核心會將所有未解鎖的可捨棄 VMO 視為符合資格 並在記憶體壓力下自行捨棄
- 當程序需要再次存取 VMO 時,會嘗試鎖定 VM。這個
現在鎖定可成功,有兩種方法之一
- 鎖定可以成功,VMO 的頁面則不受影響,即 核心尚未捨棄。
- 如果核心已捨棄 VMO,鎖定也會成功 向客戶說明其網頁已被捨棄, 他們可以再次將其初始化,或採取其他必要動作。
- 程序完成後,系統就會再次解鎖 VMO。鎖定及解鎖 可以一再以這個方式重複執行
請注意,可捨棄的記憶體並不是直接替換記憶體 壓力信號。監控記憶體壓力變化,依然有助於 例如選擇啟動大量記憶體的時機 或執行緒。未來我們也能運用這些信號 元件中的閒置程序。記憶體壓力信號也能提供 元件就能更全面地控制可用的記憶體和時機。
可捨棄的 Memory API
我們可以將現有的 zx_vmo_create()
和 zx_vmo_op_range()
系統呼叫延伸至
可支援這項功能
將擴充
zx_vmo_create()
以支援新的options
標記:ZX_VMO_DISCARDABLE
。這個標記可與ZX_VMO_RESIZABLE
。不過,對於可調整大小的 VMO 提供一般建議 適用於可捨棄的 VMO,您可以在程序之間共用可調整大小的 VMO 因此應該避免我們會延長
zx_vmo_op_range()
來支援新的作業,以便提供 正在鎖定與解鎖 -ZX_VMO_OP_LOCK
、ZX_VMO_OP_TRY_LOCK
和ZX_VMO_OP_UNLOCK
。鎖定和解鎖功能會套用至整個 VMO,因此
offset
和size
應會涵蓋 VMO 的整個範圍鎖定並解除鎖定 比 VMO 的更小範圍雖然目前的實作方式 嚴格要求offset
和size
,確保只有整個範圍 的 VMO 被視為有效範圍,可在 而無需變更用戶端行為。ZX_VMO_OP_TRY_LOCK
作業會嘗試鎖定 VMO,但會失敗。 如果核心未捨棄 VMO,這項指令就會成功,ZX_ERR_NOT_AVAILABLE
。故障時, 用戶端應使用ZX_VMO_OP_LOCK
再試一次,這保證 只要傳入的引數有效即可。ZX_VMO_OP_TRY_LOCK
作業提供了簡易的選項 可讓您嘗試鎖定 VMO 不必設定緩衝區引數客戶也可以選擇不採用 無法鎖定 VMO。ZX_VMO_OP_LOCK
作業也會需要buffer
引數, 指向zx_vmo_lock_state
結構體。這個結構適用於核心 傳回用戶端可能認為實用的資訊,其中包含:offset
和size
正在追蹤鎖定範圍:這些是size
和offset
引數由用戶端傳入。系統會傳回這些 純粹是為了方便起見,客戶不必追蹤 範圍,並直接改用傳回的結構體。如果 呼叫成功,它們一律會與size
和offset
相同 傳入zx_vmo_op_range()
呼叫的值。discarded_offset
和discarded_size
追蹤捨棄的範圍:這 是鎖定範圍內的最大範圍,其中包含已捨棄的網頁。 系統可能沒有捨棄這個範圍中的所有網頁,只是 這個範圍內所有捨棄子範圍的聯集,也可能包含 未一併捨棄的網頁有了目前的 API 如果核心遭到捨棄,捨棄的範圍會涵蓋整個 VMO。如果 取消捨棄,discarded_offset
和discarded_size
都會設為 零時差弱點
鎖定本身不會提交 VMO 中的任何頁面。只會標示 VMO 的輸出內容為「undiscardable」核心部分。用戶端可在 VMO 會透過一般 VMO 適用的任一現有方法更新 VMO,例如
zx_vmo_write()
、ZX_VMO_OP_COMMIT
,對應 VMO 並直接寫入 對應的地址
// |options| supports a new flag - ZX_VMO_DISCARDABLE.
zx_status_t zx_vmo_create(uint64_t size, uint32_t options, zx_handle_t* out);
// |op| is ZX_VMO_OP_LOCK, ZX_VMO_OP_TRY_LOCK, and ZX_VMO_OP_UNLOCK to
// respectively lock, try lock and unlock a discardable VMO.
// |offset| must be 0 and |size| must the size of the VMO.
//
// ZX_VMO_OP_LOCK requires |buffer| to point to a |zx_vmo_lock_state| struct,
// and |buffer_size| to be the size of the struct.
//
// Returns ZX_ERR_NOT_SUPPORTED if the vmo has not been created with the
// ZX_VMO_DISCARDABLE flag.
zx_status_t zx_vmo_op_range(zx_handle_t handle,
uint32_t op,
uint64_t offset,
uint64_t size,
void* buffer,
size_t buffer_size);
// |buffer| for ZX_VMO_OP_LOCK is a pointer to struct |zx_vmo_lock_state|.
typedef struct zx_vmo_lock_state {
// The |offset| that was passed in.
uint64_t offset;
// The |size| that was passed in.
uint64_t size;
// Start of the discarded range. Will be 0 if undiscarded.
uint64_t discarded_offset;
// The size of discarded range. Will be 0 if undiscarded.
uint64_t discarded_size;
} zx_vmo_lock_state_t;
將擴充 zx::vmo
介面來支援 ZX_VMO_OP_LOCK
。
使用 op_range()
的 ZX_VMO_OP_TRY_LOCK
和 ZX_VMO_OP_UNLOCK
作業。Rust、Go 和
Dart 繫結也將更新。
這個 API 可讓用戶端靈活地共用可捨棄的 VMO 並用於多項程序每個需要存取 VMO 的程序 視需要將 VMO 上鎖與解鎖沒有任何疑慮 根據對鎖定的假設,在程序間所需的協調 時間。如果沒有 VM 人,核心只會將 VMO 納入考量 功能已鎖定
VMO 的限制
可捨棄的記憶體 API 僅支援
VmObjectPaged
類型, 根據定義,無法捨棄VmObjectPhysical
。這個 API 與 VMO 本機副本 (快照和 COW 本機副本) 不相容,以及 切片,因為在副本階層中捨棄 VMO 行為
zx_vmo_create_child()
的系統呼叫會在可捨棄的 VMO 上失敗。ZX_VMO_DISCARDABLE
旗標無法在以下應用程式的options
引數中使用:zx_pager_create_vmo()
。其中一個主要原因是,受到呼叫的 VMO 可以 以及可捨棄的 VMO再者,捨棄性是默示的 ,因此不需要額外標記。
與現有 VMO 作業的互動
現有 VMO 作業的語意將維持不變。適用對象
舉例來說,zx_vmo_read()
不會驗證可捨棄的 VMO 是否在鎖定前遭到鎖定
允許執行作業。客戶必須負責確保
使 VMO 存取時處於鎖定狀態,以確保核心
因此不會捨棄它們這會限制這項變更的顯示範圍。
只有核心提供保證,在保留 VMO 的狀態下
目前處於鎖定狀態
即使已捨棄 VMO,VMO 的所有對應仍然有效。 只要用戶端鎖定 VMO 再存取對應即可客戶這麼做 如果已捨棄 VMO,則不需要重新建立對應關係。
核心捨棄 VMO 後,如果沒有先對 VMO 執行任何進一步作業
如果鎖定 VMO,將失敗,如同 VMO 沒有修訂頁面
以視需求提交頁面舉例來說,zx_vmo_read()
會失敗
ZX_ERR_OUT_OF_RANGE
。如果 VMO 已對應於程序位址空間
如果可存取對應的地址,會導致頁面錯誤
例外狀況。
核心實作
追蹤中繼資料
VmObjectPaged
中的options_
位元遮罩將延伸,以支援kDiscardable
標記;我們目前只使用了 32 個位元中的 4 位元。- 新的
lock_count
欄位會新增至VmObjectPaged
,可用於追蹤 VMO 中未解決的鎖定作業數量。 - 核心會維護一份全域可回收 VMO 清單,也就是
解鎖系統上可捨棄的 VMO清單更新如下:
ZX_VMO_OP_LOCK
將會增加 VMO 的lock_count
。如果lock_count
會從 0 至 1 之間,VMO 會從全球可收回清單中移除。ZX_VMO_OP_UNLOCK
將會減少 VMO 的lock_count
。如果lock_count
降為 0,VMO 將會新增至全球可收回的資產 請參考閱讀清單,進一步瞭解 如何選擇 Kubeflow Pipelines SDK 或 TFX
宣告邏輯
遭捨棄的 VMO 會新增至全域可回收清單
lock_count
降為零,並在再次鎖定時移除。這可維持
系統中所有已解鎖可捨棄 VMO 的 LRU 順序。記憶體不足時
壓力時,核心可以依序將 VMO 從這份清單中移除並捨棄
以及每個記憶體的可用記憶體用量這是非常簡單的
執行宣告邏輯的實際情形再完成幾個步驟
都會提及
捨棄作業
「變形」會在核心端實作
並將 VMO 的內部狀態設為 discarded
VmObjectPaged::GetPageLocked()
如果 VMO 的虛擬機器有 ZX_ERR_NOT_FOUND
狀態為 discarded
。系統會清除後續的 discarded
狀態
ZX_VMO_OP_LOCK
作業。GetPageLocked()
是存取某個函式時
VMO 的頁面向下延伸至 zx_vmo_read/write
系統呼叫和網頁
以便透過 VM 對應
存取資源如此一來,我們便能在
會捨棄已解鎖的 VMO,並在已捨棄的 VM 產生例外狀況時產生例外狀況
而未鎖定的 VMO 則是透過對應來存取。
實作
這是新的 API,因此這個階段沒有任何依附元件。核心端 和實作,實作 API 後 使用者空間用戶端即可開始採用。
成效
效能影響會因用戶端用途而異。有 用戶端使用 API 時可牢記的幾個事項。
zx_vmo_op_range()
發出系統呼叫,以便鎖定並解鎖可捨棄的 VMO。 存取權可能會使重要的效能路徑出現明顯延遲。所以 系統呼叫應用於程式碼路徑,因為這類路徑的延遲時間可能會增加 可以容錯或隱藏- 用戶端的效能也會有所提升,因為快取保存在 長時間執行記憶體用戶端必定捨棄的緩衝區 在記憶體壓力下,由於核心只會 會盡可能捨棄需要的記憶體容量用戶端可以透過快取追蹤這項變更 命中率、緩衝區需要重新初始化的次數等
安全性考量
無。
隱私權注意事項
無。
測試
- 從多個執行緒練習新 API 的核心測試 / 單元測試。
- 用於驗證核心端的重組行為 (僅限 可捨棄未鎖定的 VMO
說明文件
必須更新 Zircon syscall 說明文件,才能加入新的 API。
缺點、替代方案和未知
鎖定 VMO 中的範圍
宣告精細程度為整個 VMO 支援更精細的捨棄 VMO 中範圍的作業另有 幾個原因
- 重建已捨棄部分頁面的 VMO 並不容易。考慮中 一般用途,使用 VMO 代表匿名記憶體 緩衝區,重新填入捨棄的網頁很可能是零填滿, 不行。其餘未分類的網頁 相較於只保留一部分 VMO 的頁面 也就是只有已填入的 VMO 才有意義。
- VMO 精細程度可讓
VmObjectPaged
實作項目更簡單,需要 將追蹤中繼資料降到最低不用追蹤鎖定範圍,之後就能進行比對 就會解鎖也不會進行複雜的範圍合併。 - 還能保持輕量地宣告邏輯 來一次釋放記憶體區塊改為支援頁面精細程度 都可能需要維護網頁佇列和大量可捨棄的頁面 類似於我們用來移除使用者呼叫頁面的機制。
提議的 API 會打開門,來表示
日後如有需要,使用中的 offset
和 size
引數
目前未使用的 zx_vmo_op_range()
。正在將範圍支援新增至
鎖定 API (頁面精細程度鎖定) 看起來就像是
產生新提案這麼做可以造福備用規模的客戶
具有個別 VMO 的可捨棄區域。
捨棄的核心實作方式
核心收回可捨棄的 VMO 時,會解開其頁面和追蹤
狀態的 discarded
。日後解鎖的網頁請求會在
discarded
狀態;一旦 VMO 再次鎖定,discarded
狀態就會變為
已清除。另一個方法是直接解碼網頁,
明確追蹤狀態不過,追蹤 discarded
狀態可讓您
以及更嚴格的故障模式舉例來說,假設客戶
其位址空間對應的可捨棄 VMO,核心會在某些位置捨棄
點。如果用戶端現在沒有先嘗試透過對應關係存取 VMO
鎖定 VMO,會產生嚴重的頁面錯誤而如果核心是為了
之後解鎖存取時,就不會產生
則無聲將網頁傳送給客戶。可能沒有偵測到
會導致非預期的錯誤,因為無非預期的網頁。
另一個替代方案是在內部將 VMO 調整為零。這個 預設提供所需的故障模型 不必經過特別設定 狀態追蹤。不過,這種做法需要 VMO 的實作定義大小和外部大小 使用者看到的內容雖然內部實作定義的大小十分簡單 一些實用技巧,在日後可能也對其他用途有利 有兩個不同的大小概念,不僅令人困惑,也容易發生錯誤。直到我們 有其他具體用途 可以在內部部署 除了外部尺寸,我們也選擇避免採取這種做法。
透過原子研究加快鎖定 API 的速度
這項鎖定最佳化提供另一種低延遲的鎖定方式,可 解鎖可捨棄的 VMO,供預期要鎖定的用戶端使用 並經常解鎖這只是效能最佳化, 像是我們日後在需要時新增的功能
API 使用名為 Metex 的鎖定基元,與 Zircon 類似 使用 futex,透過使用者空間原子快速鎖定,因此能節省 因此必須考量 Ssyscall 的成本
可捨棄的 VMO 可與 Meex 建立關聯,後者將用於鎖定
而非 zx_vmo_op_range()
的系統呼叫。在每次的對戰中
狀態:已鎖定 (使用者空間用戶端使用中)、可捨棄 (適用於
核心重述內容) 和「needs syscall」(可能已由
才能檢查狀態上鎖與解鎖
VMO 會在不進入核心的情況下執行,方法是將
設定鎖定和可捨棄的圖表狀態核心捨棄
VMO 會不可分割地將其狀態轉換為「needs syscall」,表示
用戶端必須與核心同步處理,才能檢查捨棄的狀態。
本提案的細節超出此 RFC 規範,且
獨立的執行個體
頁面式建立 API
網頁程式支援的 VMO 基本上就是可捨棄的 VMO Pager 提供的機制,可以視需要重新填入已捨棄的頁面。由於 這個 RFC 中提議的可捨棄記憶體無法匿名捨棄 記憶體;另一種類型是備份的可捨棄記憶體 blobfs 使用者分頁所填入的 blob 記憶體內表示法。 瞭解這一點,我們可以考慮使用其他建立 API,並 可捨棄的 VMO 與呼叫器相關聯。VMO 建立呼叫可能會 如下所示:
zx_pager_create(0, &pager_handle);
zx_pager_create_vmo(pager_handle, 0, pager_port_handle, vmo_key, vmo_size,
&vmo_handle);
「鎖定及解鎖」功能的運作方式與先前建議使用 zx_vmo_op_range()
相同。
只有在解鎖時,核心才能捨棄 VMO 中的頁面。
這麼做的好處是它為我們提供統合式建立 API 無論檔案是否採用備份,都可用於其他可捨棄的記憶體 或匿名
不過,本例中的網頁控制項實際上並非用於特殊用途。開始時間 可能只會處理 0 的 建立網頁呼叫器更適用於需要建立頁面 填入特定內容隆重推出 但就技術複雜性而言 只是為了隨需建立一個網頁而不需負擔效能負擔 不需要;這項功能已在核心中 (非呼叫式呼叫) VMO。
使用保留器物件鎖定
這裡提議的 Locking API 會保留可捨棄的 VMO 可能發生的錯誤 可能會意外 (或惡意) 解鎖。某些情況下 程序認為 VMO 已鎖定,但其他程序已解鎖,即 第二種流程會產生額外的解鎖這會導致第一項程序 存取 VMO 時即使已正確鎖定,仍會發生錯誤或當機 務必在存取前 確認映像檔存在
與其為鎖定和解鎖,我們可以利用一個 保留器物件,用於在建立 VMO 時鎖定 VMO, 已刪除。
zx_vmo_create_retainer(vmo_handle, &retainer_handle);
只要協定控點開啟,VMO 就會保持鎖定狀態。在 如上例,這兩個程序將使用各自的控管服務 移除 VMO,避免發生錯誤額外的額外解鎖這個上鎖 可以降低這類錯誤發生的可能性 。
但核心的缺點是,核心需要儲存更多中繼資料才能追蹤
VMO 的鎖定狀態我們現在有一組與 Service 相關的保留器物件
有可捨棄的 VMO,而非單一 lock_count
欄位我們也可能會
並設定長度上限
惡意使用者造成核心無無限成長。
重組順序的優先順序
為簡化操作流程,核心將收回未鎖定
以 LRU 順序排列的可捨棄 VMO。我們可以試著找出
如有需要,請指定日後重組的優先順序 (每個 VM 中的 VMO)
優先頻帶仍可按照 LRU 順序收回)。提議的 API 會留下
以便日後透過未使用的 buffer
支援這項功能
zx_vmo_op_range()
中的 ZX_VMO_OP_UNLOCK
參數。
不過,我們可能不需要這樣的控管層級;全球 LRU 可能就足夠了如果客戶希望進一步掌控 某些緩衝區被回收時,可能會改為選擇釋放記憶體壓力 信號,並捨棄緩衝區本身
與其他回收策略互動
目前還有兩種機制可收回記憶體:
- 使用者呼叫器支援的記憶體 (記憶體內 blob) 遭到剔除;這項作業是由 處於 CRITICAL 記憶體壓力水平 (OOM 附近) 的核心。
- 記憶體壓力信號:使用者空間元件本身會釋出記憶體 嚴重和警告的記憶體壓力等級。
我們需要找出這種配置中可捨棄記憶體的位置 確保沒有任何一種回收策略會減輕大部分的負擔。 例如,我們可能希望維持 檔案,以捨棄可捨棄的記憶體。
鎖定支援呼叫器的 VMO
我們可以將 ZX_VMO_OP_LOCK
和 ZX_VMO_OP_UNLOCK
作業擴充至
日後受到呼叫的 VMO 影響。一直以來都渴望阻止使用者鎖定
如果有使用者呼叫器支援的 VMO,我們可能會提供
以及使用案例舉例來說,blobf 可能會將 VMO 鎖定在記憶體中的 blob
是否對任何核心 LRU 撤銷配置來說太重要或不適合
這樣就能避免重新排列頁面的效能成本。
鎖定呼叫器支援的 VMO 會與可捨棄的記憶體 API 緊密連結 由於使用者呼叫器支援的 VMO 基本上可以捨棄為可捨棄的 VMO 記憶體中,使用者呼叫器提供利用特殊機制重新填入資料 網頁。「鎖定」和「解鎖」功能會同時套用至這兩種可捨棄的類型 記憶體的主要差異在於 建立並填入資料
決定何時為捨棄的 VMO 重新填入
客戶可能需要某種方式,才能判斷何時可以安全重新填入已捨棄的項目 VMO。如果記憶體不足的 VMO 已填入資料,則其他頁面 都可能會使系統的記憶體壓力變大 OOM。此外,VMO 隨後解鎖後 如果記憶體壓力仍未解決,就會捨棄這些值。這可能會導致輾轉現象 用戶端會重複重新填入 VMO,只有查看核心很快就會捨棄 VMO 。
目前唯一能觀察系統記憶體壓力等級的機制是
訂閱 fuchsia.memorypressure
服務,這類流量可以相當類似
需要耗費大量資金我們可以考慮擴充此服務
以執行一次性查詢您也可以傳回
透過 zx_vmo_lock_state
結構體呈現壓力程度的指標,任一者為
目前記憶體壓力等級本身,或約略擷取的布林值
檢查系統是否處於記憶體壓力
針對協助追蹤已解鎖 VMO 的存取行為進行偵錯
在建構標記之後啟用額外的檢查,而這會失敗 已解鎖的可捨棄 VMO 上的系統呼叫。這樣開發人員就能輕鬆找出 當 VMO 存取作業不必依賴鎖定時 VMO 會在記憶體壓力下遭到捨棄,但最終會導致故障。 將 VMO 鎖定狀態的檢查項目 可能會快速增加費用 日後也無法在正式環境中啟用 但事實證明是偵錯工具的實用做法
利用對應關係擷取已解鎖的 VMO 存取權,可能更難 為達成這個目標,我們可以嘗試以下做法:
- 在已解鎖的可捨棄 VMO 取消對應時取消對應。透過這個方法 必須確保現有 VMO / VMAR 語意維持不變
- 教導 ASAN 如何解鎖鎖定 / 解鎖呼叫,告訴 ASAN
地圖應視為中毒,直到再次鎖定時,使用
ASAN_POISON_MEMORY_REGION
敬上 存取 API
既有藝術品和參考資料
- Android 上的
ashmem
ReclaimVirtualMemory
(Windows)- macOS 上的
NSCache