RFC-0010:zx_channel_iovec_t 支援 zx_channel_write 和 zx_channel_call

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

這份 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 數量。

請注意,只有 bytes 陣列會變更,因此句柄陣列的類型 (zx_handle_tzx_handle_disposition_t) 並不重要。

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。這個數字來自 8 個位元組對齊的內嵌 + 離線物件數量,這些物件可放入 65536 位元組訊息中,每個內嵌 + 離線物件都可能使用 zx_channel_iovec_t 項目。

實作

Syscall

  • 引入設計部分中定義的 zx_channel_iovec_t 類型。
  • 新增「ZX_CHANNEL_WRITE_USE_IOVEC
  • 不會變更可見的系統呼叫介面,zx_channel_iovec_t 陣列會傳遞至現有的 bytes 參數。

核心

收到 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 項目。在核心和 FIDL 編碼中,使用 64 個項目的陣列來儲存 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 呼叫中支援多個訊息。

我們考慮了三項提案:

  • 平面表示法:使用兩個 uint16_t 欄位,將 zx_channel_iovec_t 上的 reserved 欄位重新用於其他用途:message_seq (zx_channel_iovec_t 所屬的訊息) 和 handle_count (buffer 中位元組消耗的句柄數)。序號受限於單調性,且沒有間隔。這項限制可實現效能更高的核心,但日後可視需要降低。這種做法與這項 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 繫結變更為這種方式運作,而且延後新增對 read-path iovec 的支援,似乎也不會造成太多成本,除非有需要。

既有技術與參考資料

Fuchsia 有現有的 zx_stream_readvzx_stream_writev 系統呼叫,可使用向量化 I/O。Linux 也提供類似的 readvwritev 系統呼叫,分別讀取及寫入檔案描述項。