RFC-0085:減少 zx_status_t 空格

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 原始碼可能會指出在讀取完成後,還有更多資料待處理,或是針對 Socket 等類型提供更精細的閾值資訊。

  • 流程控制:核心和系統程式庫中常見的模式是回呼會傳回錯誤、值 ZX_ERR_STOPZX_ERR_NEXT,以表示應繼續呼叫回呼或停止呼叫回呼。這些特殊值雖然不是「本身」的錯誤,但目前會在系統定義的錯誤空間中指派代碼。

  • 傳送小型酬載:函式可能會在呼叫成功時,將少量資料做為結果的一部分傳送。舉例來說,zx_debuglog_read 系統呼叫的目前實作方式會巧妙地使用 zx_status_t 的正值,在成功時傳回讀入緩衝區的位元組數。

雖然理論上可以透過使用額外的 out 參數或更複雜的複合型別來解決所有這些用途,但在某些效能敏感的用途中,如果函式可以有可處理上述用途的簡單整數/註冊回傳值,效率會更高。

雖然本文件中使用「函式」和「傳回碼」等詞彙,但這些概念同樣適用於 FIDL 呼叫、系統呼叫等。同樣地,雖然這個設計會提供 C++ 範例,但相同的概念也適用於其他語言,包括 C、Rust 和 Go。

設計

在這個提案中,zx_status_t 仍是帶符號的 32 位元整數,並會繼續定義為具有單一成功代碼 ZX_OK (0)。所有其他值應繼續視為錯誤。

不過,我們更新了 zx_status_t 的定義,以便:

  • 有效的 zx_status_t 值範圍為 [-2^30, 0] (即 -10737418240)。這個範圍內的所有值都會是系統定義的錯誤代碼或單一成功代碼 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_NEXTZX_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 狀態範圍的使用者,以便使用新類型,更清楚說明產生的其他錯誤。

成效

首先是這個編碼方案的效能機制per se。在大多數情況下,與其他編碼資訊的方案相比,效能變化應會很小且微乎其微。對於具有昂貴或稀少參數的系統呼叫,效能可能會略為提升。

第二項是系統 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 參數的變化版本,或是呼叫端不感興趣的額外資訊傳遞為空值。這兩種方法都會導致人體工學降級。

使用 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-03-09:修改提案,將有效 zx_status_t 值的範圍重新定義為 [-2^30, 0]。這個縮減範圍可讓 zx_status_t 更容易嵌入為其他類型的子範圍。

  • 2021-02-10:修改提案,將 zx_status_t 中的負值分為應用程式錯誤和系統錯誤,但讓所有非負值可供應用程式使用,而無需進一步解讀。

  • 2020-10-09:初步提案將 zx_status_t 錯誤空間分成四個區段:應用程式錯誤和系統錯誤為負值;系統成功代碼和應用程式成功代碼則為正值。0 會維持 ZX_OK