檢查 VMO 檔案格式

本文件說明元件檢查檔案格式 (檢查格式)。

以檢查格式格式化的檔案稱為「檢查檔案」,此檔案通常具有 .inspect 副檔名。

瞭解如何變更格式。請參閱「擴充檢查檔案格式」一節。

簡介

元件檢查可讓元件在執行階段公開有關其狀態的結構化階層資訊。

元件會使用檢查格式代管對應的虛擬記憶體物件 (VMO),以公開包含此內部狀態的檢查階層

檢查階層包含包含類型屬性的巢狀 Nodes

目標

本文件說明的「檢查格式」具有下列目標:

  • 資料流動的低負載變化

    「檢查檔案格式」可讓你直接變更資料。舉例來說,整數遞增的負擔為約 2 個原子遞增量。

  • 支援非靜態階層

    您可以在執行階段修改檢查檔案中儲存的階層。您隨時可以在階層中新增或移除子項。透過這種方式,階層可以密切代表元件工作集中的物件階層。

  • 單一寫入者,在沒有明確同步處理的情況下,有多個閱讀器並行

    與寫入者同時運作的讀取器會對應 VMO,並嘗試拍攝資料快照。寫入者表示其位於關鍵區段,但不需與讀取器明確同步處理的產生計數器。讀取器會使用產生計數器,判斷 VMO 快照的一致且可以安全讀取的時間。

  • 在元件終止後,您可能仍可查看資料

    即使寫入元件終止,讀取器仍可能保有含有檢查資料的 VMO 控制代碼。

術語

本節定義本文件使用的常見術語。

  • 檢查檔案 - 使用本文件所說明格式的受限位元組序列。
  • 檢查 VMO - 儲存在虛擬記憶體物件 (VMO) 中的檢查檔案。
  • 區塊 - 檢查檔案的大小部分。區塊有索引和訂單。
  • 索引:特定區塊的專屬 ID。byte_offset = index * 16
  • 順序 - 指定區塊的大小,這個區塊的大小與最小大小略有不同。size_in_bytes = 16 << order。依類別的大小 (二的力量) 將區塊拆分為多個類別。
  • 節點 - 階層中其他值可嵌入的已命名值。只有階層中的節點可以成為父項。
  • 屬性 - 包含輸入資料 (例如字串、整數等) 的已命名值。
  • 階層 - 從單一「根」節點遞減排列的節點樹狀結構,每個節點可能包含屬性。檢查檔案包含單一階層。

這份文件中「必須」、「應/建議」和「可能」關鍵字符合 RFC 2119 的定義。

所有位元場圖皆以小端序儲存。

版本

目前版本:2

模塊

檢查檔案已分割成多個 Blocks,大小必須為 2 的功率。

區塊大小下限必須為 16 個位元組 (MIN_BLOCK_SIZE),且區塊大小上限必須為 16 個位元組的倍數。建議導入器指定的區塊大小上限 (通常為 4096 位元組)。在我們的參考實作中,區塊大小上限為 2048 個位元組 (MAX_BLOCK_SIZE)。

所有區塊都必須根據 16 位元組邊界對齊,而 VMO 內的定址時會納入索引,並指定 16 位元組偏移 (offset = index * 16)。

我們使用 24 位元的索引建立索引,因此檢查檔案上限為 256 MiB。

block_header 包含 16 個位元組,如下所示:

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------------------------------|
| O | R | Type  |                                               |
|---------------------------------------------------------------|
|                                                               |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
The rest (left blank) depends on the payload

每個區塊都有 order,用來指定其大小。

如果區塊大小上限為 2,048 個位元組,則會有 8 個可能的區塊順序 (NUM_ORDERS),編號為 0...7,對應大小為 16、32、64、128、256、512、1024 和 2048 個位元組的區塊。

每個區塊也都有類型,用來決定區塊中其餘位元組的解譯方式。

好友配置

這個區塊版面配置允許使用好友分配,有效率地分配區塊。我們建議使用好友分配策略,但使用檢查格式並非必要條件。

類型

所有支援的類型都會在 //zircon/system/ulib/inspect/include/lib/inspect/cpp/vmo/block.h 中定義的類別,如下所示:

