RFC-0010:zx_channel_iovec_t 支援 zx_channel_write 和 zx_channel_call

RFC-0010:支援 zx_channel_write 和 zx_channel_call 的 zx_channel_iovec_t
狀態已接受
區域
  • Kernel
說明

這項 RFC 為 zx_channel_write 和 zx_channel_call 導入新模式,可從多個記憶體區域複製輸入資料,而非單一連續緩衝區。

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

摘要

這項 RFC 為 zx_channel_writezx_channel_write_etczx_channel_callzx_channel_call_etc 導入新模式,可從多個記憶體區域複製輸入資料,而非單一連續緩衝區。這樣一來,訊息資料就能直接從多個使用者空間物件複製,不需經過中間的分配、複製和版面配置步驟,進而提升特定使用者 / 用戶端的效能。方法是更新現有的系統呼叫,在指定選項時採用 zx_channel_iovec_t 記憶體區域描述元陣列。

提振精神

這項提案的主要動機是提升成效。

對於非線性化的網域物件,FIDL 繫結目前需要 (1) 分配緩衝區,以及 (2) 以標準版面配置將物件複製到緩衝區。完成這些步驟後,緩衝區會再次複製到核心。zx_channel_iovec_t 可讓物件直接複製到核心。此外,FIDL 訊息資料不再需要以標準順序排列,只有 zx_channel_iovec_t 陣列必須反映所需順序。

設計

zx_channel_write 目前的簽章如下:

zx_status_t zx_channel_write(zx_handle_t handle,
                             uint32_t options,
                             const void* bytes,
                             uint32_t num_bytes,
                             const zx_handle_t* handles,
                             uint32_t num_handles);

輸入資料是由 bytes 指向的連續位元組陣列。 在 zx_channel_write_etczx_channel_callzx_channel_call_etc 中,有類似的陣列。這些陣列必須是連續的,因此會產生額外負荷。具體來說,對於含有行外元件的 FIDL 訊息,FIDL 編碼器必須分配緩衝區並將資料重新放置其中,這可能相當耗費資源。

zx_channel_iovec_t 提供替代路徑。zx_channel_writezx_channel_write_etczx_channel_callzx_channel_call_etc 會改為接收物件的位置和大小清單, 並在核心內進行複製,避免額外重複和分配。

zx_channel_iovec_t 在 C++ 中定義如下:

typedef struct zx_channel_iovec {
  void* buffer;            // User-space bytes.
  uint32_t capacity;       // Number of bytes.
  uint32_t reserved;       // Reserved.
} zx_channel_iovec_t;

每個 zx_channel_iovec_t 都指向要從 buffer 複製到核心訊息緩衝區的下一個 capacity 位元組。reserved 必須指派為零。 只有在 capacity 為 0 時,buffer 欄位才能為 NULL。buffer 指標可能在多個 zx_channel_iovec_t 中重複。

zx_channel_writezx_channel_write_etczx_channel_callzx_channel_call_etc 的簽章維持不變。不過,如果使用者為這些系統呼叫指定 ZX_CHANNEL_WRITE_USE_IOVEC 選項,void* bytes 引數就會解讀為 zx_channel_iovec_t*。同樣地,num_bytes 引數會解讀為陣列中的 zx_channel_iovec_t 數量。

請注意,控制代碼陣列 (zx_handle_tzx_handle_disposition_t) 的型別無關緊要,因為只有 bytes 陣列會變更。

zx_channel_iovec_t 陣列所描述的訊息會連同所有部分一併傳送,否則訊息不會傳送。無論成功或失敗,呼叫端都無法再使用提供給系統呼叫的控制代碼。

錯誤狀況

這些是 zx_channel_writezx_channel_write_etczx_channel_callzx_channel_call_etc 的錯誤情況,由於導入 iovec,因此需要更新。

ZX_ERR_OUT_OF_RANGE num_bytesnum_handles 分別大於 ZX_CHANNEL_MAX_MSG_BYTESZX_CHANNEL_MAX_MSG_HANDLES。如果指定 ZX_CHANNEL_WRITE_USE_IOVEC 選項,當 num_bytes 大於 ZX_CHANNEL_MAX_MSG_IOVEC,或 iovec 容量總和超過 ZX_CHANNEL_MAX_MSG_BYTES 時,就會產生 ZX_ERR_OUT_OF_RANGE

