RFC-0012:Zircon 可捨棄記憶體

RFC-0012:Zircon 可捨棄記憶體
狀態已接受
區域
  • 核心
說明

說明使用者空間應用程式的機制,用來向核心指出特定記憶體緩衝區符合恢復資格。

問題
變更
作者
審查人員
提交日期 (年/月)2020-10-27
審查日期 (年/月)2020-12-02

摘要

這個 RFC 描述了一種機制,使用者空間應用程式可透過這項機制向核心指出特定記憶體緩衝區符合恢復資格。當系統可用記憶體不足時,核心可以自由捨棄這些緩衝區。

提振精神

在 Zircon 等超額配置系統中,管理可用記憶體是一大問題,當使用者應用程式可分配的記憶體超過系統目前可用的記憶體時,就可以派上用場。方法是使用虛擬記憶體物件 (VMO),這類物件會延遲由實體頁面做為部分修訂版本。

如果高估任何時間點會使用的實體記憶體,如果以此為基礎,無法進一步處理記憶體配置要求,可能會導致資料表保留可用記憶體。由於應用程式會為了快取而使用大量記憶體,因此這可能會影響效能。另一方面,低估使用中的可用記憶體量,可能會導致系統快速用完系統中所有可用的記憶體,導致記憶體不足 (OOM) 的情況。此外,「免費」記憶體本身的定義相當複雜。

Zircon 核心會監控可用實體記憶體的數量,並產生不同層級的記憶體壓力信號。這些信號的用意是允許使用者空間應用程式依據整個系統的免費記憶體等級,縮減 (或增加) 記憶體用量。雖然這有助於防止系統耗盡記憶體,但將這些信號 (核心) 與作答者 (使用者應用程式) 分離的做法並不是理想的做法。回應記憶體壓力的程序,並未充分說明他們應釋放的記憶體,而核心可更準確地掌握系統中的全域記憶體用量,也可能會考慮其他形式的可回收記憶體,例如可能遭到撤銷的使用者分頁器支援記憶體。

這個 RFC 提議的機制,可讓核心在記憶體壓力下直接收回使用者空間記憶體緩衝區。這種做法有幾個優點:

  • 以便進一步控制剔除記憶體量;核心可以查詢可用記憶體層級,並只移除必要的記憶體。
  • 核心可以使用 LRU 配置來捨棄記憶體,這可能更適合在記憶體中納入目前的工作集。
  • 回應記憶體壓力信號時,使用者空間有時會拖慢記憶體。在某些情況下,可能太晚導致系統復原。
  • 使用者空間用戶端為了回應記憶體壓力而喚醒使用者,有時可能會需要更多記憶體。

設計

總覽

可捨棄記憶體通訊協定的運作方式大致如下:

  1. 使用者空間程序會建立 VMO,並標示為「discardable」
  2. 在直接 (zx_vmo_read/zx_vmo_write) 或位址空間的對應 (zx_vmar_map) 存取 VMO 之前,這個程序會「鎖定」VMO,表示 VM 正在使用中。
  3. 程序完成後,程序會鎖定 VMO,表示不再需要。核心會將所有未解鎖的 VM 視為回收條件,並且可在記憶體壓力下自由捨棄這些 VM。
  4. 當程序需要再次存取 VMO 時,會嘗試鎖定該 VM。這個鎖定現在可以透過其中一種方式成功。
    • 鎖定可以成功,但 VMO 的頁面仍保持不變,也就是核心尚未捨棄。
    • 如果核心捨棄 VMO,鎖定也會成功,同時向用戶端指出其頁面已被捨棄,以便客戶重新初始化 VM 或採取其他必要動作。
  5. 這項程序完成後,系統會再次解鎖 VMO。如需要,鎖定及解鎖可能會以此方式重複。

請注意,可捨棄的記憶體並不能直接取代記憶體壓力信號。觀察記憶體壓力變化對其他元件層級決策 (例如選擇啟動大量記憶體活動或執行緒的時間) 仍有幫助。日後我們也可以使用這些信號終止元件中的閒置程序。記憶體壓力信號也提供元件,讓元件能夠更精確地控制所需的記憶體和可用時間。