列舉 value 類型名稱 類別
kFree 0 FREE 內部
kReserved 1 RESERVED 內部
kHeader 2 HEADER 標頭
kNodeValue 3 NODE_VALUE
kIntValue 4 INT_VALUE
kUintValue 5 UINT_VALUE
kDoubleValue 6 DOUBLE_VALUE
kBufferValue 7 BUFFER_VALUE
kExtent 8 EXTENT 超出範圍
kName 9 NAME 名稱
kTombstone 10 TOMBSTONE
kArrayValue 11 ARRAY_VALUE
kLinkValue 12 LINK_VALUE
kBoolValue 13 BOOL_VALUE
kStringReference 14 STRING_REFERENCE 參考資料
  • Internal:這些類型是用於實作區塊分配作業,而僅供讀取器忽略。

  • 標頭 - 這個類型可讓讀取器偵測檢查檔案,並瞭解快照一致性的原因。這個區塊必須位於索引 0 上。

  • - 這些類型會直接顯示在階層中。值必須有名稱和父項 (必須是 NODE_VALUE)。

  • Extent:這個類型會儲存可能無法納入單一區塊的長二進位資料。

  • 名稱 - 這個類型會儲存符合單一區塊的二進位資料,並且通常用於儲存值的名稱。

  • 參考資料 - 這個類型保留一個標準值,可供其他區塊參照。

每種類型會以不同的方式解讀酬載,如下所示:

免費

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Next free block       |                       |
|---------------------------------------------------------------|
|                                                               |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 0
Next free block = index (optional)

FREE 區塊可用於分配。重要的是,零值區塊 (\0 的 16 個位元組) 會解讀為順序 0 的 FREE 區塊,因此緩衝區可以零為零來釋放所有區塊。

寫入器實作項目可能會將 FREE 區塊中 8..63 中未使用的位元用於任何用途。寫入者實作必須將所有其他未使用的位元設定為 0。

建議寫入者使用在上方指定的位置,儲存相同順序下一個可用區塊的索引。使用此欄位時,自由區塊可能會建立各大小的免費區塊清單連結,以快速分配。當 NextFreeBlock 指向 FREE 位置或順序不相同 (通常是索引 0 的標頭區塊) 時,清單末端會到達清單結尾。

已預訂

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------------------------------|
| O | R | Type  |                                               |
|---------------------------------------------------------------|
|                                                               |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 1

RESERVED 區塊可讓您輕鬆變更為其他類型。這指的是分配區塊與設定區塊類型之間的選用轉換狀態,有助於正確檢查實作項目 (確保使用中的區塊不會視為免費)。

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+---------------+-------------------------------|
| O | R | Type  | Version       | Magic number                  |
|---------------------------------------------------------------|
| Generation count                                              |
|-------------------------------+-------------------------------|
| Size in bytes                 | Unused                        |
|---------------------------------------------------------------|
|                                                               |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 2
Version = 2
Magic number = "INSP"

檔案開頭必須有一個 HEADER 區塊。其中包含魔術號碼 (「INSP」)、「版本」 (目前為 2)、用於並行控制的基數,以及以位元組分配的 VMO 部分大小。標頭的第一個位元組不得為有效的 ASCII 字元。

如要瞭解如何使用產生數來實作並行控制,請參閱下一節

NODE_VALUE 和 TOMBSTONE

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Parent index          | Name index            |
|---------------------------------------------------------------|
| Reference count (optional)                                    |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = {3,10}

節點是進一步建立巢狀結構的錨點,而值的 ParentID 欄位只能參照 NODE_VALUE 類型的區塊。

NODE_VALUE 區塊支援選用的參照計數空值標記,以便您能夠有效率地實作,如下所示:

Refcount 欄位可能包含參照指定 NODE_VALUE 做為其父項的值數量。刪除時,NODE_VALUE 會成為名為 TOMBSTONE 的新特殊類型。只有在 TOMBSTONERefcount 為 0 時,才會刪除。

這可讓不必明確追蹤節點子項的寫入器實作作業,還能避免發生以下情況:

// "b" has a parent "a"
Index | Value
0     | HEADER
1     | NODE "a", parent 0
2     | NODE "b", parent 1

/* delete "a", allocate "c" as a child of "b" which reuses index 1 */

// "b"'s parent is now suddenly "c", introducing a cycle!
Index | Value
0     | HEADER
1     | NODE "c", parent 2
2     | NODE "b", parent 1

{INT、UINT、DOUBLE,BOOL}_VALUE

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Parent index          | Name index            |
|---------------------------------------------------------------|
| Inlined numeric value                                         |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = {4,5,6,13}