ZX_ERR_INVALID_ARGS bytes 是無效指標、handles 是無效指標,或 options 包含無效選項位元。如果指定了 ZX_CHANNEL_WRITE_USE_IOVEC 選項,且 buffer 欄位含有無效指標,則為 ZX_ERR_INVALID_ARGS

對齊

zx_channel_iovec_t 中指定的位元組沒有對齊限制。每個 zx_channel_iovec_t 都會複製,且不含邊框間距。

限制

每則訊息的位元組數 (65536) 和控制代碼數 (64) 現行限制維持不變。請注意,這些限制適用於訊息,而非 zx_channel_iovec_t 項目。

每個系統呼叫的 zx_channel_iovec_t 數量上限為 8192。這個數字來自於可放入 65536 位元組訊息的 8 位元組對齊內嵌 + 離線物件數量,每個內嵌 + 離線物件可能會使用 zx_channel_iovec_t 項目。

實作

Syscall

  • 介紹設計部分定義的 zx_channel_iovec_t 型別。
  • 新增「ZX_CHANNEL_WRITE_USE_IOVEC
  • 可見的系統呼叫介面沒有任何變更,zx_channel_iovec_t 陣列會傳遞至現有的 bytes 參數。

Kernel

收到 ZX_CHANNEL_WRITE_USE_IOVEC 選項後,核心會執行下列動作:

  • zx_channel_iovec_t 物件指向的資料複製到訊息緩衝區。雖然複製作業通常也會按照 zx_channel_iovec_t 輸入的順序執行,但這並非必要。不過,最終訊息必須按照 zx_channel_iovec_t 項目順序排列。
  • 撰寫傳送給管道訊息。

FIDL

這是系統呼叫變更提案,實作作業會在核心中進行,而 FIDL 繫結變更的具體細節不在範圍內。不過,為了評估這項提案,請務必瞭解 FIDL 編碼的影響。

FIDL 繫結可選擇利用 zx_channel_iovec_t 支援功能,方法是新增將 FIDL 物件編碼為 zx_channel_iovec_t 陣列的支援功能。

這個編碼路徑與現有編碼路徑的主要差異在於,zx_channel_iovec_t 可讓核心就地複製物件。這項做法的主要問題在於指標。FIDL 編碼的訊息必須傳送至核心,且指標須替換為 PRESENTABSENT 標記值。不過,在許多情況下,系統呼叫後,物件仍需保留原始指標值,才能呼叫解構函式。

也就是說,利用 zx_channel_iovec_t 的繫結有時需要進行額外的記帳作業,確保物件正確清除。

遷移

這項功能預設為停用,因此現有使用者不會立即受到影響。視需要,呼叫網站可以遷移至使用這個選項。

實務上,目的是要遷移可從 zx_channel_iovec_t 受益的 FIDL 繫結,並使用該繫結。預期對 FIDL 使用者造成的影響不大。

效能

原型已實作並完成基準化。

這個原型在核心端實作了 zx_channel_write 選項, 並限制 FIDL 支援 (僅限內嵌物件和向量)。 郵件標頭,以及每個內嵌和非內嵌物件,各有 zx_channel_iovec_t 項目。我們使用 64 個項目的陣列,在核心和 FIDL 編碼中儲存 zx_channel_iovec_t

這些測量結果來自搭載 Intel Core i5-7300U CPU @ 2.60GHz 的電腦。

位元組向量事件基準 (zx_channel_write、zx_channel_wait_async 和 zx_channel_read) 顯著改善:

  • 4096 位元組向量:9398 奈秒 -> 4495 奈秒
  • 256 位元組向量:8788 奈秒 -> 3794 奈秒

FIDL 編碼也顯示效能有所提升。

編碼位元組向量範例的時間:

  • 4096 位元組向量:345 奈秒 -> 88 奈秒
  • 256 位元組向量:251 奈秒 -> 86 奈秒

內嵌物件的編碼作業也有小幅改善:

  • 具有 256 個 uint8 欄位的結構:67 奈秒 -> 49 奈秒

安全性考量

由於這是對現有系統呼叫的重大變更,因此實作前需要進行安全性審查。

隱私權注意事項

這項異動不會影響隱私權。

測試

系統會為每個變更的層級新增單元和整合測試。

不打算新增任何裝置或系統層級的端對端測試,但現有的測試涵蓋範圍有助於確保遷移後不會出現非預期的錯誤。

說明文件

系統呼叫說明文件必須更新,以指出支援這項功能。

不需要變更任何架構相關說明文件。

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