可捨棄的記憶體 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_LOCKZX_VMO_OP_TRY_LOCKZX_VMO_OP_UNLOCK 鎖定和解鎖功能。

  • 鎖定和解鎖功能會套用至整個 VMO,因此 offsetsize 應橫跨 VMO 的所有範圍。在 VMO 中鎖定及解鎖較小的範圍會發生錯誤。雖然目前的實作不必嚴格要求 offsetsize,但請確保只有整個 VMO 範圍視為有效,日後即可新增子範圍支援,而無需變更用戶端的行為。

  • ZX_VMO_OP_TRY_LOCK 作業會嘗試鎖定 VMO,且可能會失敗。 如果核心未捨棄 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 結構的外點。這個結構適用於讓核心傳回用戶端可能認為實用的資訊,並由下列項目組成:

    • offsetsize 會追蹤鎖定範圍:這些是用戶端傳入的 sizeoffset 引數。這些傳回只是為了方便起見而傳回,因此用戶端不需要分別追蹤範圍,可直接使用傳回的結構。如果呼叫成功,這些呼叫一律會與傳入 zx_vmo_op_range() 呼叫的 sizeoffset 值相同。
    • discarded_offsetdiscarded_size 會追蹤捨棄範圍:這是包含已捨棄頁面的鎖定範圍內最大範圍。 系統可能不會捨棄這個範圍內的所有網頁,而只是這個範圍內所有已捨棄子範圍的聯集,也可能包含尚未捨棄的頁面。在使用目前的 API 時,如果核心捨棄 VMO,捨棄的範圍將會橫跨整個 VMO。如果未刪除,discarded_offsetdiscarded_size 都會設為零。
  • 鎖定本身並不會提交至 VMO 中的任何頁面。只會將 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 介面將會擴充,以支援 op_range()ZX_VMO_OP_LOCKZX_VMO_OP_TRY_LOCKZX_VMO_OP_UNLOCK 作業。Rust、Go 和 Dart 繫結也會一併更新。

這個 API 可讓用戶端靈活地在多個程序之間共用可捨棄的 VMO。每個需要存取 VMO 的程序都可獨立執行,並視需求鎖定及解鎖 VMO。基於對鎖定狀態的假設,不需要在程序之間謹慎協調。只有在沒有人鎖定的 VMO 時,核心才會將 VMO 視為可重建。

VMO 相關限制

  • 可捨棄的記憶體 API 僅適用於 VmObjectPaged 類型,因為定義無法捨棄 VmObjectPhysical

  • API 與 VMO 副本 (快照和二氧化碳副本) 和配量不相容,因為在複製階層中捨棄 VMO 可能會導致意料之外的行為。zx_vmo_create_child() 系統呼叫會在可捨棄的 VMO 上失敗。

  • 無法在 zx_pager_create_vmo()options 引數中使用 ZX_VMO_DISCARDABLE 旗標。這麼做的一大原因是,可以複製分頁器支援的 VMO,且可捨棄的 VMO。此外,以分頁支援的 VMO 來說,隱含權會隱含,因此不需要額外的標記。

與現有 VMO 作業互動

現有 VMO 作業的語意仍維持不變。舉例來說,zx_vmo_read() 不會在允許作業前驗證可捨棄的 VMO 是否已鎖定。用戶端有責任確保在存取 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 就會加入全域可回收清單。

回收邏輯

可捨棄的 VMO 會在 lock_count 降至零時新增至全域可回收清單,並在再次鎖定時移除。這樣可保留系統中所有未解鎖可捨棄 VMO 的 LRU 順序。在記憶體壓力下,核心可以依序從這份清單中取消 VMO 佇列並捨棄這些 VMO,然後逐一查看空記憶體等級。這是實際執行重組邏輯的簡單版本。我們稍後會說明還有幾點需要注意的事項

捨棄作業

