時鐘

名稱

clock - 用於追蹤時間進度的核心物件。

摘要

時鐘是時鐘單調性參考時間軸的一維仿射變換,可由時鐘維護者以原子方式調整,並由用戶端觀察。

說明

屬性

時鐘的屬性會在時鐘建立時建立,之後便無法變更。目前已定義三個時鐘屬性。

ZX_CLOCK_OPT_MONOTONIC

設定後,系統會保證時鐘會呈現單調性行為。也就是說,任何時鐘的觀察序列都保證會產生一連串時間,這些時間一律大於或等於先前的觀察結果。單調時鐘永遠不會倒轉,但可以跳轉。正式:

假設有時鐘 C,讓 C(x) 為從參考時間軸 C's 時間軸對應的函式。C(x) 是一個區塊線性函式,由 C 維護者決定的所有時間內的所有仿射轉換區塊組成。C 是單調的,只有在下列情況下才會發生:

針對所有 R1R2R2 >= R1

C(R2) >= C(R1)

ZX_CLOCK_OPT_CONTINUOUS

設定後,時鐘就會保證持續運作。也就是說,時鐘轉換的任何更新都保證與先前的轉換區段保持第一順序連續。正式:

Ci(x)C(x)i 同構轉換片段。假設 Ri 是參考時間軸上的第一個時間點,其中 Ci(x) 已定義。時鐘 C 為連續的條件為:對於所有 i

Ci(Ri + 1) = Ci + 1(Ri + 1)

備用時間

時鐘的截止時間代表時鐘可設定的最小值。由於時鐘只能向前走,而不能倒轉,因此時鐘觀察器不可能收到小於時鐘建立者設定的截止時間的值。

在建立時,您可以透過 zx_create_args_v1_t 結構提供備用時間。否則,預設值為 0。

在時鐘更新作業期間,如果嘗試將時鐘的值設為低於備用時間的值,系統會失敗並顯示 ZX_ERR_INVALID_ARGS。未經初始設定的時鐘一律會回報為時鐘設定的備用時間。備援時間不得低於預設值零。

隱含屬性

  • 系統中所有時鐘物件的參考時鐘為單調時鐘。
  • 所有時鐘物件的標稱單位都會指定為奈秒。這個屬性無法設定。
  • 所有時鐘物件的頻率調整單位皆指定為百萬分之一 (PPM)。
  • 時鐘物件的頻率調整最大允許範圍已指定為 [-1000, +1000] PPM。這項屬性無法設定。

其他建立選項

ZX_CLOCK_OPT_AUTO_START

在建立時鐘時使用這個選項,時鐘就會以已啟動狀態開始,而非預設的未啟動狀態。詳情請參閱「啟動時鐘」。

讀取時鐘

在提供時鐘句柄的情況下,使用者可以使用 zx_clock_read() 系統呼叫查詢該時鐘提供的目前時間。時鐘讀取 ZX_RIGHT_READ 權限。時鐘讀取作業保證對所有觀察者來說是一致的。也就是說,如果兩位觀察者在完全相同的參考時間 R 查詢時鐘,他們一律會看到相同的值 C(R)

參考時間軸、zx_ticks_get()zx_clock_get_monotonic()

如先前所述,zx_clock_get_monotonic() 是所有使用者建立的 zircon 時鐘的參考時間軸。也就是說,如果使用者知道時鐘例項的目前轉換,並指定時鐘例項時間軸上的值,則可計算時鐘單調時間軸上的對應點 (反之亦然)。這也表示,如果未對核心時鐘進行速率調整,時鐘單調性和核心時鐘的滴答速率會完全相同。

除了時鐘單調時程外,Zircon 核心也會透過 zx_ticks_get()zx_ticks_per_second() 公開「時標」時程。在內部,滴答實際上是時鐘單調性參考時間線,並直接從可供核心存取的架構適當計時器單位讀取。時鐘單調性其實是將以納秒為單位的標準化時間刻度線性轉換。兩個時間軸都會在核心啟動時從零開始計時。

由於時鐘單調性是根據刻度轉換的靜態轉換,而所有核心時鐘都是根據時鐘單調性轉換,因此除了時鐘單調性之外,刻度也可做為核心時鐘的參考時鐘。

