| RFC-0152:改善 OOM 處理行為 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 改善記憶體不足的處理方式,允許更多使用者空間程式碼結束,並為使用者空間建立信號路徑,在使用者空間清理完成時通知核心。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-12-20 |
| 審查日期 (年-月-日) | 2022-02-09 |
摘要
這項 RFC 的目標是提高擷取偵錯資料的可靠性,並在記憶體不足 (「OOM」) 事件期間,盡量減少產生無用的資料。這是透過有條不紊地關閉更多使用者空間來達成,而非目前使用的基本機制。
提振精神
在系統記憶體不足時收集資料,有助於找出事件的根本原因。目前我們會收集部分資料 (例如記憶體報告),但無法保證記錄檔能協助我們瞭解系統發生的情況。目前只有少數系統元件知道系統何時會耗盡記憶體,因此收集所有相關資料並不容易。此外,OOM 發生後,分析可用資料可能很困難。處理 OOM 的方式通常會導致一連串的次要影響,進而為資料增加雜訊。這類情況一再發生,導致大家感到困惑、討論時間過長,以及得出錯誤結論,浪費許多時間。
利害關係人
導師:hjfreyer@google.com
審查人員:dgilhooley@google.com、frousseau@google.com、maniscalco@google.com、 palmer@google.com、pankhurst@google.com、ppi@google.com、pshickel@google.com、 rashaeqbal@google.com、shayba@google.com、surajmalhotra@google.com
諮詢對象:adanis@google.com、alexlegg@google.com、geb@google.com、 johngro@google.com、wez@google.com
社交化:這個概念最初是在各種相關錯誤的討論過程中提出。之後,我們向重要利害關係人諮詢了建議的解決方案。
背景
在 Fuchsia 上處理記憶體不足 (OOM) 狀況的程序如下:
- 如果可用記憶體持續減少,記憶體監控程式就會偵測到系統的可用記憶體達到
ZX_SYSTEM_EVENT_OUT_OF_MEMORY門檻。核心現在會重新啟動系統。 - 記憶體監控程式會為使用者空間產生
ZX_SYSTEM_EVENT_OUT_OF_MEMORY信號,並聲明 halt 權杖。停止權杖的用途是防止核心中的多個項目嘗試同時執行重新啟動。 - 記憶體監控程式會先休眠 8 秒,再重新啟動。使用者空間無法向核心指出已準備好重新啟動。
driver_manager會觀察ZX_SYSTEM_EVENT_OUT_OF_MEMORY信號。driver_manager啟動時,會透過system_get_event系統呼叫訂閱信號,這需要根工作控制代碼。driver_manager會告知fshost關機,然後告知所有驅動程式停止運作。系統會盡量減少資料遺失,並在重新啟動前讓硬體進入一致狀態。- 在
driver_manager和fshost之外,使用者空間會持續執行,直到記憶體監控程式的計時器到期為止。在此期間,程式會嘗試存取已分頁且無法分頁的執行檔頁面,因此通常會發生各種當機情形。這些當機會產生大量雜訊,如果有人監聽序列記錄,就可能會錄到這些雜訊。
目前的 OOM 處理程序不會使用這項功能,但已有方法可正常終止使用者空間。這個機制會依反向依附元件順序停止元件 (包括檔案系統和驅動程式),並讓所有元件有機會清理。
memory_monitor 不會參與 OOM 處理作業,但對記憶體不足事件感興趣。RFC-0091 建立的 ZX_SYSTEM_EVENT_IMMINENT_OUT_OF_MEMORY 事件是由核心產生,當系統可用記憶體達到略高於 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 門檻的程度時,就會產生這個事件。memory_monitor 會觀察這個信號,並嘗試將記憶體設定檔資料保存到儲存空間。這是盡力而為的做法,因為 memory_monitor 無法發出信號表示已處理事件,且系統隨時可能達到下限,因此可能會在 memory_monitor 清除資料前關閉檔案系統。
設計
設計概念相當簡單,且大量使用現有的系統元件,但以新方式組合而成。從高層次來看,當核心偵測到記憶體不足時,策略會記錄記憶體中的某些狀態、向使用者空間發出 OOM 訊號、核心等待使用者空間回呼核心 (設有逾時時間)、使用者空間接收訊號、多個使用者空間元件在有序關機中扮演已建立的角色,最後使用者空間回呼核心,讓核心將資料儲存在 NVRAM 中,並完成重新啟動。
詳細順序如下:
- 核心的記憶體監控程式偵測到系統記憶體不足,因此會
- 聲明暫停權杖
- 使用
ZX_SYSTEM_EVENT_OUT_OF_MEMORY將信號傳送至使用者空間 - 設定計時器
pwrbtn-monitor會收到使用者空間信號,並透過fuchsia.hardware.power.statecontrol/Admin.Reboot呼叫與power_manager通訊。- 然後,
power_manager會透過fuchsia.device.manager/SystemStateTransition.SetTerminationSystemState呼叫告知driver_manager,在結束前應將系統移至REBOOT_KERNEL_INITIATED電源狀態。 power_manager會呼叫fuchsia.sys2/SystemController.Shutdown呼叫,指示component_manager拆除元件拓撲。component_manager會以反向依附元件順序拆解元件拓撲,最終告知driver_manager結束。driver_manager會發現應該先執行轉換至REBOOT_KERNEL_INITIATED狀態,再結束。driver_manager會呼叫zx_system_powerctl,並在結束前將ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT傳遞至cmd值。- 核心會收到系統呼叫,並發出停止符記信號。
- 其餘 OOM 處理程序會繼續執行,以便將適當資訊寫入 NVRAM,供重新啟動後讀取。
- 核心會重新啟動系統。
實作
控制核心 OOM 超時
這項 RFC 建議新增核心啟動選項,以控管 OOM 超時。目前 OOM 逾時的硬式編碼為 8 秒。實作這項 RFC 會增加處理 OOM 時執行的程式碼量,因此需要較長的逾時時間。
暫停權杖異動
這項 RFC 建議變更「停止權杖」,納入核心事件物件,而非單純的原子布林值。停止權杖仍會是無法撤銷的已聲明擁有權物件。核心會使用停止權杖的事件物件來協調重新啟動作業。停止權杖 可讓事件物件發出信號,無需取得權杖。
OOM 處理流程
目前,當核心偵測到 OOM 時,會為使用者空間產生 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 信號、取得停止權杖,並啟動 8 秒計時器。這項 RFC 建議,每當核心想重新啟動系統,但同時也想讓使用者空間有機會執行某些動作時,核心應先取得停止權杖,然後通知使用者空間,並等待一段有界的時間。等待界限應等於使用者模式回應事件時允許執行的最長時間。這與目前的實作方式不同,因為目前的逾時值是等待時間的最大值和最小值。當停止權杖的事件收到訊號或達到逾時時間時,核心會完成重新啟動作業。發生 OOM 時,決定重新啟動的 Kernel 程式碼位於記憶體監控程式中,該程式會建立 OOM 當機記錄、將記錄儲存在 NVRAM 中,然後重新啟動,完成 OOM 處理程序。
如先前所述,這項 RFC 提議新增透過核心啟動選項設定 OOM 超時的功能。
目前,使用者空間 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 處理常式位於 driver_manager。這項 RFC 建議將處理常式移至 pwrbtn-monitor。
pwrbtn-monitor 是現有的元件,存在於所有建構版本中,用於控制某些硬體的電源狀態。從效果來看,OOM 就像是軟體產生的電源按鈕按下事件。由於 pwrbtn-monitor 的責任增加,我們建議將其重新命名為 system-event-monitor。
pwrbtn-monitor 收到信號時,會呼叫 fuchsia.hardware.power.statecontrol/Admin.Reboot。我們會為 OOM 新增 RebootReason,並將 pwrbtn-monitor 傳遞至此呼叫。這個呼叫會啟動現有使用者模式的正常關機路徑,以反向依附元件順序解構元件拓撲,並以driver_manager變更硬體電源狀態做為結尾。本 RFC 建議在處理 OOM 時,driver_manager 一律應使用 zx_system_powerctl 系統呼叫的重新啟動路徑,並將新值 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT 做為 cmd 引數的值傳遞。driver_manager 中的現有路徑剛好使用 zx_system_powerctl。在 x86 上,當 driver_manager 將重新啟動的完成作業委派給主機板驅動程式庫,而主機板驅動程式庫發出系統呼叫時,就會發生這種情況。在 arm64 上,driver_manager 會直接發出系統呼叫。這項 RFC 的變更正式要求在重新啟動路徑中加入 zx_system_powerctl。
當 Zircon 收到 zx_system_powerctl 且 cmd 值為 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT 時,處理常式程式碼會嘗試發出停止權杖信號。如果未聲明停止權杖,信號傳送就會失敗,且系統呼叫會傳回錯誤。如果是其他 cmd 值,處理常式程式碼會維持不變,具體來說,就是嘗試取得停止權杖,如果無法取得,就會無限期休眠。如果核心因 OOM 而啟動重新啟動程序,發出信號給權杖可讓記憶體監控計時器完成工作並重新啟動系統。如果使用者空間未在記憶體監控程式逾時前呼叫 zx_system_powerctl,監控程式會繼續執行關機程序並重新啟動系統,這與目前的實作方式相同。
效能
我們預期在某些情況下,OOM 處理作業會比目前耗時。目前發生 OOM 時,driver_manager 會停止檔案系統、停止驅動程式,然後就不會再執行任何動作。核心偵測到 OOM 後,會在 8 秒內重新啟動系統。
實作這項 RFC 後,許多使用者空間都有機會對系統即將重新啟動做出反應。具體來說,power_manager 會透過 RebootWatcher 通訊協定通知監聽器即將重新啟動,power_manager
用戶端的回應逾時時間為 5 秒。power_manager 通知重新啟動監控程式後,會告知 component_manager 拆除元件拓撲。元件拓撲會依反向依附元件順序拆除,也就是說,並非所有元件都會同時停止。元件有停止的逾時時間。實作這項 RFC 後,更多程式碼可能會在 OOM 期間執行,並可能達到各種逾時。因此重新啟動時間可能超過 8 秒,但目前大多數系統的重新啟動時間都遠短於 8 秒。
本 RFC 並未嘗試解決核心發出 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 訊號後,有項目分配更多記憶體的問題。
回溯相容性
不必擔心回溯相容性問題,變更可以軟轉換的形式進行。
安全性考量
這項 RFC 提案建議將 zx_system_get_event 系統呼叫的使用方式從 driver_manager 移至 pwrbtn-monitor。這個系統呼叫需要根工作控制碼,這是高度機密的控制碼。pwrbtn-monitor 是小巧的專用元件,可透過 fuchsia.hardware.power.statecontrol/Admin 能力控制系統電源狀態。新增根工作存取權會提高這個元件的權限。
這項 RFC 也提議延長重新啟動逾時時間。只有在我們認為 OOM 是攻擊途徑,且較長的重新啟動逾時時間會讓攻擊者有更多時間執行漏洞利用時,才需要擔心這個問題。
測試
您需要進行測試,確認系統在使用者空間於核心逾時前關閉時,以及未關閉時,都能如預期重新啟動。如果這些測試不存在,系統會新增。
我們可能也希望進行效能測試,瞭解使用者空間需要多久時間才能拆除。這些剖析測試可用於判斷核心逾時值。
說明文件
應更新各種 API 說明文件,但由於這項 RFC 主要是重新連結信號,而非從根本上改變系統行為,因此不需要進行新的概念更新。
缺點、替代方案和未知事項
替代方案:使用者空間處理常式位置
我們可以在 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 中放置使用者空間處理常式,有多種選項。建議處理常式位於 ZBI 中,並出現在所有產品上,以便及早提供一致的處理體驗。主要替代候選項目為 power_manager、shutdown-shim 和 component_manager。選擇 pwrbtn-monitor 的主要原因是,這項責任符合其整體工作,也就是因應事件重新啟動系統,而 OOM 只是軟體產生的事件,而非硬體事件。
替代方案:回報NO_CRASH所有使用者空間啟動的重新啟動
目前 Zircon 會在 OOM 期間將資料寫入永久記憶體,而這份 RFC 建議繼續採用這項做法。或者,我們也可以在每次使用者空間觸發呼叫以重新啟動系統時,將相同資料寫入持續性 RAM,無論核心是否偵測到 OOM 並向使用者空間發出 OOM 訊號。zx_system_powerctl如果我們這麼做,且在 OOM 後順利關閉使用者空間,則 Feedback 元件會看到來自 Zircon 的 NO_CRASH 重開機原因。如果核心計時器到期,且 Zircon 在 OOM 後重新啟動系統,則意見回饋會從 Zircon 看到 OOM 重新啟動原因。
這種做法的缺點是,使用者空間處理 OOM 時發生問題,可能會導致系統知道自己重新啟動,但不知道是 OOM 造成的。在這種情況下,意見回饋會看到 Zircon 的 NO_CRASH 重新啟動原因,但找不到磁碟上保留的當機資訊。在這種情況下,意見回饋仍會提交報告。
替代做法:允許在 OOM 重新啟動期間,將相容要求傳送至 zx_system_powerctl
這項 RFC 建議,一旦核心啟動 OOM 重新啟動,只有兩件事會完成重新啟動:使用者空間呼叫 zx_system_powerctl,且 cmd 值為 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT,或核心的重新啟動計時器到期。或者,我們也可以允許任何相容的呼叫完成重新啟動。zx_system_powerctl相容的呼叫也會重新啟動系統,無論傳遞的 cmd 值為何。這可解決競爭狀況,也就是使用者空間在核心發出 OOM 狀況信號前,獨立決定重新啟動系統。核心可能會根據在 zx_system_powerctl 呼叫中實際收到的 cmd 值,寫入不同的重新啟動原因。這有助於稽核系統是否通常會遵循預期的重新啟動路徑。
替代方案:透過核心物件發出信號,處理使用者空間完成作業
本 RFC 建議透過使用特定 cmd 值呼叫 zx_system_powerctl,完成核心啟動的 OOM 重新啟動。而是可以變更核心到使用者空間的信號機制,讓使用者空間接收管道或事件物件。使用者空間接著可以傳送訊息或判斷/取消判斷訊號,指出是否可以繼續重新啟動。這個替代方案的優點是,完成重新啟動的元件不需要存取根資源。您必須存取根資源才能zx_system_powerctl。這個替代方案需要更多工作,因為它對現今的核心/使用者空間信號傳輸方式進行了更大幅度的變更。
缺點:仍有可能出現某些種族
目前核心可能會偵測到 OOM 狀況、聲明停止權杖,然後使用者空間會呼叫 zx_system_powerctl,因為使用者空間先前決定要重新啟動。在這種情況下,對 zx_system_powerctl 的呼叫會失敗。driver_manager,然後結束。component_manager 會繼續拆除元件拓撲,最終抵達 power_manager 並終止該元件。由於 power_manager 設為根工作的重要項目,因此終止 power_manager 會導致根工作當機。通常在根工作終止時,Zircon 會重新啟動系統,但由於 MemoryWatchdog 持有停止權杖,因此不會重新啟動。系統最終會達到 MemoryWatchdog 的逾時時間,並重新啟動系統。
這項 RFC 允許類似的競賽。當系統達到 OOM 狀態時,使用者空間可能正在拆除,準備重新啟動。pwrbtn-monitor可能已結束,也就是說,使用者空間中沒有任何項目可觀察核心的 OOM 信號。或者,pwrbtn-monitor 可能正在執行,但由於 power_manager 只允許一個進行中的系統關機或重新啟動要求,因此重新啟動系統的嘗試會失敗。先前的終止要求最終會送達 driver_manager。如先前所述,driver_manager 對 zx_system_powerctl 的要求會失敗,且根工作會當機,但系統不會重新啟動,直到 MemoryWatchdog 的逾時到期為止。這場賽事有多糟糕?這相當良性,因為使用者空間會自行清理。在根工作當機時,使用者空間已盡可能清理自身。最大的缺點是重新啟動速度較慢。
如果 reboot-on-terminate 元件在錯誤的時間退出,也可能發生競爭條件。元件可以自行設定,以便在退出時讓 component_manager 重新啟動系統。component_manager 會呼叫 fuchsia.hardware.power.statecontrol/Admin.Reboot 重新啟動系統。如果 component_manager 收到指令要終止元件拓撲,且 reboot-on-terminate 元件在終止後退出,component_manager 就不會嘗試重新啟動系統。如果這類在終止時重新啟動的元件在元件呼叫 Admin.Reboot 後,但在 power_manager 告知 component_manager 拆除拓撲之前結束,系統會當機,因為 component_manager 在呼叫 power_manager 失敗時會發生恐慌。這項 RFC 並未提議修正這個競爭條件。由於我們不知道 component_manager 發生恐慌時系統的執行量,因此競爭可能會導致無法預測的行為。
不明:完全耗盡記憶體的機率影響
目前尚不清楚這項 RFC 對於系統在 OOM 處理期間完全耗盡記憶體風險的影響。因為大多數使用者空間元件不會監聽結束信號,且會在用戶端結束時立即終止,因此提議的變更可降低系統完全耗盡記憶體的機率。這應該會開始快速釋放記憶體。我們預期觀察到結束信號的使用者空間元件應立即結束,同時釋放記憶體。由於某些系統的關機時間可能比現有的 8 秒逾時時間長,因此建議的變更可能會增加記憶體不足的機率,進而讓某些元件有更多時間分配記憶體。這項 RFC 也會延遲關閉檔案系統,目前檔案系統會快速結束。檔案系統快取通常會採用由核心管理的捨棄式記憶體形式。目前尚不清楚長時間執行的檔案系統是否會對記憶體造成顯著負面影響。