將 VMO 的所有頁面,並將 VMO 的內部狀態設為 discarded,藉此在核心端實作「discard」。如果 VMO 的狀態為 discardedVmObjectPaged::GetPageLocked() 就會失敗並顯示 ZX_ERR_NOT_FOUND。後續的 ZX_VMO_OP_LOCK 作業會清除 discarded 狀態。GetPageLocked() 是向下漏斗所有 VMO 頁面存取的函式,包含透過 zx_vmo_read/write 系統呼叫和透過 VM 對應存取網頁。這讓我們能夠在未解鎖的 VMO 上進行系統呼叫失敗,以及在透過對應存取已捨棄的未鎖定 VMO 時,產生例外狀況。

實作

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

效能

效能影響可能因用戶端用途而異。用戶端使用 API 時,需要留意一些事項。

  • 在存取前鎖定及解鎖可捨棄的 VMO 的 zx_vmo_op_range() 系統呼叫,可能會對效能重要路徑造成明顯延遲。因此,應將系統呼叫用於程式碼路徑,如此一來,程式碼路徑即可能容忍或隱藏較長的延遲時間。
  • 此外,用戶端可能會因為快取儲存在記憶體中較長時間而提高效能。現在用戶端在記憶體壓力下,捨棄的緩衝區可以保留較長時間,因為核心只會視需要捨棄記憶體。用戶端可以透過快取命中率、需要重新初始化緩衝區的次數等方式追蹤這項變更。

安全性考量

無。

隱私權注意事項

無。

測試

  • 核心測試 / 單元測試,從多個執行緒執行新 API。
  • 驗證核心端重組行為的單元測試,只能捨棄未鎖定的 VMO。

說明文件

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

缺點、替代方案和未知

在 VMO 內鎖定範圍

復原的精細程度會選為整個 VMO,而不是支援更精細的 VMO 範圍捨棄作業。背後的原因有幾個

  • 重建 VMO 時,可能會覺得有部分捨棄頁面並不容易。考量到一般用途 (其中 VMO 用來代表匿名記憶體緩衝區),重新填入捨棄的頁面可能無法填滿,而剩餘的頁面可能並不合理。單獨保留 VMO 頁面的子集可能也不是很有價值,亦即只有在已完整填入 VMO 時,VMO 才有意義。
  • VMO 精細程度有助於簡化 VmObjectPaged 實作,並盡可能減少追蹤中繼資料。我們不需要追蹤鎖定範圍,之後就能比對解鎖條件。也不需要處理複雜的範圍合併問題。
  • 此做法也會將回收邏輯保持精簡,以便一次釋放大量記憶體。如要改為支援頁面精細程度,可能需要維護網頁佇列和可捨棄的網頁,類似於我們用來移除使用者呼叫器備份網頁的機制。

提出的 API 不會讓大門保持開啟,藉此表示日後可收回的範圍,而 zx_vmo_op_range() 中目前未使用的 offsetsize 引數。為鎖定 API 新增範圍支援 (頁面精細程度鎖定) 似乎是目前提案的自然延伸。這將對用戶端有益處,因為備份小型的可捨棄區域具有個別 VMO 的成本可能十分有限。

捨棄核心實作

核心收回可捨棄的 VMO 時,會撤銷其頁面,並以 discarded 追蹤狀態。日後解鎖的網頁要求會失敗並處於 discarded 狀態;一旦 VMO 再次鎖定,就會清除 discarded 狀態。另一種替代方法是在不明確追蹤狀態的情況下,直接卸除頁面。不過,追蹤 discarded 狀態可提供更嚴格的故障模型。舉例來說,想想用戶端在位址空間中對應可捨棄的 VMO 的情況,而核心在某個時間點就會捨棄該 VM。如果用戶端現在在未先鎖定 VMO 的情況下,嘗試透過對應存取 VMO,就會發生嚴重的頁面錯誤。反之,如果核心僅限停用頁面,後續未解鎖的存取權則只會導致零頁面直接發送給用戶端。這有可能會發生系統未偵測到的問題,或由於沒有預期的網頁,導致發生更細微的錯誤。