擷取時鐘的詳細資料

除了讀取時鐘的目前值之外,具備 ZX_RIGHT_READ 權限的進階使用者也可以讀取時鐘,並在過程中使用 zx_clock_get_details() 取得詳細資料。呼叫成功後,傳回給呼叫端的詳細資料結構會包含:

  • 目前的時鐘單調到時鐘轉換。
  • 目前的時間間隔到時鐘轉換。
  • 時鐘目前的對稱誤差範圍預估值 (如有)。
  • 根據時鐘單調參考時間軸定義的,上次更新時鐘的時間。
  • 在觀察時鐘時,系統記時器的觀察結果。
  • 在建立時定義的錶面所有靜態屬性。
  • 每次更新時鐘的基礎轉換時,產生值都會變更的產生值。

進階使用者可以利用這些詳細資料,不僅計算時鐘的最近 now 值 (透過使用 Ticks-to-Clock 轉換來轉換已回報的 Ticks-now 觀察值,兩者皆由取得詳細資料作業回報),還可以:

  • 瞭解自上次 zx_clock_get_details() 作業 (使用產生隨機值) 以來,時鐘轉換是否已變更。請注意,時鐘產生的 Nonce 不保證會從任何指定值開始,也不保證會在每次更新時以任何特定方式 (例如以固定值遞增) 變更。相反地,每次更新時,產生值會變更為一個值,且該值保證會與更新發生前立即的值不同。
  • 將時鐘轉換作業與其他時鐘的轉換作業組合,以便推斷兩個時鐘之間的關係。
  • 瞭解時鐘維護者對誤差範圍的最佳估計值。
  • 根據上次校正時間、目前轉換作業和時鐘的最大許可校正因數,推論時鐘相對於參考時鐘的可能未來值範圍 (請參閱上方「|隱含屬性」一節所述的頻率調整作業最大許可範圍)。

啟動時鐘和時鐘信號

建立後,時鐘並未立即開始運作。所有嘗試讀取時鐘的動作都會傳回時鐘設定的截止時間,如果在建立時未指定,則預設為 0。

時鐘開始運作後,時鐘維護者會執行第一個更新作業,該作業必須包含設定值作業。時鐘會在該時間點開始運作,其速率等於參考時鐘加上維護者指定的偏差。

時鐘也有 ZX_CLOCK_STARTED 信號,可讓使用者瞭解時鐘實際啟動時間。這個信號一開始不會設為已設定,但在第一次成功更新作業後就會設為已設定。一旦啟動,時鐘就會持續運作,並且會一律提出 ZX_CLOCK_STARTED 信號。

一開始,時鐘是時鐘單調性的複本,因此時鐘單調性時間軸與合成時間軸之間的轉換函式為恆等函式。建立後,這部時鐘仍可能維持,但須遵守權利、ZX_CLOCK_OPT_MONOTONICZX_CLOCK_OPT_CONTINUOUS 屬性,以及設定的備用時間。

如果您使用 ZX_CLOCK_OPT_AUTO_START 選項建立時鐘,則無法設定大於目前時鐘單調時間的回溯時間。如果允許這項操作,則會導致時鐘的目前時間設為其截止時間之前的時間。

維護時鐘

擁有時鐘物件 ZX_RIGHT_WRITE 權限的使用者,可以使用 zx_clock_update() 系統呼叫,擔任時鐘的維護者。每次呼叫 zx_clock_update() 時,時鐘的三個參數都可能會調整,但並非每次都需要調整所有參數。這些值如下:

  • 時鐘的絕對值。
  • 時脈的頻率調整值 (與額定值的偏差,以 ppm 表示)
  • 時鐘的絕對誤差範圍估計值 (以奈秒為單位)

在系統呼叫本身期間,時鐘轉換會發生變更。使用者可能無法指定調整的具體參考時間。

對設有 ZX_CLOCK_OPT_MONOTONIC 屬性的時鐘進行任何絕對值變更,如果會導致非單調性行為,就會失敗,並傳回 ZX_ERR_INVALID_ARGS 的回傳碼。

第一個更新作業會啟動計時器,且必須包含設定值作業。

