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

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

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

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

問題陳述

Monotonic Clock 停權」和「啟動時間軸 RFC」指出 Fuchsia 必須提供啟動時間軸,回報自啟動以來經過的時間長度,包括暫停到閒置的時間。該 RFC 提到需要變更核心,但並未明確指出具體內容。

摘要

此 RFC 說明在 Zircon 核心中支援啟動時間軸時,所需要的 sys 呼叫和 API 變更。

利害關係人

講師:jamesr@google.com

審查者:

  • adamperry@google.com
  • andresoportus@google.com
  • cja@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 個位元組的邊框間距,就足以容納以 4 位元組 zx_clock_t 儲存的 clock_id 所保留的 clock_id

更新 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 異動

中斷 Syscall

Zircon Interrupt API 包含兩個系統呼叫,可在單調時間戳記上運作:

// Current function signatures.

// Allows users to wait on an interrupt, and returns the timestamp at which the
// interrupt was triggered.
zx_status_t zx_interrupt_wait(zx_handle_t handle,
                              zx_time_t* out_timestamp);

// Triggers a virtual interrupt at the given timestamp.
zx_status_t zx_interrupt_trigger(zx_handle_t handle,
                                 uint32_t options,
                                 zx_time_t timestamp);

這會造成問題,因為中斷會在暫停期間觸發,而此時單調時鐘會暫停。因此,這些時間戳記必須改用啟動時間軸。具體來說,系統呼叫簽章會變更為:

// Proposed function signatures.

zx_status_t zx_interrupt_wait(zx_handle_t handle,
                              zx_instant_boot_t* out_timestamp);
zx_status_t zx_interrupt_trigger(zx_handle_t handle,
                                 uint32_t options,
                                 zx_instant_boot_t timestamp);

由於 zx_instant_boot_tzx_time_t 都是 int64_t 的別名,因此只要簡單搜尋及取代,就能完成這項變更。

libzx 包裝函式

libzx 程式庫會在 zx_interrupt_waitzx_interrupt_trigger 系統呼叫周圍提供 C++ 包裝函式。這些包裝函式必須更新,才能使用新的啟動時間戳記。由於這些包裝函式會在不同時間軸上使用時間戳記的強型別,因此必須將所有呼叫網址遷移至使用啟動時間。

通訊埠封包變更

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

zx_packet_interrupt_t

zx_interrupt_bind 系統呼叫可讓使用者將中斷物件繫結或解除繫結至連接埠。觸發繫結中斷時,ZX_PKT_TYPE_INTERRUPT 類型的封包會在指定的通訊埠排入佇列。該封包的結構如下:

typedef struct zx_packet_interrupt {
  // Timestamp at which the interrupt was triggered.
  zx_time_t timestamp;
  // ..some reserved fields...
} zx_packet_interrupt_t;

timestamp 欄位必須切換為使用啟動時間 (因此變成 zx_instant_boot_t),原因與 zx_interrupt_wait 傳回的時間戳記必須位於啟動時間表上相同。

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 不會綁定特定時間軸。

既有技術與參考資料