這項提案的主要缺點是需要支援核心中的選項,以及使用 ZX_CHANNEL_WRITE_USE_IOVEC 選項的 FIDL 繫結,因此會增加複雜度,需要確保物件在就地複製後能正確清除。

限制

有論點認為 zx_channel_iovec_t 的數量下限應較低,可能更接近 16 而非 8192。這樣一來,zx_channel_iovec_t 陣列就能複製到核心的堆疊。不過,這樣做會導致實作策略無法為每個超出範圍的 FIDL 物件指派一個項目。zx_channel_iovec_t

在實務上,如果 zx_channel_iovec_t 數量龐大,或至少避免將工作轉移至核心,在使用者空間中線性化可能會更有效率。不過,為求簡單,建議您先使用 8192 限制,之後再視需要進一步調整。

上限提高後,實作層面的影響是 zx_channel_iovec_t 陣列無法完全放在核心堆疊上。一般情況下可以使用堆疊緩衝區,但如果項目數量夠多,就必須複製到較大 (且較慢) 的緩衝區。

向量化帳號代碼

可以為控制代碼提供 zx_channel_iovec_t 的對等項目,或將控制代碼與現有 zx_channel_iovec_t 中的位元組一併納入。不過,由於控制代碼陣列通常很小,因此控制代碼的優點較為有限。為求簡單,控點會保留在專屬陣列中。

支援在單一寫入作業中傳送多則訊息

先前版本的 RFC 包含在單一 zx_channel_write 呼叫中支援多個訊息的提案。

我們考慮了三項提案:

  • 平面表示法:將 zx_channel_iovec_t 上的 zx_channel_iovec_t 欄位重新用於 zx_channel_iovec_t,並使用兩個 uint16_t 欄位:message_seq (訊息所屬的 zx_channel_iovec_t) 和 handle_count (buffer 中位元組使用的控制代碼數量)。序號必須單調遞增,且不得有間隙。reserved這項限制可提升核心實作的效能,但日後如有需要,可以放寬這項限制。這種做法符合 RFC 規範,且可將多訊息支援功能新增至現有結構。
  • 陣列的陣列表示法:訊息的外層陣列,每個訊息都有指標,指向每個訊息的 iovec 內層陣列。這與 Linux 系統呼叫 sendmmsg 中使用的結構類似,使用者可能較為熟悉。雖然我們並未測量陣列陣列表示法的效能,但有證據顯示,間接性可能會造成 5% 到 25% 的負擔 (請參閱 CL)。
  • 以標頭為前置字元的表示法:緩衝區開頭為標頭,後接 iovec 陣列。標頭包含 16 個訊息描述元,每個描述元只包含 uint16_t message_size 欄位。這個欄位會決定與訊息相關聯的 zx_channel_iovec_t 項目數量。這種表示法提供階層式結構,但不需要額外的重新導向和複製作業。

在設計討論中,由於平面呈現方式的效能屬性和簡潔性,因此獲得青睞。雖然多訊息支援的完整提案超出本 RFC 的範圍,但請注意,本 RFC 與平面表示法相容。

專用於 iovec 的系統呼叫

與其在現有系統呼叫中新增選項,不如建立 zx_channel_write_ioveczx_channel_write_etc_ioveczx_channel_call_ioveczx_channel_call_etc_iovec 系統呼叫。不過,建議您選擇一個選項,以免系統呼叫次數暴增,增加使用者的認知負荷。

zx_channel_read 中的 zx_channel_iovec_t 支援

這項 RFC 建議支援管道寫入的 zx_channel_iovec_t,但不支援管道讀取。這是因為寫入端使用 iovec 的動機很明確 (避免 FIDL 線性化步驟),但讀取端沒有明確的立即好處。

Rust 繫結可能會受益於讀取端 iovec 支援,方法是將緩衝區分割成多個較小的緩衝區,每個緩衝區都有自己的擁有權。這會簡化類似 LLCPP 的繫結變體,基本上是將緩衝區轉換為輸出物件。不過,目前沒有短期計畫要變更 Rust 繫結,以支援這種做法,而且延後新增對讀取路徑 iovec 的支援,似乎不會造成太多成本。

既有技術和參考資料

Fuchsia 具有現有的 zx_stream_readvzx_stream_writev 系統呼叫,可使用向量化 IO。Linux 也提供類似的 readvwritev 系統呼叫,分別用於讀取和寫入檔案描述元。