福奇亞的記憶力重塑

大多數作業系統都採用記憶體回收策略,確保任何時間點的執行程序組合都能有效利用所有可用實體記憶體。作業系統有固定數量的實體記憶體 (RAM) 分配給所有執行中的程序,而且可能無法同時容納所有程序。

以最簡單來說,記憶體重構就是替換頁面,如果目前使用者活動較不重要的頁面會替換為更重要的頁面,大多數作業系統都會保留可用頁面集區,確保傳入記憶體配置可以快速完成,不必在等待使用中的頁面釋放時遭到封鎖。

Fuchsia 也採用了類似的策略,也就是嘗試讓可用記憶體容量維持在超過特定閾值。Fuchsia 在核心和使用者空間中運用多種記憶體回收技術。本指南說明這些記憶體回收技巧的運作方式。Fuchsia 也提供一組工具,可用來分析及傾印記憶體用量 (請參閱記憶體用量分析工具)。

呼叫器支援的記憶體清除

使用者空間檔案系統會使用分頁器,在來自外部來源 (例如磁碟) 隨選的隨選檔案中分頁。檔案系統使用 VMO 來代表記憶體中的檔案,而 VMO 會在存取這類 VM 時,以呼叫器服務填入其頁面。

在 Fuchsia 中,blobfs 是一種不可變更的檔案系統,用於託管所有執行檔,使用 Pager 來依需求填入頁面。當系統面臨記憶體壓力時 (也就是可用記憶體的開始量偏低),核心會收回 blobf 支援的頁面以收回記憶體。由於這些網頁存在於磁碟中,因此可視需要重新擷取。

核心會追蹤所有透過分頁回應的記憶體。當可用記憶體不足時,系統會找出適合剔除的候選記憶體。系統會追蹤多個 LRU (最近使用最少) 的頁面佇列追蹤網頁,而核心背景執行緒會定期輪替,遵循「age」(年齡) 頁面。在記憶體壓力下,另一個背景執行緒會收回最舊頁面佇列中的頁面。

當可用記憶體下降時,核心會將較舊和移除政策調整為較為積極、較快且較快速的頁面,以便找到較容易判讀的候選項目。為避免發生輾轉現象,MRU (最近使用) 的網頁佇列永遠不會遭到移除。此佇列的長度取決於系統的流失量。

如果系統相對安靜且記憶體用量穩定,則核心時間頁面的速度會變慢,在 MRU 佇列中則會累積更多網頁。另一方面,如果使用者循環瀏覽多項活動,並持續切換工作集,核心就會嘗試以更積極的方式循環頁面。

使用者空間程序也可以使用移除提示來調整核心移除策略。程序可以使用 DONT_NEED 提示來指出網頁已不再使用,因此適合移除。也可以利用 ALWAYS_NEED 指出網頁重要,不應將其視為剔除,從而避免在有人再次存取網頁時浪費資源。

如要進一步瞭解移除提示,請參閱參考說明文件:zx_vmo_op_rangezx_vmar_op_range

無簡化頁面

只有在寫入時,匿名 VMO (非頁面式 VM) 中的頁面才會填入 / 修訂。核心會使用單例模式零頁面完成讀取作業。即使網頁已承諾寫入,核心仍會嘗試簡化僅填入零的重複頁面,以節省記憶體。核心會定期掃描匿名 VMO 中的實體頁面,設法排除重複的零頁。

頁面表格重新調整

位址空間所述,VMAR 階層可協助核心追蹤虛擬與實體記憶體對應關係。首次存取虛擬位址時,系統會使用位址空間的 VMAR 樹狀結構來查詢基礎實體頁面。接著,虛擬與實體的對應關係會儲存在硬體頁面資料表中,方便 MMU 用於日後查詢。在記憶體壓力下,核心可在已有一段時間未存取的硬體頁面資料表中收回記憶體。如果需要這些對應,可以從 VMAR 樹狀結構重新建構。

可捨棄的 VMO

使用者空間程序可能會建立可捨棄的 VMO 特殊變種版本。用戶端可以根據是否有使用來鎖定及解鎖 不受限制的 VMO。當系統面臨記憶體壓力時,核心會找出已解鎖並釋出這些可捨棄的 VMO。

程式碼範例 (模數錯誤處理):

// Create a discardable VMO.
zx_handle_t vmo;
uint64_t vmo_size = 5 * zx_system_get_page_size();
zx_vmo_create(vmo_size, ZX_VMO_DISCARDABLE, &vmo);

// Lock the VMO.
zx_vmo_lock_state_t lock_state = {};
zx_vmo_op_range(vmo, ZX_VMO_OP_LOCK, 0, vmo_size, &lock_state,
                sizeof(lock_state));

// Use the VMO as desired.
zx_vmo_read(vmo, buf, 0, sizeof(buf));

