本文件說明元件檢查檔案格式 (檢查格式)。
以檢查格式格式化的檔案稱為「檢查檔案」,此檔案通常具有 .inspect
副檔名。
瞭解如何變更格式。請參閱「擴充檢查檔案格式」一節。
簡介
元件檢查可讓元件在執行階段公開有關其狀態的結構化階層資訊。
元件會使用檢查格式代管對應的虛擬記憶體物件 (VMO),以公開包含此內部狀態的檢查階層。
檢查階層包含包含類型屬性的巢狀 Nodes。
目標
本文件說明的「檢查格式」具有下列目標:
資料流動的低負載變化
「檢查檔案格式」可讓你直接變更資料。舉例來說,整數遞增的負擔為約 2 個原子遞增量。
支援非靜態階層
您可以在執行階段修改檢查檔案中儲存的階層。您隨時可以在階層中新增或移除子項。透過這種方式,階層可以密切代表元件工作集中的物件階層。
單一寫入者,在沒有明確同步處理的情況下,有多個閱讀器並行
與寫入者同時運作的讀取器會對應 VMO,並嘗試拍攝資料快照。寫入者表示其位於關鍵區段,但不需與讀取器明確同步處理的產生計數器。讀取器會使用產生計數器,判斷 VMO 快照的一致且可以安全讀取的時間。
在元件終止後,您可能仍可查看資料
即使寫入元件終止,讀取器仍可能保有含有檢查資料的 VMO 控制代碼。
術語
本節定義本文件使用的常見術語。
- 檢查檔案 - 使用本文件所說明格式的受限位元組序列。
- 檢查 VMO - 儲存在虛擬記憶體物件 (VMO) 中的檢查檔案。
- 區塊 - 檢查檔案的大小部分。區塊有索引和訂單。
- 索引:特定區塊的專屬 ID。
byte_offset = index * 16
- 順序 - 指定區塊的大小,這個區塊的大小與最小大小略有不同。
size_in_bytes = 16 << order
。依類別的大小 (二的力量) 將區塊拆分為多個類別。 - 節點 - 階層中其他值可嵌入的已命名值。只有階層中的節點可以成為父項。
- 屬性 - 包含輸入資料 (例如字串、整數等) 的已命名值。
- 階層 - 從單一「根」節點遞減排列的節點樹狀結構,每個節點可能包含屬性。檢查檔案包含單一階層。
這份文件中「必須」、「應/建議」和「可能」關鍵字符合 RFC 2119 的定義。
所有位元場圖皆以小端序儲存。
版本
目前版本:2
- 版本 2 允許將值的名稱設為 NAME 或 STRING_REFERENCE。
模塊
檢查檔案已分割成多個 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:這個類型會儲存可能無法納入單一區塊的長二進位資料。
名稱 - 這個類型會儲存符合單一區塊的二進位資料,並且通常用於儲存值的名稱。
參考資料 - 這個類型保留一個標準值,可供其他區塊參照。
每種類型會以不同的方式解讀酬載,如下所示:
- 免費
- 已預訂
- 標頭
- 常見的「值」欄位
- NODE_VALUE
- INT_VALUE
- UINT_VALUE
- DOUBLE_VALUE
- BUFFER_VALUE
- 優異
- 姓名
- TOMBSTONE
- ARRAY_VALUE
- 連結
- BOOL_VALUE
- 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 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
的新特殊類型。只有在 TOMBSTONE
的 Refcount
為 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 | 按照下方定義,將前兩個項目解讀為線性直方圖的 floor 和 step_size 參數。 |
kExponentialHistogram | 2 | 按照以下定義,將前三個項目解讀為 floor 、initial_step 和 step_multiplier ,以取得指數直方圖。 |
線性直方圖
陣列是線性直方圖,以內嵌方式儲存其參數,並包含溢位和欠位值區。
前兩個元素分別是參數 floor
和 step_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)
指數直方圖
陣列是指數直方圖,以內嵌方式儲存參數,並包含溢位和反向溢位值區。
前三個元素分別為 floor
、initial_step
和 step_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)
LINK_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 |
|---------------------------------------------------------------|
| 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 的一致快照:
- 持續鎖定,直到版本編號平均 (無並行寫入) 為止。
- 複製整個 VMO 緩衝區,並且
- 檢查緩衝區的版本號碼是否等於步驟 1 的版本號碼。
只要版本號碼相符,用戶端就能讀取本機副本以建構共用狀態。如果版本號碼不相符,用戶端可能會重試整個程序。
寫入者鎖定演算法
寫入者可透過下列步驟,將檢查 VMO 鎖定以供修改:
atomically_increment(generation_counter, acquire_ordering);
這會將產生作業設為奇數,藉此針對並行讀取鎖定檔案。取得排序可確保在這項變更前,不會重新排序載入內容。
寫入者解鎖演算法
寫入者可以執行下列操作,解鎖檢查 VMO 之後的修改內容:
atomically_increment(generation_counter, release_ordering);
將產生版本設為新的偶數,即可解鎖檔案,以便並行讀取。版本排序可確保在產生計數更新之前,可以看到寫入檔案的內容。