數字 VALUE 區塊全都包含 64 位元數字類型,內嵌於區塊的第二 8 個位元組中。

BUFFER_VALUE

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Parent index          | Name index            |
|---------------------------------------------------------------|
| Total length                  | Extent index              | F |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 7
Total length = size of the buffer
Extent index = index of the first extent containing bytes for the buffer
F = Display format {0,1}

一般 BUFFER_VALUE 區塊會參照一或多個已連結的 EXTENT 區塊任意位元組資料。

BUFFER_VALUE 區塊包含第一個 EXTENT 區塊的索引,且該區塊包含所有範圍中的資料總長度 (以位元組為單位)。

格式旗標會指定位元組資料的解讀方式,如下所示:

列舉 意義
kUtf8 0 位元組資料可能會解讀為 UTF-8 字串。
kBinary 1 位元組資料為任意二進位資料,且無法列印。

最大

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Next extent index     | R                     |
|---------------------------------------------------------------|
| Payload                                                       |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 8
Next extent index = index of next extent in the chain
Extent index = index of the extent containing bytes for the string
Payload = byte data payload up to at most the end of the block. Size
          depends on the order

EXTENT 區塊包含任意位元組資料酬載和鏈結中下一個 EXTENT 的索引。在讀取「Total Length」位元組之前,系統會依序讀取每個 EXTENT,藉此擷取 buffer_value 的位元組資料。

NAME

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------------------------------|
| O | R | Type  | Length    | Reserved                          |
|---------------------------------------------------------------|
| Payload                                                       |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 9
Payload = contents of the name. Size depends on the order

NAME 區塊會為物件和值提供使用者可理解的 ID。其中包含完全在指定區塊中的 UTF-8 酬載。

STRING_REFERENCE

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Next Extent Index     | Reference Count       |
|---------------------------------------------------------------|
| Total length                  | Payload                       |
'---------------------------------------------------------------'

O = Order
R = Reserved
Type = 14
Next Extent Index = index of the first overflow EXTENT, or 0 if Payload does not overflow
Reference Count = number of references to this STRING_REFERENCE
Total length = size of the Payload in bytes. Payload overflows into Next Extent if
               Total length > ((16 << Order) - 12)
Payload = the canonical instance of a string. The size of the Payload field depends on the
          Order. If the size of the Payload + 12 is greater than 16 << Order, then the Payload
          is too large to fit in one block and will overflow into Next Extent

STRING_REFERENCE 區塊用於在 VMO 中實作帶有參考語意的字串。這些是 EXTENT 連結清單的起始清單,這表示其值不受大小限制。如果系統應在預計 NAME 的位置使用 STRING_REFERENCE 區塊,

ARRAY_VALUE

.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------+-----------------------|
| O | R | Type  | Parent index          | Name index            |
|---------------------------------------------------------------|
| T | F | Count | Reserved                                      |
|---------------------------------------------------------------|
| Payload                                                       |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 11
T = Type of the stored values {4,5,6,14}
F = Display format {0,1,2}
Count = Count of stored values
Payload = array of size |count|

ARRAY_VALUE 區塊 Payload 的格式取決於「儲存值類型」T,解讀方式與「類型」欄位完全相同。針對 T ∊ {4, 5, 6}Payload 應為封裝在位元組邊界上的 64 位元數值。在 T ∊ {14} 中,Payload 應由 32 位元值組成,代表 24 位元值區塊的 24 位元索引,並隨位元組界線一起封裝。T在本例中,您只能使用 F = 0 這個平面陣列。

設為 F = 0 時,ARRAY_VALUE 應預設為執行個體化。在數字案例中,這應為相關聯的零值。在字串範例中,這是空白字串,以特殊值 0 表示。

指定「儲存值類型」 (或其中索引) 的確切「計數」項目會在偏移 16 的區塊中出現在區塊的位元組中。

「Display Format」欄位可用來影響陣列的顯示方式,並將其解讀如下:

列舉 說明
kFlat 0 顯示為有排序的平面陣列,不含其他格式。
kLinearHistogram 1 按照下方定義,將前兩個項目解讀為線性直方圖的 floorstep_size 參數。
kExponentialHistogram 2 按照以下定義,將前三個項目解讀為 floorinitial_stepstep_multiplier,以取得指數直方圖。

線性直方圖

陣列是線性直方圖,以內嵌方式儲存其參數,並包含溢位和欠位值區。

