本文档介绍了组件检查文件格式(检查格式)。
使用“检查格式”设置格式的文件称为“检查文件”,
(通常具有 .inspect
文件扩展名)
有关如何更改格式的信息。请参阅 扩展 Inspect 文件格式
概览
通过组件检查,组件能够 公开有关其在运行时状态的结构化分层信息。
组件使用检查工具托管映射的虚拟内存对象 (VMO) 此格式可公开包含此内部状态的检查层次结构。
检查层次结构由包含类型化 Properties 的嵌套 Nodes 组成。
目标
本文档中介绍的检查格式具有以下目标:
低开销数据更改
使用“检查文件格式”可就地更改数据。例如, 递增整数的开销约为 2 个原子增量。
支持非静态层次结构
存储在检查文件中的层次结构可通过 运行时。您可以在层次结构中的任一位置添加或移除子节点 。这样,层次结构就可以准确地代表 对象。
单个写入者、多个读取器并发(无需显式同步)
与写入方同时运行的读取器映射 VMO 并尝试 截取数据的快照。作者表明视频处于关键部分 通过无需显式同步的世代计数器 和读者互动。读者使用生成计数器来确定 VMO 快照保持一致,可以安全地读取。
在组件终止后数据可能仍然可用
读取器甚至可能会维护包含检查数据的 VMO 的句柄 在写入组件终止后运行。
术语
本部分定义了本文档中常用的术语。
- 检查文件 - 采用本文档中所述的格式的有界字节序列。
- 检查 VMO - 存储在虚拟内存对象 (VMO) 中的检查文件。
- Block - 检查文件一定大小的部分。块有一个索引和一个顺序。
- 索引 - 特定块的唯一标识符。
byte_offset = index * 16
- 顺序 - 块的大小,以相对于最小值的位移而来表示
。
size_in_bytes = 16 << order
。将区块拆分为 大小来决定类别。 - 节点 - 层次结构中的指定值,在其下可以有其他值 嵌套。在层次结构中,只有节点可以成为父级。
- Property - 包含类型化数据的命名值(例如 String、 整数等)。
- 层次结构 - 从单个“根”开始的节点树这个节点 每个都可以包含属性。检查文件包含一个 单个层次结构
本文档使用了 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 位,因此检查文件最大可为 256MiB。
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
,用于指定其大小。
如果最大块大小为 2048 个字节,则最多有 8 个块
订单 (NUM_ORDERS
),编号为 0...7,对应于尺寸块
分别为 16、32、64、128、256、512、1024 和 2048 个字节。
每个块还具有类型,用于确定其余的 块中的字节将被解释。
好友分配
这种方块布局可实现使用Buddy 来高效分配方块 分配。推荐的好友分配比例 策略,但这并不是使用 Inspect Format 的必要条件。
类型
所有支持的类型均在 //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 |
参考文档 |
内部 - 这些类型用于实现块分配,并且 则必须被读者忽略。
Header - 此类型可让读者检测检查文件和原因 快照一致性此块必须位于索引 0 处。
值 - 这些类型直接显示在层次结构中。值必须具有名称 和一个父项(必须为
NODE_VALUE
)。Extent - 此类型用于存储可能不适合放在单个块中的长二进制数据。
名称 - 此类型用于存储适合单个块的二进制数据, 它通常用于存储值的名称。
Reference - 该类型包含单个规范值,其他块可以引用该值。
每种类型对载荷的解释都不同,如下所示:
- 免费
- 已预订
- 标题
- 通用 VALUE 字段
- 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 中的未使用位。
用于任何用途的屏蔽设置Writer 实现必须将所有其他未使用
位设为 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
块支持可选的引用计数和 tombstoning
以确保高效的实现,如下所示:
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
块都包含内嵌在
块的第二个 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
。检索 buffer_value 的字节数据
按顺序读取每个 EXTENT
,直到读取 Total Length 字节。
姓名
.---------------------------------------------------------------.
| |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
块为对象和值提供人类可读的标识符。他们
由完全适合指定块的 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
,
与 Type 字段完全相同。其中 T ∊ {4, 5, 6}
,Payload
应为 64 位
按字节边界打包的数值。其中,T ∊ {14}
,Payload
应由
32 位值,表示 T
类型的块的 24 位索引,沿着字节打包在一起
边界。在本例中,仅允许使用平面数组 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) 默认为 Count - 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
块,其内容
是引用另一个检查文件的唯一标识符。读者
获取一对 (Identifier, File)
对(通过
目录读取或其他接口),它们可能会尝试遵循
建立链接。
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 步中得到的编号。
只要版本号匹配,客户端就可以读取其本地 来构造共享状态。 如果版本号不匹配,客户端可能会重试整个 过程。
写入者锁定算法
写入者通过执行以下操作来锁定 Inspect VMO 以进行修改:
atomically_increment(generation_counter, acquire_ordering);
这通过将世代设置为 奇数。获取排序可确保加载操作之前不会被重新排序 更改。
写入者解锁算法
写入者通过执行 以下:
atomically_increment(generation_counter, release_ordering);
通过将世代设置为 ( 一个新的偶数。版本排序可确保对文件的写入 。