RFC-0085:減少 zx_status_t 空格

RFC-0085:減少 zx_status_t 空間
狀態已接受
區域
  • 核心
  • 系統
說明

縮減有效 zx_status_t 值的範圍,以更輕鬆地嵌入其他類型中。

問題
變更
  • 436685
作者
審查人員
提交日期 (年/月)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 核心 (包括內部和做為 syscall 的傳回值);在許多 FIDL 通訊協定中用於表示錯誤;用於多個系統程式庫來回報錯誤;以及內部通常用於純應用程式程式碼,用來回報函式之間的錯誤。

雖然 zx_status_t 目前的定義允許傳遞各種不同的錯誤狀況,但目前只能傳達單一成功值。一段時間過後,Fuchsia 開發人員發現了以下幾個用途,將其他非錯誤資訊傳達給呼叫端:

  • 非錯誤警告:某些函式想要產生警告,其中函式通常會成功,但有潛在的問題。舉例來說,寫入緩衝區的要求成功,但緩衝區小於可用資料量。

  • 物件狀態的額外資訊:例如,核心處理序間通訊 (IPC) 基本功能也許會表示在讀取完成後有較多資料處於待處理狀態,或是針對通訊端等類型提供更詳盡的門檻資訊。

  • 流程控制:核心和系統程式庫的常見模式是回呼傳回錯誤、值 ZX_ERR_STOP 表示不應再呼叫回呼,或使用 ZX_ERR_NEXT 表示應繼續呼叫回呼。雖然這些特殊值並非「每」的錯誤,但目前是在系統定義的錯誤空間中指派程式碼。

  • 通訊小型酬載:函式可能希望在呼叫成功時,在其中傳送少量資料做為結果的一部分。舉例來說,目前 zx_debuglog_read syscall 的實作方式會使用 zx_status_t 的正值,傳回成功讀取緩衝區的位元組數。

雖然理論上可以使用額外的外層參數或更複雜的複合類型來解決上述所有用途,但在某些效能敏感用途中,如果函式可提供簡單的整數/暫存傳回值來處理上述用途,則會更有效率。

在本文件中,我們使用「函式」和「傳回程式碼」一詞,這些提案與 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 所需的步驟如下:

  • 更新描述 zx_status_t 語意的說明文件 (包括 Markdown 文件和來源註解),以符合此規格提議的內容。

  • 更新 zx_debuglog_read 系統呼叫以使用自訂 (非 zx_status_t) 類型。

  • 更新 zx_status_t 狀態範圍目前的使用者,使用更能準確說明產生其他錯誤的新類型。

效能

第一種是這些編碼配置的效能機制。比起其他編碼此資訊的其他配置,效能變化應該不大且差距。針對具有昂貴或稀少參數的系統呼叫,效能可能會稍微改善。

其次是系統 API 的效能變更,改為使用 success-with-more-information 配置。變更的原因是,向使用者空間傳達更多資訊,這樣有助於做出更複雜的決策,進而提高效能。

整體而言,我們預期系統呼叫用來傳遞其他資訊的能力有助於提升效能,且會讓程式碼評估狀態值變得更加複雜。

除了效能以外,這項變更和日後使用此能力的變更可能會變更多個二進位檔的程式碼大小,尤其是在產生的 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 參數的變化版本,或者呼叫端對額外資訊傳遞的空值,完全沒有興趣。兩者皆符合人體工學降級。

使用傳出引數傳遞少量資訊是效率不彰和使用成本昂貴的資源,因為這會耗用少量的暫存器 (尤其是 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 kernel 會在內部使用負數範圍,針對特定功能保留正數範圍。大部分的系統呼叫會在 syscall 邊界將這個單一值分割為傳回代碼,以及執行緒本機 errno 變數。

  • UEFI 規格會將狀態空間分區為負值 (錯誤)、零 (成功) 和正值 (警告)。錯誤和警告範圍都會進一步分為「EFI 保留」範圍和 OEM 範圍,使用最重要的位元。

版本記錄

  • 2021-03-12:我們提案的部分建議採用新的錯誤代碼,指出收到超出範圍的 zx_status_t 值。相反地,系統會稽核樹狀結構內程式碼來移除這類用法,並更新 FIDL 繫結產生器,避免超出範圍的值,以免超出程序邊界。

  • 2021-03-09:已修改提案,將有效 zx_status_t 值的範圍重新定義為 [-2^30, 0]。這個縮減範圍可讓 zx_status_t 更容易嵌入為其他類型的子範圍。

  • 2021 年 2 月 10 日:修改提案,在應用程式錯誤和系統錯誤之間分割 zx_status_t 中的負值,但要在不進一步解讀的情況下,保留應用程式的所有非負值。

  • 2020-10-09:初始提案將 zx_status_t 錯誤空間分割為四個分區:應用程式錯誤和系統錯誤做為負值;而系統成功代碼和應用程式成功碼則為正值。0 仍然是 ZX_OK