检查 VMO 文件格式

本文档介绍了组件检查文件格式(检查格式)。

使用“检查格式”设置格式的文件称为“检查文件”, (通常具有 .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

方块

检查文件会拆分为多个 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 - 该类型包含单个规范值,其他块可以引用该值。

每种类型对载荷的解释都不同,如下所示:

免费

.---------------------------------------------------------------.
|         |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_VALUEPayload 的格式取决于存储值类型 TType 字段完全相同。其中 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 将前两个条目解读为线性直方图的 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) 默认为 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)
.---------------------------------------------------------------.
|         |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:

  1. 自旋,直到版本号为偶数(无并发写入),
  2. 复制整个 VMO 缓冲区,以及
  3. 检查缓冲区中的版本号是否与版本号相同 第 1 步中得到的编号。

只要版本号匹配,客户端就可以读取其本地 来构造共享状态。 如果版本号不匹配,客户端可能会重试整个 过程。

写入者锁定算法

写入者通过执行以下操作来锁定 Inspect VMO 以进行修改:

atomically_increment(generation_counter, acquire_ordering);

这通过将世代设置为 奇数。获取排序可确保加载操作之前不会被重新排序 更改。

写入者解锁算法

写入者通过执行 以下:

atomically_increment(generation_counter, release_ordering);

通过将世代设置为 ( 一个新的偶数。版本排序可确保对文件的写入 。