RFC-0260:核心啟動時間支援

RFC-0260:核心啟動時間支援
狀態已接受
區域
  • 核心
說明

列舉支援啟動時間表所需的 Zircon API 變更。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2024-06-14
審查日期 (年-月-日)2024-09-25

問題陳述

Monotonic Clock Suspension and the Boot Timeline RFC 指出,Fuchsia 必須提供啟動時程,以便回報啟動後經過的時間,包括在暫停待命期間所花費的時間。該 RFC 提到需要進行核心變更,但並未明確指出這些變更為何。

摘要

這份 RFC 說明支援 Zircon 核心的啟動時間表所需的系統呼叫和 API 異動。

相關人員

講師:jamesr@google.com

審查者:

  • adamperry@google.com
  • andresoportus@google.com
  • cja@google.com
  • costan@google.com
  • fmil@google.com
  • gkalsi@google.com
  • harshanv@google.com
  • johngro@google.com
  • maniscalco@google.com
  • mcgrathr@google.com
  • miguelfrde@google.com
  • surajmalhotra@google.com
  • tgales@google.com
  • tmandry@google.com

社會化:

這份 RFC 已以一系列文件的形式公開,每份文件都會討論設計的各個部分。這些文件已傳送給核心團隊和機構內的各個人員,並由他們進行重複驗證。

需求條件

核心「必須」支援:

  1. 讀取啟動時間表的目前值。
  2. 使用啟動時間表做為參考時間表,建立自訂時鐘。
  3. 在啟動時間表上建立計時器,不過可喚醒系統的計時器會明確保留給日後的 RFC。
  4. 在啟動時間軸中回報記錄訊息和當機記錄的時間戳記。

請注意,我們不打算在啟動時間表中支援其他與時間相關的系統呼叫,因為我們尚未發現有足夠動機的用途。如果有相關用途,我們會在日後的 RFC 中重新考慮這項決定。

設計

在 Zircon API 中推出新的時間類型別名

Zircon 有幾種表示今天時間的方式:

  1. zx_time_t:以奈秒為單位,代表某個時間點。一般來說,這個點是從單調時間軸上取樣,但不一定如此;例如 zx_clock_read 也會傳回 zx_time_t,但並非位於單調時間軸上。
  2. zx_duration_t:代表兩個時間點之間的時間長度或距離。這個量值與時間軸沒有具體關聯,但一律以奈秒為單位。
  3. zx_ticks_t:以平台特定的時間間隔表示時間點或時間長度。

這些都是 int64_t 的別名,這表示編譯器不會為任何實體提供類型檢查。它們只會用於改善程式碼的易讀性。將這些類型重複用於啟動時間表,會大幅降低使用時間戳記的程式碼可讀性,因為開發人員無法輕易區分不同時間表上的時間戳記。

因此,我們將推出新的類型別名,並採用以下結構:

zx_<kind>_<timeline>_[units_]t

其中:

  • kind 可以是 instantduration
  • timeline 可以是 monoboot
  • unitsticks 或省略,在這種情況下,單位會視為奈秒。絕大多數的程式碼都會以奈秒為單位計算時間,因此這項省略動作可讓常見的情況更容易輸入。

大多數現有的 zx_time_t 用途都會遷移至 zx_instant_mono_t,而大多數現有的 zx_duration_t 用途則會遷移至 zx_duration_mono_t。我們會依個別情況評估並遷移 zx_ticks_t 的用途。

現有的別名不會移除,並會在需要使用模糊類型時使用 (例如 zx_clock_read 仍會傳回 zx_time_t)。核心團隊的目標是在長期內移除這些模糊類型,但這項作業的確切性質超出本文範圍,將留待日後的 RFC 處理。

時間的使用者空間類型

雖然前一個章節所述的時間類型別名會改善 Zircon API 中的程式碼清晰度和可讀性,但不會提供編譯器強制執行的類型安全性。相反地,這類型安全性將由 Zircon 的 Rust 繫結C++ libzx 程式庫中的使用者空間系統呼叫包裝函式程式庫提供,這兩者都需要透過此 RFC 中引入及修改的系統呼叫進行擴充。使用者空間程式碼應盡可能使用這些程式庫。

適合強型別化的 API 需要在每種語言中進行精心設計,才能符合慣用性並在實際操作中易於使用。我們不會倉促進行這項工作,而且也不需要在各語言中完成這項工作,即可完成前面所述的 C API 類型遷移作業。

讀取目前的啟動時間

我們將引入兩個系統呼叫,讓使用者模式讀取啟動時間表的目前值:

zx_instant_boot_t zx_clock_get_boot(void);
zx_instant_boot_ticks_t zx_ticks_get_boot(void);

第一個會以奈秒為單位回報時間,第二個則會以平台時脈回報時間。這會在單調時間軸上鏡像對應的系統呼叫。

請注意,在單調和啟動時間軸上,刻度與秒數的比率是一樣的,也就是說,zx_ticks_per_second 的結果可用於將 zx_ticks_getzx_ticks_get_boot 的結果轉換為秒數。

建立自訂錶面