// Unlock the VMO. The kernel is free to discard it now.
zx_vmo_op_range(vmo, ZX_VMO_OP_UNLOCK, 0, vmo_size, nullptr, 0);

// Lock the VMO again before use.
zx_vmo_op_range(vmo, ZX_VMO_OP_LOCK, 0, vmo_size, &lock_state,
                sizeof(lock_state));

if (lock_state.discarded_size > 0) {
  // The kernel discarded the VMO. Re-initialize it if required.
  zx_vmo_write(vmo, data, 0, sizeof(data));
} else {
  // The kernel did not discard the VMO. Previous contents were preserved.
}

記憶體壓力信號

Fuchsia 提供使用者空間處理程序,方便因應全系統的可用記憶體而直接控制其記憶體用量。用戶端可以註冊以接收記憶體壓力信號,並根據觀察到的記憶體壓力等級採取行動。有三個記憶體壓力等級

名稱說明
1

記憶體壓力等級健康。

已註冊的用戶端可自由保留快取內容,並分配記憶體不受限制。

但是,用戶端應留意,不會在過渡回降至 NORMAL 時主動重新建立快取,導致記憶體激增而立即將層級再次推送至 WARNING。

2

記憶體壓力等級有點限制,如果取消勾選,可能會跨越重要壓力範圍。

註冊的用戶端應將作業最佳化來限制記憶體用量,而非達到最佳效能,例如減少快取大小及非必要的記憶體配置。

用戶端必須謹慎控管工作的工作量,才能收回記憶體,並確保不會導致明顯的效能降低。存在一些記憶體壓力,但為了收回記憶體,對使用者而言沒有回應靈敏度的合理性。

3

記憶體壓力等級相當有限,

已註冊的用戶端應會捨棄所有非必要的記憶體,並且避免分配更多記憶體。否則可能會導致工作終止。如果發生全域記憶體壓力,系統會重新啟動。

用戶端可能會視需要執行費用高昂的工作來收回記憶體,因為如未這麼做,可能會導致終止。在這種情況下,客戶可能會認為成效命中很合理。

比較記憶體壓力信號與可捨棄的 VMO

使用者空間用戶端可以在記憶體壓力信號或可捨棄 VMO 之間挑選,或是根據自己的需求搭配使用兩種回收機制。選擇時需要考量的事項:

  • 記憶體壓力信號可讓用戶端執行快取以外的作業。舉例來說,工作可以拆解工作樹狀結構中非必要的程序。這些程式也可以停止某些會耗用大量記憶體的活動,或是暫停開始新的活動,直到壓力水平正常。
  • 使用可捨棄的 VMO 時,使用者空間用戶端可讓您控制何時釋出記憶體給核心。核心會根據各種因素決定何時釋出記憶體,例如可用記憶體數量、可透過其他方法收回的記憶體等。如果用戶端想要微調其快取的生命週期、何時該剪輯內容等,記憶體壓力信號可能會比較適合。
  • 當程序為了回應記憶體壓力信號而關閉 VMO 時,可捨棄的 VMO 最終可能會保留內容的時間超過。核心可驅動系統捨棄可捨棄的 VMO,而核心對於可用記憶體容量有較多全域背景資訊,因此能確切掌握收回多少。核心也有其他收回記憶體的方式,因此可能無法釋放所有可捨棄的 VMO。另一方面,如果使用者空間用戶端本身要回應記憶體壓力,則可能會每次以相同的方式回應,進而縮減所有快取。
  • 可捨棄的記憶體也能讓核心更快收回記憶體,以便系統更快復原。透過記憶體壓力信號,在核心信號改變壓力等級,以及使用者空間程序做出回應之間,可能會出現一些處理序間通訊 (IPC) 和排程延遲的情況。

OOM (記憶體不足) 重新啟動

所有記憶體回收策略都有可能在面對某些積極的記憶體配置模式時,無法釋放足夠的記憶體。發生這種情況時,核心會在完全關閉檔案系統後選擇重新啟動,以免資料遺失。當可用記憶體等級低於預先設定的 OOM 門檻時,就會觸發 OOM 重新啟動作業。

測試記憶體壓力回應的工具

觀察及測試核心記憶體回收

請使用 k scanner 指令觀察及測試核心使用的重組技巧:頁面器支援的移除、可捨棄的 VMO 補貼、零頁面簡化和頁面表格重建功能。也可以用來測試頁面佇列旋轉 / 緩解策略,以便有效剔除。在序列主控台上執行 k scanner 以查看所有可用的選項:

