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_async
和 zx_port_wait
,已經可讓層級觸發的輪詢運作。不過,邊緣觸發輪詢需要在已啟用的物件上等待信號 (透過 I/O) 變為停用,然後再重新啟用,並在後續信號轉換時在埠上排隊等待封包。這是 ZX_WAIT_ASYNC_EDGE
的意圖。
設計
幸好,實作 zx_object_wait_async
的 ZX_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_async
。zx_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_wait
與 EPOLLET
的模式為 (1) epoll_wait
(2) 非阻斷 I/O,直到 fd 不就緒 (3) 再次 epoll_wait
,因此 ZX_WAIT_ASYNC_EDGE
的替代方案是在每個 I/O 作業中執行檢查,以查看檔案描述符是否已透過 epoll_ctl
與 EPOLLET
新增至 epoll 檔案描述符 (且已停止就緒),並重新啟動等待狀態。這會需要大幅修改 zxio
和 fdio
,用於較少見的用途。
既有技術與參考資料
這項變更的目的是在 Linux 中模擬 EPOLLET 標記:
https://man7.org/linux/man-pages/man7/epoll.7.html