除了最開始的設定值作業之外,所有嘗試以 ZX_CLOCK_OPT_CONTINUOUS 屬性設定的計時器絕對值都會失敗,並傳回 ZX_ERR_INVALID_ARGS 的錯誤碼

時鐘誤差範圍估計值的注意事項

zx_clock_get_details() 系統呼叫可為使用者提供時鐘的多項精細詳細資料,包括「誤差範圍估計值」。這個以奈秒為單位的值,代表時鐘維護人員目前對時鐘與參考資料之間差距的最佳估計值。舉例來說,如果使用者擷取的時間 X 誤差範圍估計值為 E,則時鐘維護者會嘗試表示,他們認為時鐘的實際值位於 [ X-E, X+E ] 範圍內。

此預估值的信賴水準「不會」由核心 API 指定。有些時鐘維護者可能會使用嚴格的上限,其他人則會使用無法證明但提供「高信心」的上限,而其他人可能對自己的預估值沒有信心,甚至完全沒有信心。

如果使用者需要瞭解所存取錯誤預估值的客觀品質 (例如,要強制執行憑證有效日期或 DRM 授權到期日),則應瞭解系統中哪個元件會維護時鐘,以及維護者在提供錯誤邊界預估值的信心程度時,會提供哪些保證。

可對應的時鐘

除了使用標準的系統呼叫 (zx_clock_read()zx_clock_get_details()) 讀取時鐘或取得其詳細資料之外,Zircon 核心時鐘還提供另一種方法,可減少觀察時鐘的額外負擔,因為您 (幾乎) 不必進入 Zircon 核心即可觀察時鐘。這裡的主要概念是,時鐘的核心只是將仿射變換套用至所選參考時鐘的目前值。如果轉換狀態可透過共用記憶體由使用者模式存取,且基礎參考時鐘不需要進入 Zircon 核心即可讀取,則合成時鐘也可以觀察,而無須進入核心。輸入「mappable」時鐘。

創作

您可以呼叫 zx_clock_create(),並在建立時傳遞 ZX_CLOCK_OPT_MAPPABLE,藉此建立可對應的核心時鐘。新建立的時鐘句柄將包含 ZX_RIGHT_MAP 權限,以及所有其他預設權限。此外,透過 get-details 作業回報的選項欄位會包含 ZX_CLOCK_OPT_MAPPABLE 標記,反映出這是可對應的錶面。

對應

如要使用共用記憶體觀察時鐘的狀態,必須先執行兩項操作。

1) 必須決定時鐘狀態的對應大小。2) 時鐘的共用狀態必須對應至使用者的位址空間。

如要擷取時鐘對應的大小,使用者必須使用 ZX_INFO_CLOCK_MAPPED_SIZE 主題呼叫 zx_object_get_info(),並傳遞 uint64_t 大小的緩衝區,以便接收時鐘的對應大小。

一旦知道大小,使用者就可以呼叫 zx_vmar_map_clock(),實際將時鐘的狀態對應至其位址空間。這個呼叫的運作方式與 zx_vmap_map_vmo() 非常相似,只是內建了一些額外的限制。特別注意:

1) 僅允許 ZX_VM_PERM_READ 權限,所有其他 ZX_VM_PERM 標記都明確禁止,無論使用者擁有的句柄權限為何。對應時鐘資料一律必須為唯讀。2) 傳遞至 syscall 的 len 參數「必須」與從 get-info 呼叫擷取的值相符。3) 對應時鐘狀態時,無法指定 vmo_offset。必須一律對應整個時鐘狀態區域。

對時鐘進行對應時,必須同時具備時鐘句柄的 ZX_RIGHT_READZX_RIGHT_MAP 權限。

成功的對應作業會傳回時鐘物件狀態在指定 VMAR 中對應的虛擬位址。

取消對應

取消對時脈的對應程序,與取消對 VMO 或 I/O 緩衝區的對應程序相同。使用者只要在用來對應時鐘的 VMAR 上呼叫 zx_vmar_unmap(),即可傳遞從原始 get-info 呼叫收到的長度,以及從 zx_clock_map() 呼叫收到的虛擬位址。

