| RFC-0009:邊緣觸發的 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,否則該事件會遺失。因此,在邊緣觸發模式中,呼叫 zx_object_wait_async 重新啟動檔案物件時,必須在 epoll_wait 傳回之前呼叫。這時就必須使用 ZX_WAIT_ASYNC_EDGE。在 zx_port_wait 傳回和 epoll_wait 傳回之間,可以透過這個旗標呼叫 zx_object_wait_async,因為雖然訊號在這個時間點處於啟用狀態,但 ZX_WAIT_ASYNC_EDGE 會略過訊號是否處於啟用狀態的檢查 (此時訊號處於啟用狀態),因此不會立即在連接埠上將封包加入佇列。這表示當 I/O 發生到 EWOULDBLOCK 時,檔案物件已由 zx_object_wait_async 監控,涵蓋範圍不會有間隙。
實作
ZX_WAIT_ASYNC_EDGE 選項已在 zx_object_wait_async 中實作,詳情請參閱 fxr/438521,而 epoll 中的使用方式則已在 fxr/438656 中實作。
效能
對效能的影響微乎其微,因為只有額外新增的方法參數和該參數的檢查會加入現有程式碼。
安全性考量
不適用
隱私權注意事項
不適用
測試
已新增其他單元測試。
說明文件
實作 CL 中已在 zx_object_wait_async 新增說明文件。
zx_object_wait_async
缺點、替代方案和未知事項
這似乎是實作這項功能最簡單的方法,類似於其他作業系統中邊緣觸發輪詢的實作方式。
使用這個旗標時,請務必注意不要錯過 I/O 事件。執行 I/O 後,且在呼叫 zx_object_wait_async 前,信號可能會變成有效狀態,在這種情況下,系統可能會錯過從未發出信號到發出信號的轉換。實務上,ZX_WAIT_ASYNC_EDGE 旗標會立即使用 zx_port_wait 傳回的內容,指出信號在物件上處於有效狀態。這樣一來,在訊號處於非使用中狀態前 (通常是直到傳回 ZX_ERR_SHOULD_WAIT 為止),系統會在物件上執行非封鎖 I/O,之後即可呼叫 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