本文件說明 Sysmem 用來管理記憶體的 VMOs 階層。本文假設您對 Fuchsia 上的虛擬記憶體和非 sysmem 記憶體的計算有所瞭解。
堆積
所有 VMOs 都是從堆積分配。目前的堆為:
名稱 | 固定集區 | 說明 |
---|---|---|
Sysmem-core | 否 | 一般主記憶體 |
SysmemAmlogicProtectedPool | 是 | 從 CPU 無法存取的記憶體 |
SysmemContiguousPool | 是 | 實體相鄰的主記憶體 |
tee_secure | 是 | 專用於 Amlogic 解密編碼影片的記憶體 |
Sysmem-external-heap (可能有多個) | 否 | 目前用於金魚 |
Sysmem-contig-core | 否 | 連續主記憶體;僅用於沒有 SysmemContiguousPool 的系統 |
並非所有堆積都會從固定集區進行子分配,例如「核心」和「連結核心」可以從主記憶體進行分配。部分堆疊會直接對應至 fuchsia.sysmem.HeapType 值,但所選堆疊也可能取決於 is_physically_contiguous
等 BufferMemorySettings 成員。
Sysmem-core
Sysmem-core 會從主記憶體進行配置。如果未在記憶體中新增任何限制,則會採用預設選項。
SysmemContiguousPool
CPU 和系統上的其他裝置只需要虛擬連續記憶體。它們可以選擇任何實體位址的任意頁面,並依賴 MMU 硬體指派新的連續虛擬位址。這樣一來,您就能輕鬆分配記憶體,因為任何實體頁面都可以使用。
不過,部分硬體不具備 MMU 或散列收集能力。這類硬體需要實體相鄰的位址空間,也就是記憶體中每個頁面在 RAM 中的順序相同。隨著系統執行,主記憶體會變得越來越碎裂,因此無法找到長時間執行的空閒記憶體,因為其他已分配的頁面會隨機散布在記憶體中。
為避免發生這個問題,Sysmem 會使用獨立的連續資源池。在記憶體分割前,它會在開機後不久配置大量記憶體集區,然後將較小的部分交給應用程式。理論上,這類記憶體仍可能會分散,但實際上,由於只有較大的記憶體區塊會從集區中分配,且區塊中的所有記憶體會同時釋回至集區,因此這項做法是可行的。
SysmemAmlogicProtectedPool
在搭載 Amlogic SoC 的系統中,DRM (受保護的記憶體) 影片必須在具有存取控制的特殊區域中分配,以確保應用程式無法讀取已解密的影片。這些區域必須由 CPU 無法存取,且只有 GPU 和其他硬體在硬體已連線的特殊模式下才能存取,以確保不會發生記憶體外洩。
記憶體無法隨意標示為受保護或未受保護。硬體只能將少數 (少於 32 個) 區域標示為受保護區域。為支援此功能,sysmem 可以在開機後立即配置受保護的資源池 (類似連續資源池),並告知韌體保護該區域中的所有記憶體。然後,它可以從這個受保護記憶體集區中進行子分配。
tee_secure
tee_secure 適用於另一種保護記憶體,可儲存不同類型的資料。韌體會配置這個區域,而 ZBI其他驅動程式庫可以從韌體中擷取記憶體資訊和位置,然後通知 sysmem。Sysmem 可視需要從這個堆積中進行子分配。
Sysmem-external-heap
外部堆疊不一定會使用實體記憶體。舉例來說,Goldfish 堆積是外部堆積,可代表 FEMU 虛擬機器外部的視訊記憶體。用戶端可以傳遞 VMO 句柄,但不應直接寫入記憶體;相反地,Goldfish 驅動程式庫會使用 VMO koid 查詢主機資源。
VMO 階層
Sysmem 會使用 VMOs 階層來追蹤用戶端的記憶體用量。有三個因素可以讓 VMO 持續運作:
VMO 的控制代碼。
將 VMO 對應至程序的位址空間。
PMT,表示已對應至裝置。
基於安全性考量,sysmem 必須等到所有這類參照都消失,才能回收 VMO 用於其他用戶端的記憶體。對於一般 VMO,核心會在所有參照都消失後,只銷毀 VMO,藉此處理這項問題。不過,sysmem 會從較大的實體位址範圍中子分配 VMOs,因此需要洞察 VMO 是否已遭到銷毀,以便決定要重複使用的記憶體範圍。
核心會支援 ZX_VMO_ZERO_CHILDREN
信號,以便支援這些用途:如果 VMO 的所有子項都已關閉,則會在父項 VMO 上傳送 ZX_VMO_ZERO_CHILDREN
信號。
用戶端葉節點 VMOs
這些是交給用戶端的 VMOs;用戶端會在分配 VMOs 之前,透過呼叫 BufferCollection.SetName 為其命名。用戶端也可以直接在 VMOs 上設定 ZX_PROP_NAME
,但不建議這麼做,因為 sysmem 驅動程式無法存取該名稱。
只要 BufferCollection 持續參照這些 VMOs,Sysmem 也會保留這些 VMOs 的參照,即使目前沒有子項具有 VMO 句柄也一樣。
中間 VMOs
每個葉節 VMO 都有一個中間 VMO 做為父項。葉狀和中間 VMOs 之間的對應關係為 1 對 1。名稱是由堆積設定,通常會針對來自堆積的所有 VMOs 固定。例如,SysmemContiguousPool-child 是連續區塊的 VMOs。
Sysmem 會使用這些 VMOs 偵測是否已清除所有葉子 VMO 參照項目;一旦收到 ZX_VMO_ZERO_CHILDREN 信號,它就會知道可以安全刪除 VMO 並可能重複使用空間。中間 VMOs 絕不會傳遞至 sysmem 程序外,因此用戶端無法直接參照這些 VMOs。
堆積 VMOs
這些代表堆積中 VMOs 所分配的記憶體整個集區。這些記憶體通常會在開機後立即配置,以確保有足夠的記憶體可用。堆積 VMOs 也可能代表劃出的實體位址範圍,例如 tee_secure 會重疊由啟動載入程式指派的特定實體範圍。
中間 VMOs 會以堆積 vmo 的切片形式分配,因此每個中間 VMO 都代表堆積中不同的記憶體範圍
如果堆積區不代表記憶體的實體池,則不需要堆積區 VMO。在這種情況下,系統會在沒有父項 VMO 的情況下,分配中間 VMO。
回報記憶體
檢查
Sysmem 會提供「檢查」階層,將記憶體用量回報給快照和其他用戶端應用程式。以下是簡單的階層範例:
root:
sysmem:
collections:
logical-collection-0:
allocator_id = 1
heap = 0
min_coded_height = 1024
min_coded_width = 600
name = vc-framebuffer
pixel_format = 101
pixel_format_modifier = 0
size_bytes = 2490368
vmo_count = 1
collection-5:
channel_koid = 20048
debug_id = 5498
debug_name = driver_host
collection-6:
channel_koid = 20050
debug_id = 5498
debug_name = driver_host
collection-at-allocation-7:
debug_id = 19829
debug_name = virtual-console.cm
min_buffer_count = 1
collection-at-allocation-8:
debug_id = 5498
debug_name = driver_host
collection-at-allocation-9:
debug_id = 5498
debug_name = driver_host
vmo-20085:
koid = 20085
heaps:
SysmemContiguousPool:
allocations_failed = 0
allocations_failed_fragmentation = 0
free_at_high_water_mark = 37498880
high_water_mark = 2490368
id = 1
is_ready = true
last_allocation_failed_timestamp_ns = 0
max_free_at_high_water = 37498880
size = 39989248
used_size = 2490368
vmo-20085:
koid = 20085
size = 2490368
SysmemRamMemoryAllocator:
id = 0
Sysmem 會透過 /dev/diagnostics/class/sysmem/XXX.inspect
檔案中的檢查階層回報記憶體檢視畫面 (其中 XXX 是 3 位數的偽隨機 ID)。每個顯示的邏輯集合都代表一組由一組用戶端分配的相同緩衝區。這些邏輯集合包含該集合中實體中介 VMOs 的 koid 清單。koid 在系統的生命週期中是唯一的,可用於在 memgraph 輸出內容中唯一識別 sysmem VMOs。
所有堆也都有檢查節點。這些資訊可能包括所有子 VMOs 的大小和 koid,以及堆積的完整度,以及是否有任何未成功的配置。某些堆疊只包含名稱和 ID 屬性,而非從中分配的 VMOs 相關資訊。
邏輯集合的 allocator_id
會與用於分配記憶體的堆積 id
相符。
由於 sysmem 無法查看系統中的其他程序,因此檢查資料有限。舉例來說,它不知道哪些其他程序正在保留其 VMOs 的參照,只知道至少有一個程序正在保留。它也不知道建立 VMOs 的用戶端程序確切名稱。Sysmem 用戶端應使用其程序名稱和 koid 呼叫 Allocator.SetDebugClientInfo,但系統不會強制執行這項操作,因此無法保證用戶端設定的名稱正確無誤。
不過,有些資訊只能透過檢查資料判斷。舉例來說,用戶端程序可以保留 BufferCollection 的管道,而無須保留任何 VMOs 的句柄。只有 sysmem 知道其處理程序中 BufferCollection 管道和 VMOs 之間的對應關係。channel_koid
資源會提供管道伺服器 koid 的相關資訊。
ZX_INFO_PROCESS_VMOs
memgraph
和 mem
工具會使用這個系統呼叫。它可以判斷哪些程序有 VMOs 參照,這對於以安全的方式將記憶體歸屬給程序至關重要。
sysmem 使用的 VMO 階層可能會導致這些工具發生問題。舉例來說,mem
會忽略沒有任何已提交記憶體 (已指派記憶體,並由實體記憶體支援) 的 VMOs,以免輸出內容雜亂。這會導致 mem 忽略葉狀 VMO,因為它是樹狀結構中實際分配記憶體的根 VMO。Mem 有一些駭客攻擊,可針對 SysmemContiguousPool
和 SysmemAmlogicProtectedPool
的子項 VMO,在樹狀結構中傳播記憶體資訊 - 它會查看葉節點 VMO 的「大小」,並假設已分配所有記憶體。這項功能只適用於分配時不會重疊的固定大小集區,因此只適用於硬式編碼的集區組合。
外部堆積 VMOs 也相當複雜,因為它們實際上不會佔用訪客虛擬機器中的記憶體。因此,mem 不會回報這些記憶體 (其已提交的記憶體大小為 0),這表示很難將主機系統的記憶體歸因於訪客內的程序。
memgraph -v
會減少記憶體資訊的處理作業,但使用者必須自行處理,才能判斷記憶體用量。由於 VMOs 不一定有一致的名稱,因此也可能難以判斷哪些 VMOs 來自 sysmem。
統一做法
任何想要完整且準確查看 sysmem VMOs 的工具,都必須合成檢查和 ZX_INFO_PROCESS_VMOS
資訊。Sysmem 的檢查資料應是 sysmem VMOs 存在的真實資訊來源,而核心則是哪些程序保留 VMOs 參照的真實資訊來源。這需要透過邏輯緩衝區收集項目進行疊代,並列出其 koids,然後透過 ZX_INFO_PROCESS_VMOS
查看其大小,以及哪些程序參照其子項。
公用程式可為每個程序建立快照 ZX_INFO_HANDLE_TABLE
。接著,它可以使用該資料表在 channel_koid
中查詢 koid,以判斷哪個程序保留了該 BufferCollection。
在某些情況下,系統無法正確計算記憶體。主要問題是管道訊息中保留的句柄並未在任何地方回報,因此無法計算這些參照。用戶端可以將 VMO 句柄塞入管道,但從不從管道讀取資料,甚至連核心都不知道要將記憶體歸給誰。在這些情況下,您可以使用偵錯用途的用戶端資訊做為備用。
未來可能的異動
為每個用戶端建立中間 VMO,讓 sysmem 自行判斷哪些用戶端仍有對應的 VMOs。
請讓元件架構將無法偽造的 ID 傳遞至 sysmem,而非讓用戶端傳遞可偽造的偵錯名稱。