RFC-0009:邊緣觸發 async_wait

RFC-0009:Edge 觸發 async_wait
狀態已接受
區域
  • 核心
說明

建議做法是,如果指定 ZX_WAIT_ASYNC_EDGE 標記,系統會略過初始檢查,並將訊號集新增至 DispatchObject 的興趣清單,不論初始訊號狀態為何。在這種運作模式下,其中一個信號必須從非活動狀態轉為活動狀態,才能將封包排入提供的連接埠 (可能需要信號在程序中變成非活動狀態)。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2020-10-24
審查日期 (年-月-日)2020-11-06

摘要

等待信號在物件上斷言通常是依層級觸發,並在 zx_object_wait_async 開始時進行檢查,以防信號已處於活動狀態,在這種情況下,系統會立即將封包傳送至埠。這個 RFC 會在 zx_object_wait_async 中加入選項,ZX_WAIT_ASYNC_EDGE 不會執行初始檢查,因此只有在呼叫後,信號從非活動狀態轉為活動狀態時才會產生封包。

zx_object_wait_async 可能會在物件信號已啟用時,透過 ZX_WAIT_ASYNC_EDGE 呼叫。在這種情況下,只有在物件上的訊號變為非活動狀態,且隨後進行斷言後,封包才會排入埠。事實上,這正是 ZX_WAIT_ASYNC_EDGE 的常用用途。

提振精神

Linux 中的 epoll 輪詢機制可在兩種模式下運作:依層級觸發和依邊緣觸發。Fuchsia 的等待功能,特別是 zx_object_wait_asynczx_port_wait,已經可讓層級觸發的輪詢運作。不過,邊緣觸發輪詢需要在已啟用的物件上等待信號 (透過 I/O) 變為停用,然後再重新啟用,並在後續信號轉換時在埠上排隊等待封包。這是 ZX_WAIT_ASYNC_EDGE 的意圖。

設計

幸好,實作 zx_object_wait_asyncZX_WAIT_ASYNC_EDGE 旗標相當簡單。

目前,如果其中一個信號集已處於啟用狀態,系統會直接呼叫觀測器的 OnMatch 方法,而不會採取任何進一步的動作。否則,如果沒有任何信號集處於啟用狀態,系統會透過提供的 SignalObserver,將該集合新增至 DispatchObject 的興趣清單。

建議做法是,如果指定 ZX_WAIT_ASYNC_EDGE 標記,系統會略過初始檢查,並將信號集新增至 DispatchObject 的興趣清單,不論初始信號狀態為何。在這種作業模式下,其中一個信號必須從非活動狀態轉為活動狀態,才能將封包排入所提供的通訊埠 (可能需要信號在程序中變為非活動狀態)。

在 epoll 中使用 ZX_WAIT_ASYNC_EDGE,並搭配 EPOLLET 邊緣觸發

這項變更的主要用途,是透過 epoll 中的 EPOLLET 標記啟用邊緣觸發式輪詢。在 Zircon 中等待與在 epoll 中輪詢不同,因為檔案描述元資料會使用 epoll_ctl/EPOLL_CTL_ADD 新增至 epoll 檔案描述元資料,並持續監控,直到使用 epoll_ctl/EPOLL_CTL_DEL 移除為止。Zircon 等待 (尤其是使用 zx_object_wait_async 時) 一律是一次性的,且檔案物件必須在信號啟用後再次呼叫 zx_object_wait_async,才能「重新啟動」。

由於 epoll 使用方式必須透過重複呼叫 epoll_wait 來運作 (不一定會呼叫 epoll_ctl),因此對 zx_object_wait_async 的重新啟用呼叫必須發生在 epoll_wait 的某處。

針對預設的層級觸發式輪詢,在 epoll_wait 中,一旦 zx_port_wait 傳回信號檔案物件,我們就無法在傳回前呼叫 zx_object_wait_async,因為該物件上的信號是活動的,且會在埠上產生重複封包。因此,系統會維護一份有效的層級觸發檔案描述元資料清單,並在進入 epoll_wait 前,針對清單中的檔案描述元呼叫 zx_object_wait_async,然後在 zx_port_wait 中等待。

針對邊緣觸發的輪詢,在 epoll_wait 傳回後,應執行非阻斷式 I/O,直到 EWOULDBLOCK 傳回為止。此時,檔案物件上的信號將處於停用狀態。此時應呼叫 epoll_wait。不過,如果在 I/O 作業傳回 EWOULDBLOCK 和呼叫 epoll_wait 之間,檔案物件上的信號變得有效,則除非已呼叫 zx_object_wait_async,否則該事件將會遺失。因此,在邊緣觸發模式中,必須在 epoll_wait 返回之前,呼叫 zx_object_wait_async 以重新啟用檔案物件。這時就需要 ZX_WAIT_ASYNC_EDGE。在 zx_port_wait 傳回和 epoll_wait 傳回之間,可以使用此標記呼叫 zx_object_wait_async,因為雖然此時信號處於活動狀態,但 ZX_WAIT_ASYNC_EDGE 會略過檢查信號是否處於活動狀態 (此時信號處於活動狀態),因此不會立即在埠上排入封包。這表示在 I/O 發生到 EWOULDBLOCK 之間,檔案物件已由 zx_object_wait_async 監控,且涵蓋範圍沒有空隙。

實作

fxr/438521 已實作將 ZX_WAIT_ASYNC_EDGE 選項新增至 zx_object_wait_async,而 fxr/438656 也已實作在 epoll 中使用 ZX_WAIT_ASYNC_EDGE 的功能。

成效

由於只在現有程式碼中新增額外的方法參數和該參數的檢查,因此效能影響不大。

安全性考量

隱私權注意事項

測試

新增其他單元測試。

說明文件

已在實作 CL 中將文件新增至 zx_object_wait_asynczx_object_wait_async

缺點、替代方案和未知事項

這似乎是實作這項功能最簡單的方式,類似於在其他作業系統中實作邊緣觸發輪詢的方式。

使用這個旗標時,請務必小心,不要錯過 I/O 事件。信號可能會在執行 I/O 後,且在呼叫 zx_object_wait_async 之前就已啟用,在這種情況下,系統可能會錯過從未收到信號到收到信號的轉換。實際上,ZX_WAIT_ASYNC_EDGE 標記會在 zx_port_wait 傳回後立即使用,表示信號在物件上有效。以這種方式,在物件上執行非阻斷式 I/O 直到信號無效 (通常是直到 ZX_ERR_SHOULD_WAIT 傳回) 後,即可呼叫 zx_port_wait 等待其他 I/O 就緒。

由於邊緣觸發 epoll_waitEPOLLET 的模式為 (1) epoll_wait (2) 非阻斷 I/O,直到 fd 不就緒 (3) 再次 epoll_wait,因此 ZX_WAIT_ASYNC_EDGE 的替代方案是在每個 I/O 作業中執行檢查,以查看檔案描述符是否已透過 epoll_ctlEPOLLET 新增至 epoll 檔案描述符 (且已停止就緒),並重新啟動等待狀態。這會需要大幅修改 zxiofdio,用於較少見的用途。

既有技術與參考資料

這項變更的目的是在 Linux 中模擬 EPOLLET 標記:

https://man7.org/linux/man-pages/man7/epoll.7.html