使用者模式時鐘會使用 zx_clock_create 系統呼叫建立。系統呼叫函式已接受 options 參數,因此我們不需要變更函式簽章。

相反地,我們會推出名為 ZX_CLOCK_OPT_BOOT 的新選項標記,指示新建立的時鐘使用啟動時間軸做為參考時間軸。

時鐘 (單調和啟動) 的時間軸屬性將無法變更,也就是說,時鐘一旦建立就無法切換時間軸。

重新命名 zx_clock_details 中的欄位

zx_clock_details_v1_t 結構體包含兩個欄位,用於儲存從單調時間軸到時鐘合成時間軸的轉換:

typedef struct zx_clock_details_v1 {
  // other fields
  zx_clock_transformation_t ticks_to_synthetic;
  zx_clock_transformation_t mono_to_synthetic;
  // other fields
} zx_clock_details_v1_t;

這些欄位會重新命名,以強調這些欄位現在會儲存從參考時間軸轉換至時鐘合成時間軸的轉換作業:

typedef struct zx_clock_details_v1 {
  // other fields
  zx_clock_transformation_t reference_ticks_to_synthetic;
  zx_clock_transformation_t reference_to_synthetic;
  // other fields
} zx_clock_details_v1_t;

快速掃描程式碼庫後,我們發現很少人使用這些欄位,而且這些使用者都位於 fuchsia.git 之外。因此,原地重新命名應該會比較簡單。

建立開機計時器

使用者模式計時器是使用 zx_timer_create 系統呼叫建立。這個系統呼叫可方便地接受 clock_id 參數,該參數會決定計時器應運作的時間表。

因此,系統會引入名為 ZX_CLOCK_BOOT 的新 clock_id 值,指示新建立的計時器在啟動時間軸上運作。

與時鐘類似,計時器的時間軸屬性無法變更,也就是說,計時器一旦建立就無法切換時間軸。

請注意,zx_timer_set 會重複使用來設定啟動計時器。由於此系統呼叫會同時將單調和啟動期限做為輸入內容,因此其參數類型會保留為 zx_time_t

clock_id 新增至 zx_info_timer_t

使用者可以使用 ZX_INFO_TIMER 主題呼叫 zx_object_get_info,以取得計時器句柄的相關資訊。產生的 zx_info_timer_t 結構體會修改為包含建立計時器時使用的 clock_id

通常,這需要一些結構體演進,但在這種情況下,結構體已具有 4 個位元的填充,足以容納 clock_id,而 clock_id 會儲存為 4 個位元的 zx_clock_t

更新 Zircon 結構體中的時間戳記

Zircon 結構體中有多個時間戳記,可用於回報今天的單調時間,但應在可用時切換為使用啟動時間。

請注意,繼續回報單調時間的時間戳記在功能上不會變更,但類型會更新為 zx_instant_mono_t,如上文類型別名一節所述。

zx_log_record_t 時間戳記

zx_log_record_t 是 Zircon 公開的結構體,可說明偵錯記錄中訊息的結構。這個結構體包含 timestamp 欄位,該欄位為 zx_time_t,也是 ZX_CLOCK_MONOTONIC 上的點。這會更新為 zx_instant_boot_t,因此會是 ZX_CLOCK_BOOT 上的點。

當機記錄檔的上線時間戳記

crashlog 目前會回報使用單調時間的 uptime 欄位。由於單調時間不包含暫停期間,因此應修改為使用啟動時間,並將其類型更新為 zx_instant_boot_t

Interrupt API 異動

中斷系統呼叫

Zircon Interrupt API 包含兩個使用時間戳記的系統呼叫:zx_interrupt_waitzx_interrupt_trigger。目前,這些系統呼叫會使用單調時間軸。不過,由於中斷動作可能會在「暫停至閒置」期間觸發,因此預設的時間戳記行為會變更,以便使用啟動時間表。這有助於確保中斷時間戳記是唯一的,即使跨暫停-繼續執行週期也是如此,但唯一性仍受限於時鐘解析度。使用單調時鐘後,在暫停至閒置期間觸發的所有中斷都會具有相同的時間戳記,也就是系統暫停的時間。

為了與可能依賴單調時間戳記的現有驅動程式 (例如顯示器驅動程式) 保持相容性,我們會在 zx_interrupt_create 系統呼叫中新增名為 ZX_INTERRUPT_TIMESTAMP_MONO 的新標記。設定這個旗標後,相關聯的中斷物件將繼續使用 zx_interrupt_waitzx_interrupt_trigger 和中斷埠封包的單調時間戳記 (詳見下文)。

zx_object_get_info 主題