使用者可能會在對時鐘建立對應後關閉時鐘的句柄,但仍可讀取時鐘的狀態。就像關閉 VMO 句柄不會破壞該 VMO 的任何對應項目一樣,關閉時鐘句柄也不會破壞任何對應項目。請參閱 zx_vmar_unmap()zx_vmar_destroy()

觀察已對應的時鐘

如要觀察已對應的時鐘,使用者可以呼叫 zx_clock_read_mapped()zx_clock_get_details_mapped(),並將已對應時鐘狀態的虛擬位址做為第一個參數傳遞。其他所有設定都維持不變,詳情請參閱 zx_clock_read()zx_clock_get_details() 的說明文件。

下列任何動作都會導致未定義的行為:

  • 嘗試使用任何虛擬位址 (除了原始對 zx_clock_map() 的呼叫傳回的位址) 呼叫對應的系統呼叫觀察時鐘。
  • 嘗試使用部分或完全未對應的時鐘狀態,呼叫已對應的時鐘觀察系統呼叫。

此外,使用者不得嘗試使用對應的時鐘狀態,推斷時鐘本身的詳細資料。時鐘狀態的格式未指定,且可能隨時變更。唯一合法的時鐘對應狀態使用方式,是搭配呼叫 zx_clock_read_mapped() orzx_clock_get_details_mapped()`。

使用提供的系統呼叫進行適當存取時,時鐘觀察值會保持「多觀察者」一致性,也就是說,如果這些觀察值有固定順序,則多個觀察者所做的一組觀察值會保持一致性,例如這些觀察值有固定順序。詳情請參閱這裡

zx_info_maps_tzx_info_vmos_t 結構體中的時鐘對應表示法

Zircon 目前提供兩個診斷 zx_object_get_info() 主題,用於擷取目前有效對應項目的資訊,分別是 ZX_INFO_VMAR_MAPSZX_INFO_PROCESS_MAPS,兩者都會傳回 zx_info_maps_t 結構體陣列,描述指定 VMAR 或程序中目前有效的對應項目。

時鐘對應項目的列舉方式與 VMO 對應項目相同,但有以下差異。

1) 由於時鐘目前不是可命名物件,因此系統會將字串「kernel-clock」傳回做為對應的名稱,而非通常會傳回的 VMO 目前名稱。2) 對應項目的 KOID 欄位會使用時鐘物件的 KOID 填入。

此外,程式可以使用 ZX_INFO_PROCESS_VMOS 主題,擷取 zx_info_vmos_t 結構體陣列,取得與程序相關聯的目前 VMOs 相關資訊。如果程序將時鐘對應至其位址空間,時鐘也會顯示在這項列舉中。就像 zx_info_maps_t 案例一樣,回報的 KOID 會是時鐘物件的 KOID,而回報的名稱會是「kernel-clock」。此外,項目的 flags 欄位會設定 ZX_INFO_VMO_VIA_MAPPING 位元,指出物件因有效對應而列於清單中。

範例

以下是簡易範例,說明如何對應及讀取時鐘。

class MappedClockReader {
 public:
  MappedClockReader(const zx::clock& clock) {
    zx_status_t status = clock.get_info(ZX_INFO_CLOCK_MAPPED_SIZE, &mapped_clock_size_,
                                        sizeof(mapped_clock_size_), nullptr, nullptr));
    ZX_ASSERT(status == ZX_OK);

    status = zx::vmar::root_self()->map_clock(ZX_VM_PERM_READ | ZX_VM_MAP_RANGE, 0, clock,
                                              mapped_clock_size_, &clock_addr_);
    ZX_ASSERT(status == ZX_OK);
  }

  ~MappedClockReader() {
    if (clock_addr_ != 0) {
      zx::vmar::root_self()->unmap(clock_addr_, mapped_clock_size_);
    }
  }

  zx_time_t Read() {
    zx_time_t result{0};
    const zx_status_t status = zx::clock::read_mapped(clock_addr_, &result);
    ZX_ASSERT(status == ZX_OK);
    return result;
  }

 private:
  zx_vaddr_t clock_addr_{0};
  uint64_t mapped_clock_size_{0};
}

參考資料