k scanner
usage:
scanner dump                    : dump scanner info
scanner push_disable            : increase scanner disable count
scanner pop_disable             : decrease scanner disable count
scanner reclaim_all             : attempt to reclaim all possible memory
scanner rotate_queue            : immediately rotate the page queues
scanner reclaim <MB> [only_old] : attempt to reclaim requested MB of memory.
scanner pt_reclaim [on|off]     : turn unused page table reclamation on or off
scanner harvest_accessed        : harvest all page accessed information

k scanner dump 會傾印頁面佇列的目前狀態,以及核心用於回收的其他相關記憶體計數器:

k scanner dump
[SCAN]: Scanner enabled. Triggering informational scan
[SCAN]: Found 4303 zero pages across all of memory
[SCAN]: Found 8995 user-pager backed pages in queue 0
[SCAN]: Found 3278 user-pager backed pages in queue 1
[SCAN]: Found 8947 user-pager backed pages in queue 2
[SCAN]: Found 10776 user-pager backed pages in queue 3
[SCAN]: Found 3981 user-pager backed pages in queue 4
[SCAN]: Found 0 user-pager backed pages in queue 5
[SCAN]: Found 0 user-pager backed pages in queue 6
[SCAN]: Found 0 user-pager backed pages in queue 7
[SCAN]: Found 1347 user-pager backed pages in DontNeed queue
[SCAN]: Found 40 zero forked pages
[SCAN]: Found 0 locked pages in discardable vmos
[SCAN]: Found 0 unlocked pages in discardable vmos
pq: MRU generation is 12 set 10.720698018s ago due to "Active ratio", LRU generation is 6
pq: Pager buckets [8995],[3278],8947,10776,3981,0,{0},0, evict first: 1347, live active/inactive totals: 12273/25051

使用 k scanner reclaimk scanner reclaim_all 測試收回記憶體:

k scanner reclaim_all
[EVICT]: Free memory before eviction was 7161MB and after eviction is 7290MB
[EVICT]: Evicted 33004 user pager backed pages
[SCAN]: De-duped 25 pages that were recently forked from the zero page

使用 k pmm drop_user_pt 重新調整測試頁面資料表:

k pmm
…
pmm drop_user_pt                             : drop all user hardware page tables

觀察並產生記憶體壓力

使用 k pmm mem_avail_state 指令分配記憶體以達到指定的記憶體壓力等級,以便在系統中產生記憶體壓力。這有助於測試整個系統對記憶體壓力的回應:

k pmm mem_avail_state
pmm mem_avail_state info                     : dump memory availability state info
pmm mem_avail_state [step] <state> [<nsecs>] : allocate memory to go to memstate <state>, hold the state for <nsecs> (10s by default). Only works if going to <state> from current state requires allocating memory, can't free up pre-allocated memory. In optional [step] mode, allocation pauses for 1 second at each intermediate memory availability state until <state> is reached.

k pmm mem_avail_state info 會傾印目前的記憶體壓力狀態。

k pmm mem_avail_state info
watermarks: [50M, 60M, 150M, 300M]
debounce: 1M
current state: 4
current bounds: [299M, 16.0E]
free memory: 7253.5M

記憶體可用性狀態會從 0 開始編號,是之前所述的記憶體壓力信號層級超集。

  • OOM 是狀態 0。這是免費記憶體等級,可讓核心決定重新啟動系統。
  • Imminent-OOM 是狀態 1。這是診斷專用的記憶體等級,可從 OOM 層級小幅差異設定。這個程式庫的唯一用途是提供一種方法來安全收集 OOM 診斷資訊,因為可能太晚收集 OOM 層級的診斷資訊。如要進一步瞭解這個層級,請參閱 RFC-0091
  • Critical 是狀態 2。這個層級會觸發 CRITICAL 記憶體壓力訊號。
  • Warning 是狀態 3。這個等級會觸發警告記憶體壓力信號。
  • Normal 是狀態 4。這個層級會觸發 NORMAL 記憶體壓力訊號。

在上述範例中,current state 為 4,也就是「一般」。

watermarks 會顯示記憶體門檻,說明不同的記憶體可用性狀態。上述範例的輸出內容顯示以下記憶體門檻:

OOM: 50MB, Imminent-OOM: 60MB, Critical: 150MB, Warning: 300MB

debounce 是在計算記憶體狀態邊界時使用滑動或錯誤邊界。本例中的資源為 1 MB。

current bounds 會顯示目前記憶體狀態適用的可用記憶體邊界。目前狀態是 Normal,也就是 watermarks,因此 Normal 會從 300 MB 門檻開始。使用 1 MB 去彈功能時,下限為 299 MB。這裡的 Normal 層級沒有適用的上限,此處已設為 UINT64_MAX

最後,系統上的總 free memory 為 7253.5 MB。

請使用 k pmm mem_avail_state X 指令,轉換為記憶體可用性狀態 X,其中 X 是上述的數值記憶體狀態。視需要指定將要求狀態保留多久。您也可以選擇「逐步」進入中繼狀態,並在這些狀態各暫停。