由於中斷物件會在建立時設定時間表,因此我們必須為使用者空間提供一種方法,以便在執行階段查詢指定中斷句柄的時間表。為此,系統會將新的 ZX_INFO_INTERRUPT 主題新增至 zx_object_get_info。傳入此主題會傳回下列結構體:typedef struct zx_info_interrupt { // The options used to create the interrupt object. uint32_t options; } zx_info_interrupt_t; 接著,呼叫端可以檢查 options 欄位是否有 ZX_INTERRUPT_TIMESTAMP_MONO

libzx 包裝函式

libzx 程式庫會在 zx_interrupt_waitzx_interrupt_trigger 系統呼叫周圍提供 C++ 包裝函式。這些包裝函式需要更新,才能同時使用單調遞增和啟動時間戳記。

通訊埠封包變更

有幾個 port 封包包含需要修改的時間戳記。

zx_packet_interrupt_t

zx_interrupt_bind 系統呼叫可將中斷物件繫結至連接埠。當繫結的中斷觸發時,系統會在通訊埠上排入 ZX_PKT_TYPE_INTERRUPT 封包。這個封包包含 timestamp 欄位,指出中斷發生的時間。

為與更新後的中斷 API 保持一致,這些封包中的 timestamp 欄位預設會改為使用啟動時間表。這可確保所有中斷時間戳記 (無論是透過系統呼叫或中斷封包取得),都能在暫停-繼續執行週期中提供一致的中斷時間資訊。

不過,如果是使用 ZX_INTERRUPT_TIMESTAMP_MONO 標記建立的中斷,對應封包中的 timestamp 欄位將繼續使用單調時間戳記。這可確保與依賴此行為的驅動程式相容。

zx_packet_signal_t

此封包會排入佇列,並與使用 zx_object_wait_async 系統呼叫的物件繫結,該系統呼叫具有以下簽名:

zx_status_t zx_object_wait_async(zx_handle_t handle,
                                 zx_handle_t port,
                                 uint64_t key,
                                 zx_signals_t signals,
                                 uint32_t options);

signals 集合中的信號在 handle 上斷言,且呼叫端在 options 欄位中傳遞 ZX_WAIT_ASYNC_TIMESTAMP 時,產生的封包會排入連接埠:

typedef struct zx_packet_signal {
  // ... other fields ...
  uint64_t timestamp;
  // ... reserved fields ...
} zx_packet_signal_t;

timestamp 欄位會記錄信號的斷言時間。視我們等待的物件類型而定,時間戳記會出現在不同的時間軸上。舉例來說,我們希望在等待啟動計時器時取得啟動時間戳記。

因此,我們建議採取以下做法:

  1. 在名為 ZX_WAIT_ASYNC_BOOT_TIMESTAMPzx_object_wait_async 系統呼叫中新增旗標,表示 timestamp 欄位應使用開機時間表。
  2. timestamp 的類型切換為多型別別名 zx_time_t

我們會稽核 ZX_WAIT_ASYNC_TIMESTAMP 標記的所有現有用途,並確保在暫停單調時鐘前,所有需要啟動時間戳記的呼叫端都已遷移。

實作

核心 API 的變更會先實作,每個變更類別 (讀取時間、時鐘、計時器等) 都會分開為不同的 CL。

這些變更 (應相對較小) 之後,我們會進行較大的變更,為 Rust 和 C++ 系統呼叫包裝函式程式庫新增啟動時間支援。這些異動將分為以下兩個階段:

  1. 第 1 階段將新增對此 RFC 新增或修改的系統呼叫支援。這會需要修改所用類型,但這個階段主要會著重於功能變更。
  2. 第 2 階段將針對時間類型新增更強大的類型強制執行機制。這項工作會在第 1 階段後完成,因為需要更長的時間才能解決各語言所需的確切類型結構。

成效

支援啟動時間所需的系統呼叫修改作業純粹是加法運算,因此不應變更系統的任何現有效能特性。

同樣地,在 Zircon 中更新各種結構體的時間戳記欄位,以便使用啟動時間表,這對效能不會造成任何影響,因為計算啟動時間的成本不會比計算單調時間的成本高。

人體工學

使用時間系統呼叫和類型的使用者可能必須瞭解,現在核心支援多個時間軸。不過,大多數程式應該只使用單調時間。

回溯相容性

除了記錄記錄時間戳記時間軸的語意變更之外,這些變更都與原始碼和二進位檔相容。

不過,許多現有程式碼可能需要從使用單調時間軸切換為使用啟動時間軸,以維持正確性。如要進一步瞭解我們如何處理這項問題,請參閱「Monotonic Clock Suspension and the Boot Timeline RFC」。

安全性考量

我們不認為這項提案會帶來任何安全性影響。如要進一步瞭解原因,請參閱「Monotonic Clock Suspension and the Boot Timeline RFC」的安全性部分。

隱私權注意事項

這項提案不會影響系統隱私權,因為在核心中新增啟動時間支援功能不會涉及任何形式的資料收集。

測試

核心測試將進行修改,以便測試新的系統呼叫和現有系統呼叫的修改內容。

說明文件

您必須記錄上述所有 Zircon API 修改內容。具體來說,這表示:

  1. 記錄所有新的類型別名,並更新現有類型別名的文件。
  2. 為新加入的系統呼叫撰寫文件。
  3. 記錄新增至現有系統呼叫的新標記。
  4. 記錄更新的各種時間戳記時間軸。

此外,我們應該更新 fuchsia.dev 上的時鐘說明文件,強調 zx_time_t 不會綁定特定時間軸。

既有技術與參考資料