| RFC-0085:減少 zx_status_t 空間 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 縮減有效的 zx_status_t 值範圍,方便將類型嵌入其他類型。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2020-10-09 |
| 審查日期 (年-月-日) | 2021-04-08 |
摘要
本文建議將有效 zx_status_t 值範圍從所有 32 位元帶正負號的整數縮小為 [-2^30, 0],並淘汰應用程式定義的錯誤碼。這樣一來,zx_status_t 就能輕鬆嵌入為其他型別的子範圍。
提振精神
zx_status_t 是簡單的錯誤類型,用於傳達特定動作是否成功。定義為帶正負號的 32 位元整數。值 ZX_OK (0) 表示作業成功。所有其他值都表示某種形式的錯誤,系統定義的錯誤代碼 (負值) 和應用程式定義的錯誤代碼 (正值) 都支援。
zx_status_t 型別廣泛用於整個 Fuchsia。舉例來說,Zircon 核心會使用這項機制 (內部使用,以及做為系統呼叫的回傳值);許多 FIDL 通訊協定會使用這項機制來指出錯誤;多個系統程式庫會使用這項機制來回報錯誤;純應用程式碼內部也經常使用這項機制,做為在函式之間回報錯誤的便利方法。
雖然目前的 zx_status_t 定義可傳達各種不同的錯誤情況,但目前只能傳達單一成功值。隨著時間推移,Fuchsia 開發人員發現,在某些情況下,將其他非錯誤資訊傳回呼叫端會很有用:
非錯誤警告:部分函式會發出警告,表示函式大致上成功執行,但可能存在問題。舉例來說,寫入緩衝區的要求成功,但緩衝區小於可用資料量。
物件狀態的額外資訊:舉例來說,核心 IPC 基本型別可能會想在讀取完成後指出有更多資料待處理,或提供更精細的閾值資訊 (例如插座)。
流程控制:核心和系統程式庫的常見模式是回呼傳回錯誤、
ZX_ERR_STOP值 (表示不應再呼叫回呼),或ZX_ERR_NEXT值 (表示應繼續呼叫回呼)。這些特殊值雖然本身並非錯誤,但目前在系統定義的錯誤空間中會指派代碼。傳送小型酬載:函式可能會希望在呼叫成功時,傳送少量資料做為結果的一部分。舉例來說,目前
zx_debuglog_read系統呼叫的實作方式會使用zx_status_t的正值,在成功時傳回讀取到緩衝區的位元組數。
雖然理論上,所有這些用途都可以使用額外的輸出參數或更複雜的複合型別解決,但在某些對效能要求嚴格的用途中,如果函式可以有簡單的整數/暫存器回傳值,就能處理上述用途,效率會更高。
雖然本文使用「函式」和「回傳代碼」等詞彙,但這些概念同樣適用於 FIDL 呼叫、系統呼叫等。同樣地,雖然這個設計提供 C++ 範例,但相同概念也適用於其他語言,包括 C、Rust 和 Go。
設計
在這項提案中,zx_status_t 仍為帶正負號的 32 位元整數,且會繼續定義為具有單一成功代碼 ZX_OK (0)。其他所有值仍應視為錯誤。
不過,我們會更新 zx_status_t 的定義,以便:
有效
zx_status_t值的範圍為[-2^30, 0](即-1073741824至0)。這個範圍內的所有值都會是系統定義的錯誤代碼,或是單一成功代碼ZX_OK。應用程式定義的錯誤碼 (目前定義為所有正數
zx_status_t值) 已淘汰,詳情請參閱下方的「回溯相容性」。
限制 zx_status_t 可採用的值範圍後,使用者就能在其他型別中嵌入 zx_status_t 值範圍,不必擔心錯誤代碼值會與非錯誤傳回值重疊。舉例來說,函式可能會定義 result_or_count_t 型別,其中負值對應至 zx_status_t 錯誤代碼,非負值則對應至處理的元素數量。
我們要求函式實作者定義「新」型別,而不只是在 zx_status_t 空間的未使用部分發出值。這樣一來,函式使用者就能清楚瞭解函式傳回的內容,以及應如何解讀:如果函式具有 zx_status_t 傳回類型,使用者就能確保 ZX_OK 是唯一有效的成功值。
範例
以下各節將示範如何處理上述不同用途,並假設 zx_status_t 的範圍有限。
其他狀態資訊
目前,zx_channel_read 的使用者只能透過對管道執行失敗的讀取作業,判斷是否有訊息等待處理。使用這項提案,zx_channel_read 可以導入新的回傳型別,將「有更多訊息等待處理」狀態做為回傳值的一部分,避免額外的系統呼叫:
/// Keep reading messages until none remain on the channel.
do {
// Read from the channel.
zx_channel_read_result_t result =
zx_channel_read(channel, buffer, /*options=*/ZX_GET_CHANNEL_STATE);
// `zx_channel_read_result_t` defines negative values to correspond
// to `zx_status_t` error codes.
if (result < 0) {
return static_cast<zx_status_t>(result);
}
// Otherwise, the result is defined to be a bitmap indicating
// the state of the channel.
} while ((result & ZX_CHANNEL_MORE_MESSAGES_WAITING) != 0);
流量控制
與其依賴錯誤代碼 ZX_ERR_NEXT 和 ZX_ERR_STOP (並依賴說明文件告知呼叫端需要處理哪些代碼),不如導入新類型,使用非負空間來表示流量控制:
// Negative values are zx_status_t error codes, while non-negative
// values must be one of the constants below.
using zx_iteration_status_t = int32_t;
constexpr int32_t ZX_ITERATION_CONTINUE = 0;
constexpr int32_t ZX_ITERATION_DONE = 1;
// ...
while (true) {
zx_iteration_status_t result = Next(thing);
if (result < 0) {
return result; // error
}
if (result == ZX_ITERATION_DONE) {
break;
}
// ...
}
將酬載混入回應
zx_debuglog_read 已使用非負空間傳回小型酬載 (讀取的位元組數),這目前違反了 zx_status_t 的嚴格定義。這項提案可讓 zx_debuglog_read 定義新類型,明確指出如何解讀回傳值:
// Read the debug log. Returns a negative value on error, otherwise
// the number of bytes read from the debug log.
zx_debuglog_read_result_t result = zx_debuglog_read(buffer);
if (result < 0) {
return result; // error
}
print_log(/*buffer=*/buffer, /*size=*/result);
應用程式定義的錯誤代碼
如果應用程式想定義自己的錯誤代碼,可以繼續這麼做,但應定義類型,清楚說明值的解讀方式:
enum ApplicationError {
INVALID_AUTHORIZATION = 1,
TOO_MANY_OUTSTANDING_REQUESTS = 2,
// ...
}
// Zero indicates success. Negative values map to `zx_status_t` error
// codes. Positive values map to `ApplicationError` error codes.
using app_status_t = int32_t;
由於 zx_status_t 只會佔用 [-2^30, 0] 範圍,因此應用程式可以視需要使用 [-2^31, -2^30) 範圍做為應用程式定義的錯誤代碼,並視需要將正數空間空下來做為其他回傳代碼。
回溯相容性
這項提案會將 zx_status_t 的有效範圍更新為 [-2^30, 0] 值,這些值都是系統定義的。不過,目前有一小部分應用程式會使用正空間,儲存應用程式專屬的程式碼。
根據這項 RFC 的實作內容,我們會將樹狀結構內的正向狀態碼使用者遷移至新的 (非 zx_status_t) 型別。
實作
如要實作這項 RFC,請按照下列步驟操作:
更新說明文件 (包括 Markdown 文件和來源註解),說明
zx_status_t的語意,以符合本規格的提案。更新
zx_debuglog_read系統呼叫,以使用自訂 (非zx_status_t) 型別。請更新目前使用正向
zx_status_t狀態範圍的使用者,改用可更清楚說明產生哪些其他錯誤的新型別。
效能
首先是這項編碼機制本身的效能機制。在大多數情況下,相較於其他資訊編碼方式,效能變化應該不大且微幅。對於具有昂貴或稀少參數的系統呼叫,效能可能會略有提升。
第二項是系統 API 的效能變化,這些 API 會改用「成功但有更多資訊」的架構。這項變更的部分動機是向使用者空間傳達更多資訊,以便做出更精密的決策,進而提升效能。
整體而言,我們預期系統呼叫能力傳達額外資訊所帶來的成效提升,將會超過評估狀態值時稍微複雜的程式碼。
除了效能之外,這項變更和日後使用這項能力進行的變更,可能會改變許多二進位檔中的程式碼大小,尤其是產生的 FIDL 繫結。
安全性考量
將 zx_status_t 範圍嵌入其他型別可能會造成混淆,因此有引入軟體錯誤的風險。執行這類嵌入作業的函式或通訊協定應仔細評估,確認好處是否大於造成混淆的風險。
將函式從規格外的 zx_status_t 用途和應用程式專屬錯誤代碼遷移至更明確的型別,應可減少混淆情況。
隱私權注意事項
這項提案不會以有意義的方式與使用者資料互動,因此應該不會影響隱私權。
測試
我們會為上述幾個新函式開發單元測試。
說明文件
樹狀結構 Markdown 說明文件和程式碼內註解會更新,以反映 zx_status_t 的新定義。
缺點、替代方案和未知事項
缺點:來自不受信任來源和 FIDL 繫結的 zx_status_t 值
目前我們無法防止超出範圍的 zx_status_t 值透過管道傳輸。如果應用程式從不受信任的來源收到 zx_status_t 值,且這些值必須在範圍內,則需要手動驗證。從長遠來看,或許可以更新 FIDL 繫結產生器,檢查並拒絕超出範圍的 zx_status_t 值,但這項工作與本 RFC 無關。
替代方案:核心輸出參數
核心的另一個設計空間是使用額外的 out 參數。這種做法有幾個缺點:
變更這些系統呼叫的類型是侵入性較高的變更,需要較長的遷移時間。
這些系統呼叫都必須有含 out 參數的變數,或者呼叫端不需額外資訊,因此傳遞 null。兩者在人體工學方面都較差。
使用 out 引數傳達少量資訊,會浪費稀少且昂貴的資源,因為這會耗用少數幾個暫存器之一 (特別是在 x86_64 上),或是透過指標進行相當昂貴的 user_copy。
替代做法:將 zx_status_t 的非負值留空,供自由使用
這項提案建議限制有效 zx_status_t 值的範圍,以利將範圍嵌入其他型別,但不允許應用程式直接使用未使用的範圍。
先前的提案是分割 zx_status_t 空間,將負值保留給錯誤,並允許函式視需要使用非負值。允許將 zx_status_t 型別重複用於函式專屬用途,可讓開發人員輕鬆開始從函式傳回額外酬載 (無須建立額外型別),且方便核心系統呼叫開始傳回額外資料給特定呼叫端,同時不會破壞現有呼叫端的 ABI。
但缺點是光看函式型別,就無法清楚瞭解函式可能傳回的值範圍。此外,也支援下列常見的成語:
zx_status status = CallFunction();
if (status != ZX_OK) {
return status;
}
如果 CallFunction 使用回傳代碼的正向空間,就無法再保證正確性。
zx_status_t 可能有多種不同的解讀方式,因此我們拒絕採用這個替代方案,以免造成混淆 (進而導致錯誤)。
替代方法:將代碼劃分為錯誤代碼和成功代碼
先前的提案建議將 zx_status_t 分割為錯誤代碼和成功代碼範圍,並將每個範圍進一步分割為系統定義和應用程式定義的範圍。
這種做法有幾個缺點:
系統定義的成功代碼實用性不明: 錯誤代碼經常會向上傳播至呼叫堆疊,但成功代碼通常會立即處理或直接捨棄。 如果語意是全球通用的,就不太需要一組成功代碼。
如果只允許正值為成功代碼,會妨礙值的其他更有效率用途,例如傳回物件目前狀態的位元欄位,或是傳回位元組數等小型酬載。
如要將正空間用於成功代碼,必須遷移目前使用該空間的現有代碼,這些代碼目前用於應用程式專屬錯誤代碼。
允許每個函式使用整個非負空間,可減少遷移負擔,並為開發人員提供更多彈性。
既有技術和參考資料
Linux 核心會在內部使用負值範圍表示錯誤,並將正值範圍保留給函式專用。大多數系統呼叫會在系統呼叫邊界,將這個單一值分割為回傳代碼和執行緒本機
errno變數。UEFI 規格會將狀態空間劃分為負值 (錯誤)、零 (成功) 和正值 (警告)。錯誤和警告範圍都會使用第二個有效位元,進一步劃分為「EFI 保留」範圍和 OEM 範圍。
版本記錄
2021-03-12:移除提案中建議的新錯誤代碼部分,該代碼表示收到超出範圍的
zx_status_t值。而是會稽核樹狀結構內程式碼,移除這類用法,並更新 FIDL 繫結產生器,避免超出範圍的值在程序界限間傳播。2021 年 3 月 9 日:修改提案,將有效
zx_status_t值的範圍重新定義為[-2^30, 0]。這個縮減的範圍可讓zx_status_t更容易嵌入為其他型別的子範圍。2021-02-10:修改提案,將
zx_status_t中的負值拆分為應用程式錯誤和系統錯誤,但保留所有非負值,供應用程式使用,不需進一步解讀。2020 年 10 月 9 日:初步提案,將
zx_status_t錯誤空間分割為四個分區:應用程式錯誤和系統錯誤為負值;系統成功代碼和應用程式成功代碼為正值。0 會維持ZX_OK。