RFC-0010:zx_channel_iovec_t 支援 zx_channel_write 和 zx_channel_call | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 這個 RFC 針對 zx_channel_write 和 zx_channel_call 引進了新模式,可從多個記憶體區域複製輸入資料,而非從單一連續的緩衝區複製輸入資料。 |
問題 | |
變更 | |
作者 | |
審查人員 | |
提交日期 (年/月) | 2020-09-25 |
審查日期 (年/月) | 2020-10-06 |
摘要
這個 RFC 引進 zx_channel_write
、zx_channel_write_etc
、zx_channel_call
和 zx_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_etc
、zx_channel_call
和 zx_channel_call_etc
中,會有類似的陣列。這些陣列必須連續才能產生負擔。特別是對於含有外線元件的 FIDL 訊息,FIDL 編碼器必須分配緩衝區,並將資料重新移至該緩衝區,因為費用可能會高昂。
zx_channel_iovec_t
提供替代路徑。zx_channel_write
、zx_channel_write_etc
、zx_channel_call
和 zx_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
」必須指派為 0。只有在 capacity
為 0 時,buffer
欄位才能為空值。buffer
指標可能會在多個 zx_channel_iovec_t
中重複。
zx_channel_write
、zx_channel_write_etc
、zx_channel_call
或 zx_channel_call_etc
的簽名則維持不變。不過,當使用者指定這些系統呼叫的 ZX_CHANNEL_WRITE_USE_IOVEC
選項時,void* bytes
引數會解讀為 zx_channel_iovec_t*
。同樣地,num_bytes
引數將解譯為陣列中的 zx_channel_iovec_t
數量。
請注意,處理陣列的類型 (zx_handle_t
或 zx_handle_disposition_t
) 並無關聯,因為只有 bytes
陣列發生變更。
zx_channel_iovec_t
陣列描述的訊息可與訊息的所有部分一併傳送,否則系統完全不會傳送訊息。成功和失敗時,呼叫端都無法再使用提供給 syscall 的處理方法。
錯誤狀況
這些是 zx_channel_write
、zx_channel_write_etc
、zx_channel_call
和 zx_channel_call_etc
的錯誤狀況,由於 iovecs 採用後而更新。
ZX_ERR_OUT_OF_RANGE num_bytes
或 num_handles
大於 ZX_CHANNEL_MAX_MSG_BYTES
或 ZX_CHANNEL_MAX_MSG_HANDLES
。如果指定 ZX_CHANNEL_WRITE_USE_IOVEC
選項,當 num_bytes
大於 ZX_CHANNEL_MAX_MSG_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
項目。
實作
席斯來電
- 按照設計章節中定義的
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
支援,方法是在 zx_channel_iovec_t
陣列中新增對 FIDL 物件編碼的支援功能。
這個編碼路徑和現有的編碼路徑的主要差異在於,zx_channel_iovec_t
允許核心自行複製物件。這項功能的主要小工具為指標。FIDL 編碼的訊息必須傳送至核心,並將指標替換為 PRESENT
或 ABSENT
標記值。然而,在許多情況下,物件在系統呼叫後仍需使用原始指標值,這樣才能呼叫解構函式。
也就是說,使用 zx_channel_iovec_t
的繫結有時需要執行額外的簿記工作,確保能正確清除物件。
遷移
由於這項功能是以預設為停用的選項形式實作,因此不會對現有使用者造成立即影響。網站可以視需要遷移以使用該選項。
基本上,我們的目的是遷移 zx_channel_iovec_t
享有的 FIDL 繫結,以便使用。這應幾乎不會對 FIDL 使用者造成任何影響。
效能
已導入原型並進行基準測試。
這個原型已在核心端導入 zx_channel_write 選項,並有限的 FIDL 支援 (僅限內嵌物件和向量)。訊息標頭以及每個內嵌和外行物件都有一個 zx_channel_iovec_t
項目。64 個項目的陣列用於將 zx_channel_iovec_t
同時儲存在核心和 FIDL 編碼中。
這些測量結果來自搭載 Intel Core i5-7300U CPU (2.60GHz) 的機器。
位元組向量事件基準 (zx_channel_write、zx_channel_wait_async 和 zx_channel_read) 可大幅提升:
- 4096 位元組向量:9398 ns -> 4495 ns
- 256 位元組向量:8788 ns -> 3794 ns
FIDL 編碼也展現了效能的提升。
位元組向量範例的編碼時間:
- 4096 位元組向量:345 ns -> 88 ns
- 256 位元組向量:251 ns -> 86 ns
內嵌物件也會顯示小型的編碼改善:
- 具有 256 uint8 欄位的結構:67 ns -> 49 ns
安全性考量
由於這是現有系統呼叫的重大變更,因此必須在實作之前進行安全性審查。
隱私權注意事項
這項措施不會對隱私權造成任何影響。
測試
每個變更層都會新增單元和整合測試。
您不應新增任何裝置或整個系統的端對端測試,但現有的測試涵蓋範圍可協助確保在遷移作業完成後不會產生非預期的錯誤。
說明文件
系統呼叫說明文件需要更新,以指出這項功能支援這項功能。
不需要變更架構說明文件。
缺點、替代方案和未知
本提案的主要缺點是增加複雜度,包括需要支援核心中的選項,以及使用 ZX_CHANNEL_WRITE_USE_IOVEC
選項的 FIDL 繫結會實際增加的複雜度,確保物件因就地複製而經過變動後,能夠妥善清理物件。
限制
其中一個引數是 zx_channel_iovec_t
數量的下限,可能比 8192
更接近 16
。這麼做可將 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
重新利用reserved
欄位,使用兩個uint16_t
欄位:message_seq
(zx_channel_iovec_t
所屬的訊息) 和handle_count
(buffer
中位元組使用的處理次數)。序號限制為單調,且無缺漏。這項限制可啟用更有效率的核心實作,但日後可以視需要調降。這個方法符合這項 RFC,並可在現有結構中加入多訊息支援。 - 陣列陣列表示:外部訊息陣列包含訊息陣列,每個訊息都會有指標,做為每則訊息的 iovecs 內部陣列。這與 Linux 系統呼叫
sendmmsg
中使用的結構類似,使用者可能較熟悉。雖然系統並未評估陣列陣列呈現的效能,但已有證據顯示由於間接的關係,可能會產生 5-25% 的負擔 (請參閱 CL)。 - 標頭前置字元表示:緩衝區的開頭是標頭,後面接著 iovec 陣列。標頭包含 16 個訊息描述元,每個描述元都包含
uint16_t
message_size
欄位。這個欄位決定與訊息相關聯的zx_channel_iovec_t
項目數量。這個表示法提供階層結構,但無需額外的重新導向和複製。
在設計討論中,平面表示法因為效能屬性和簡易性而獲得青睞。雖然多訊息支援的完整提案不在這個 RFC 的涵蓋範圍內,但請注意,這個 RFC 與扁平表示法相容。
iovec 專屬 syscall
您可以建立 zx_channel_write_iovec
、zx_channel_write_etc_iovec
、zx_channel_call_iovec
和 zx_channel_call_etc_iovec
系統呼叫,而不是在現有的系統呼叫中新增選項。不過,建議採用這個選項,避免使用者系統呼叫次數和認知上的爆炸性增加。
zx_channel_read 中的 zx_channel_iovec_t 支援
這個 RFC 提議支援管道寫入的 zx_channel_iovec_t
,但不支援頻道讀取。這是因為寫入方面有明顯的動機 - 避免進行 FIDL 線性化步驟 - 但是讀取面無法明確且立即獲得好處。
Rust 繫結可能會藉由將緩衝區分割成多個具有專屬擁有權的較小緩衝區,進而受益於讀取端 iovec 支援。這有助於建立與 LLCPP 類似的繫結變化版本,基本上會將緩衝區轉換為輸出物件。不過,目前沒有將 Rust 繫結變更以這種方式運作的短期計畫,而且在有需要前,延後新增讀取路徑 iovec 的成本似乎相當多。
先前的圖片和參考資料
Fuchsia 目前有使用向量化 io 的 zx_stream_readv
和 zx_stream_writev
系統呼叫。Linux 也提供類似的 readv
和 writev
系統呼叫,分別讀取及寫入檔案描述元。