前兩個元素分別是參數 floorstep_size (定義如下)。

值區數量 (N) 為隱含的 Count - 4

其餘元素為值區:

2:     (-inf, floor),
3:     [floor, floor+step_size),
i+3:   [floor + step_size*i, floor + step_size*(i+1)),
...
N+3:   [floor+step_size*N, +inf)

指數直方圖

陣列是指數直方圖,以內嵌方式儲存參數,並包含溢位和反向溢位值區。

前三個元素分別為 floorinitial_stepstep_multiplier 參數 (定義如下)。

值區數量 (N) 會以隱含方式計算 - 5。

剩餘的值區如下:

3:     (-inf, floor),
4:     [floor, floor+initial_step),
i+4:   [floor + initial_step * step_multiplier^i, floor + initial_step * step_multiplier^(i+1))
N+4:   [floor + initial_step * step_multiplier^N, +inf)
.---------------------------------------------------------------.
|         |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|
|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|
|---+---+-------+-----------------------------------------------|
| O | R | Type  | Parent index          | Name index            |
|---------------------------------------------------------------|
| Content index     |                                       | F |
'---------------------------------------------------------------'

O = Order
R = Reserved, must be 0
Type = 12
Parent index = Index of the parent block
Name index = Index of the name of this value
Content index = Index of the content of this link (as a NAME node)
F = Disposition flags {0,1}

LINK_VALUE 區塊可讓節點支援位於獨立檢查檔案中的子項。

內容索引指定另一個 NAME 區塊,其內容是參照另一個檢查檔案的專屬 ID。讀取器應透過讀取目錄或其他介面取得 (Identifier, File) 對組,並且可以使用儲存的 ID 將樹狀結構組合在一起,來嘗試追蹤連結。

「Disposition Flags」會指示讀者如何拼接樹木,如下所示:

列舉 說明
kChildDisposition 0 連結檔案中的階層應為 LINK_VALUE 父項的子項。
kInlineDisposition 1 連結檔案中儲存的根層級子項和屬性應新增至 LINK_VALUE 的父項。

例如:

// root.inspect
root:
  int_value = 10
  child = LINK("other.inspect")

// other.inspect
root:
  test = "Hello World"
  next:
    value = 0


// kChildDisposition produces:
root:
  int_value = 10
  child:
    test = "Hello World"
    next:
      value = 0

// kInlineDisposition produces:
root:
  int_value = 10
  test = "Hello World"
  next:
    value = 0

如果節點之間的子項名稱與內嵌連結子項之間的值發生衝突,則會以讀取器定義優先順序。不過,大多數讀取工具都會發現,讓系統優先採用連結值,因此可能會覆寫原始值。

並行控制

寫入者必須使用全域版本計數器,以便讀取器可以在讀取之間偵測傳輸中的修改和修改,而不必與寫入者通訊。這支援單一寫入多重讀取並行。

這項策略適用於寫入作業在開始和結束寫入作業時,逐步增加全域產生計數器

這是一個簡單的策略,而且具有極大好處:從開始和結束寫入時,寫入器即可對緩衝區執行任意數量的作業,而不需考慮資料更新的不可部分性。

主要缺點是,由於經常更新寫入者,因此讀取作業可能會無限期延遲,但讀取者可實際採取因應措施。

讀取器演算法

讀取器會使用下列演算法取得檢查 VMO 的一致快照:

  1. 持續鎖定,直到版本編號平均 (無並行寫入) 為止。
  2. 複製整個 VMO 緩衝區,並且
  3. 檢查緩衝區的版本號碼是否等於步驟 1 的版本號碼。

只要版本號碼相符,用戶端就能讀取本機副本以建構共用狀態。如果版本號碼不相符,用戶端可能會重試整個程序。

寫入者鎖定演算法

寫入者可透過下列步驟,將檢查 VMO 鎖定以供修改:

atomically_increment(generation_counter, acquire_ordering);

這會將產生作業設為奇數,藉此針對並行讀取鎖定檔案。取得排序可確保在這項變更前,不會重新排序載入內容。

寫入者解鎖演算法

寫入者可以執行下列操作,解鎖檢查 VMO 之後的修改內容:

atomically_increment(generation_counter, release_ordering);

將產生版本設為新的偶數,即可解鎖檔案,以便並行讀取。版本排序可確保在產生計數更新之前,可以看到寫入檔案的內容。