舉例來說,這會觸發轉換到 Critical 記憶體狀態:

k pmm mem_avail_state 2
memory-pressure: memory availability state - Critical
pq: MRU generation is 714 set 4.144414945s ago due to "Active ratio", LRU generation is 708
pq: Pager buckets [3482],[115],317,0,199,0,{6939},0, evict first: 0, live active/inactive totals: 3597/7455
memory-pressure: set target memory to evict 1MB (free memory is 149MB)
Leaked 1817528 pages
Sleeping for 10 seconds...
[EVICT]: Free memory before eviction was 147MB and after eviction is 151MB
[EVICT]: Evicted 986 user pager backed pages
Freed 1817528 pages
memory-pressure: memory availability state - Normal
pq: MRU generation is 717 set 1.213355379s ago due to "Timeout", LRU generation is 711
pq: Pager buckets [4351],[258],149,37,0,1,{5798},0, evict first: 0, live active/inactive totals: 4609/5985

系統藉由分配 1817528 頁面 (頁面大小為 4 KB),轉換為 Critical然後,有 10 秒的睡眠時間 (預設保留狀態),期間 Critical 壓力會持續存在。最後,系統釋出了 1817528 分配的頁面,並將記憶體壓力降至 NormalCritical 狀態轉換會使部分支援分頁回應的記憶體遭到撤銷,您可以看到 [EVICT] 行。

k pmm mem_avail_state 指令是非常實用的工具,可用來測試整個系統的記憶體壓力回應。由於運作原理是分配實際的實體記憶體,因此可在核心及使用者空間中,執行系統丟棄的所有重組機制。

這些額外的 k pmm oom 指令可用於在 OOM 層級測試系統回應。

pmm oom [<rate>]                             : leak memory until oom is triggered, optionally specify the rate at which to leak (in MB per second)
pmm oom hard                                 : leak memory aggressively and keep on leaking
pmm oom signal                               : trigger oom signal without leaking memory

k pmm oom 的輸出範例:

k pmm oom
Disabling VM scanner
memory-pressure: free memory is 49MB, evicting pages to prevent OOM...
pq: MRU generation is 13 set 7.979442243s ago due to "Active ratio", LRU generation is 7
pq: Pager buckets [4538],[4517],3624,4606,13716,4976,{0},0, evict first: 1347, live active/inactive totals: 9055/28269
memory-pressure: found no pages to evict
memory-pressure: free memory after OOM eviction is 49MB
…
memory-pressure: pausing for 8s after OOM mem signal
[00028.317] 02811:03481> [fshost] INFO: [admin-server.cc(33)] received shutdown command over admin interface
[00028.317] 02811:03481> [fshost] INFO: [fs-manager.cc(281)] filesystem shutdown initiated
[00028.317] 02811:38032> [fshost] INFO: [fs-manager.cc(310)] Shutting down /data
[00028.318] 12900:12902> [minfs] INFO: [minfs.cc(1471)] Shutting down
[00028.340] 12900:12902> [minfs] WARNING: [src/storage/minfs/bin/main.cc(53)] Unmounted
[00028.341] 02811:03481> [fshost] INFO: [admin-server.cc(39)] shutdown complete
[00028.342] 02811:02813> [fshost] INFO: [main.cc(309)] terminating
[00028.342] 02687:02689> [driver_manager.cm] INFO: [suspend_handler.cc(205)] Successfully waited for VFS exit completion
memory-pressure: rebooting due to OOM
memory-pressure: stowing crashlog
ZIRCON REBOOT REASON (OOM)
Shutting down debuglog
platform_halt suggested_action 1 reason 3
Rebooting...

模擬使用者空間中的記憶體壓力信號

使用 ffx profile memory signal 指令模擬使用者空間中的記憶體壓力信號,而不必實際產生記憶體壓力。假如目標是測試特定使用者空間程序的回應與記憶體壓力信號,而不想改變系統的記憶體狀態,這個做法就很實用。

Signals userspace clients with specified memory pressure level. Clients can use this
command to test their response to memory pressure. Does not affect the real memory
pressure level on the system, or trigger any kernel reclamation tasks.
Positional Arguments:
  level             memory pressure level. Can be CRITICAL, WARNING or NORMAL.

例如,使用 ffx profile memory signal WARNING 時,ffx log 輸出內容會顯示以下內容:

[00213.059579][26701][26703][memory_monitor] INFO: [pressure_notifier.cc:106] Simulating memory pressure level WARNING

請注意,這個指令實際上不會分配任何記憶體。它只會模擬使用者空間中要求層級的一次性記憶體壓力信號,而不影響核心的記憶體可用性狀態。因此,這不會觸發任何核心記憶體收回作業,例如剔除分頁器支援的記憶體。