或者,您也可以在內部將 VMO 調整為零。這可讓我們提供預設使用的失敗模型,您無需執行任何明確狀態追蹤。不過,這麼做除了要追蹤使用者看到的外部大小之外,還必須追蹤 VMO 內部實作定義的大小。雖然設定內部實作定義的大小是很實用的技巧,未來可能也會受益於其他用途,但如果同時有兩個不同的大小標記,不但會造成混淆,也容易發生錯誤。因此,在我們有其他具體用途可明顯受益於外部大小之外,我們決定避免採用這種做法。

透過原子學加快鎖定 API 的速度

這項鎖定最佳化功能提供一個另一種低延遲選項,可用於鎖定及解鎖可捨棄的 VMO,非常適合預期會經常鎖定及解鎖的用戶端使用。這純粹只是效能最佳化,日後若有需要,我們也可以加入這項功能。

這個 API 採用稱為 Metex 的鎖定基元,這與 Zircon 服用類似,可讓您透過使用者空間原子快速鎖定,從而節省系統呼叫的成本。

可捨棄的 VMO 可與 Meet 建立關聯,用於鎖定及解除鎖定,而不是 zx_vmo_op_range() 系統呼叫。Meet 可有三種狀態:鎖定 (使用者空間用戶端使用)、可捨棄 (符合核心復原) 和「需要 syscall」(核心可能已收回,系統需要透過 syscall 來檢查狀態。鎖定及解鎖 VMO 時,不必進入核心,方法是在鎖定和可捨棄之間,以不可部分的形式翻轉碰觸狀態。當核心捨棄 VMO 時,其狀態會以不可分割的形式將狀態轉換成「needs syscall」,表示用戶端必須與核心同步,才能檢查捨棄的狀態。本提案的更多詳細資料不在這個 RFC 的涵蓋範圍內,將透過獨立單位提供。

以 Pager 為基礎的建立 API

網頁器支援的 VMO 基本上就是可捨棄的 VMO,因為 Pager 會提供機制,讓您可視需求重新填入捨棄的頁面。此 RFC 中提議的可捨棄記憶體類型是匿名的可捨棄記憶體;另一個類型是檔案支援的可捨棄記憶體,以 blobfs 使用者頁面器填入的 blob 記憶體內表示法為例。請記住,我們可以考慮使用替代建立 API,其中可捨棄的 VMO 會與 Pager 建立關聯。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,無論檔案支援或匿名皆是如此。

不過,在本例中,翻頁元素實際上並不具有特殊用途。由於負責處理一般匿名記憶體,因此可能只提供零個隨選網頁。呼叫器更適合用於需要以特殊方式填入特定內容的網頁。我們針對技術複雜度和效能負擔引進另一層的間接攻擊,目的是不必要就建立任何隨選頁面,因此在核心中已內建此功能,適用於一般 (非頁面式 VM) 的 VMO。

使用保留工具物件鎖定

此處建議的鎖定 API 會留給可能出現錯誤,因為可捨棄的 VMO 可能會遭到無意間 (或惡意) 解鎖。在某些情況下,可能會發現 VMO 已鎖定,但另一個程序已解鎖,即第二項程序會發出額外解鎖問題。這會導致第一個程序存取 VMO 時出錯或當機,即使該程序已在存取前已正確鎖定亦然。

我們可以使用保留物件進行鎖定,而不鎖定與解鎖作業,這樣會在建立 VMO 時將 VMO 鎖定,並在刪除時將其解鎖。

zx_vmo_create_retainer(vmo_handle, &retainer_handle);

只要保留器控點保持開啟,VMO 就會保持鎖定狀態。在上述範例中,這兩個程序會各自使用自己的保留工具鎖定 VMO,避免發生錯誤的額外解鎖情形。這種鎖定模型可降低發生這類錯誤的可能性,並讓您輕鬆診斷發生錯誤的情況。

缺點是核心必須儲存更多中繼資料,才能追蹤 VMO 的鎖定狀態。我們現在有與可捨棄 VMO 相關聯的保留工具物件清單,而非單一 lock_count 欄位。如果您想避免發生惡意使用者導致核心成長不受限的可能性,我們可能也想要限制這份清單的長度。

要求復權順序

為讓您輕鬆上手,核心將以 LRU 順序收回未解鎖的免受保護 VMO。我們可以嘗試讓用戶端明確指定日後的復原優先順序 (如果每個優先頻帶中的 VMO 仍可以 LRU 順序收回)。提議的 API 日後會透過 ZX_VMO_OP_UNLOCK 中目前未使用的 buffer 參數,支援這項功能,以便日後支援這項功能。zx_vmo_op_range()

不過,我們可能不需要這樣的程度的控管;使用全域 LRU 順序可能就足夠了。如果用戶端想要進一步控制特定緩衝區的回收時間,可以改為選擇採用記憶體壓力信號,並自行捨棄這些緩衝區。

與其他聲明策略互動

目前提供兩種收回記憶體的機制:

  • 移除使用者分頁器支援記憶體 (記憶體內的 blob),由核心在 CRITICAL 記憶體壓力等級 (以及 OOM 附近) 完成。
  • 記憶體壓力信號,也就是使用者空間元件會在「CRITICAL」和「WARNING」記憶體壓力等級釋出記憶體。

我們必須找出這種配置中可捨棄的記憶體位置,確保沒有任何單一回收策略能承受大部分的負擔。舉例來說,我們可能希望將檔案支援記憶體的某種剔除率維持在可捨棄的記憶體。

鎖定呼叫器支援的 VMO

我們日後可以將 ZX_VMO_OP_LOCKZX_VMO_OP_UNLOCK 作業延伸至支援分頁功能的 VMO。一直以來,一直都希望支援使用者呼叫已備份的 VMO,如果出現具體用途,我們或許會想提供相關資訊。例如,blobfs 可以將記憶體內的 VMO 鎖定在記憶體中重要的 blob,或者不符合核心 LRU 撤銷配置的規模,藉此避免重組 VM 的效能成本。

鎖定分頁式 VMO 會與可捨棄的記憶體 API 完美結合,因為使用者呼叫器支援的 VMO 基本上可以視為可捨棄記憶體類型,而使用者呼叫器會提供特殊機制來重新填入頁面。鎖定和解鎖功能接著適用於兩種可捨棄的記憶體,這兩種類型的主要差異在於建立和填入的方式。

決定何時重新填入捨棄的 VMO

用戶端可能需要找出可以安全重新填入捨棄 VMO 的方法。如果因為記憶體壓力而重新填入 VMO,則提交的額外頁面可能會損害系統的記憶體壓力,將 VM 推送到更接近 OOM 的位置。此外,在 VMO 隨後解鎖後,如果記憶體壓力保持不變,VMO 就可能遭捨棄。這可能會導致發生輾轉現象,也就是用戶端重複重新填入 VMO,只會看到核心很快就會捨棄。

目前,觀測系統記憶體壓力等級的唯一機制是訂閱 fuchsia.memorypressure 服務,而這種做法可能會相當昂貴。我們可以考慮擴充此服務,以提供執行一次性查詢的方式。我們也可以考慮透過 zx_vmo_lock_state 結構傳回壓力等級指標,可以是目前的記憶體壓力等級,也可以粗略擷取系統是否處於記憶體壓力下。

透過偵錯輔助功能追蹤已解鎖的 VMO 存取作業

在建構標記後方啟用其他檢查,以便在未解鎖的可捨棄 VMO 上進行系統呼叫失敗。這有助於開發人員輕鬆找出 VMO 存取權未在鎖定前發生的錯誤,而不必仰賴在記憶體壓力下捨棄 VMO,進而導致失敗。由於我們日後會新增範圍支援,因此對 VMO 的鎖定狀態檢查可能會耗用大量資源,因此無法在實際工作環境中啟用,但也許可做為偵錯工具使用。

透過對應擷取已解鎖的 VMO 存取,可能較難實作。我們可嘗試下列幾種方法達成這個目標:

  • 取消對應已解鎖的對應可捨棄 VMO。透過這個方法,我們需要確認現有的 VMO / VMAR 語意保持不變。
  • 訓練 ASAN 前後的包裝函式,告知 ASAN 已解鎖 VMO 的對應應被視為中毒,直到再次鎖定為止,都會使用 ASAN_POISON_MEMORY_REGION 介面。

先前的圖片和參考資料