4. SMBIOS 数据结构基础

SMBIOS 的核心是标准化的数据结构。每个结构都遵循统一的头部格式、类型编号规则、字符串区域机制以及表结束标记。理解这些基础是解析和开发 SMBIOS 功能的第一步。

4.1 Structure Header 格式

每个 SMBIOS 结构都以一个 4 字节的固定头部(Header)开始。这个头部是所有结构类型的共同起点,定义了结构的类型、长度和句柄。

偏移量 (Offset) 字节数 (Size) 字段名 描述
00h 1 Type 结构类型编号,标识该结构代表的信息类别(如 BIOS、系统信息、内存设备等)。
01h 1 Length 结构格式化区域的长度(以字节为单位)。该长度不包括字符串区域。
02h 2 Handle 结构的唯一标识符。用于在结构之间建立引用关系(例如,内存设备引用其所属的内存控制器)。
关键点: Length 字段仅包含格式化数据部分的长度。要访问紧随其后的字符串区域,必须跳过 Length 指定的字节数。

4.2 Type 编号规则

SMBIOS 规范为每种信息类别分配了一个唯一的 Type 编号。编号范围从 0 到 127 由规范定义,128 到 255 保留给 OEM 或特定实现使用。

以下是常见且重要的 Type 编号及其含义:

Type 名称 描述
0 BIOS Information BIOS 供应商、版本、起始地址、运行大小等。
1 System Information 制造商、产品名称、版本、序列号、UUID。
2 Baseboard (or Module) Information 主板制造商、产品、版本、序列号。
3 System Enclosure or Chassis 机箱类型、制造商、版本、序列号、资产标签。
4 Processor Information CPU 类型、家族、制造商、最大/当前速度、核心数。
7 Cache Information 缓存配置、大小、类型、支持的模式。
16 Physical Memory Array 物理内存阵列的位置、用途、最大容量、错误修正类型。
17 Memory Device 具体内存条的大小、类型、速度、制造商、序列号。
127 End-of-Table SMBIOS 结构表的终止标记。
开发提示: 在解析 SMBIOS 表时,通常通过 Type 字段来筛选感兴趣的结构。例如,获取 CPU 信息时,只处理 Type == 4 的结构。

4.3 String 区域机制

SMBIOS 结构中的文本信息(如制造商名称、版本号)并不直接存储在格式化区域中,而是通过 字符串区域(String Area) 来管理。这是一种节省空间且灵活的机制。

  • 存储方式: 格式化区域中的字符串字段存储的是一个 字符串索引号(从 1 开始)。
  • 字符串区域位置: 紧跟在格式化区域之后(即偏移量为 Length 的位置开始)。
  • 字符串格式: 由一系列以 null (0x00) 结尾的 ASCII 字符串组成。
  • 访问规则: 要获取某个字段的字符串值,先读取其索引号 n,然后在字符串区域中跳过前 n-1 个 null 结尾的字符串,第 n 个字符串即为所需内容。
  • 空字符串: 如果索引号为 0,则表示该字段没有可用的字符串信息。
常见错误: 字符串索引从 1 开始,而不是 0。索引 0 是一个特殊值,表示“无字符串”。直接使用索引 0 去字符串区域查找会导致解析错误。

4.4 End-of-Table 标记

SMBIOS 结构表不是无限延伸的。它以一个特殊的 End-of-Table 结构作为结束标记。这个结构的 Type 编号为 127。

End-of-Table 结构的特点:

  • Type: 固定为 127 (0x7F)。
  • Length: 固定为 4 字节(仅包含头部,没有格式化数据)。
  • Handle: 固定为 0xFFFF(表示无效句柄)。
  • 字符串区域: 不存在字符串区域。

在遍历 SMBIOS 结构表时,当遇到 Type == 127 的结构,应立即停止解析,因为其后不再有有效的结构数据。

4.5 结构布局总览

下面的 SVG 图展示了 SMBIOS 结构在内存中的完整布局,从头部到字符串区域,并以 End-of-Table 结构结束。

SMBIOS 结构内存布局 SMBIOS 结构内存布局 Structure Header (4 Bytes) Type (1B) Length (1B) Handle (2B) Formatted Area (Length - 4 Bytes) String Area (Variable Length, ends with double null) End-of-Table (Type 127)

4.6 字符串索引访问示例

下面的 SVG 图展示了如何通过索引号在字符串区域中定位具体的字符串。

字符串索引访问示例 字符串索引访问示例 字段值: 索引 2 String Area [1] "BIOS" [2] "v2.1.0" [3] "2024-01-01" 索引 2 指向这里

4.7 解析流程总结

解析一个 SMBIOS 结构的标准流程如下:

  1. 定位结构: 从 SMBIOS Entry Point 获取结构表起始地址。
  2. 读取头部: 读取前 4 字节,获取 Type、Length 和 Handle。
  3. 检查结束: 如果 Type == 127,则解析结束。
  4. 处理格式化数据: 根据 Type 对应的结构定义,解析从偏移量 4 开始的 Length - 4 字节数据。
  5. 解析字符串: 从偏移量 Length 开始,遍历字符串区域,根据索引号提取文本。
  6. 跳转到下一个结构: 跳过当前结构的格式化区域和整个字符串区域(直到遇到两个连续的 null 字节),然后重复步骤 2。
代码示例: 以下 C 语言代码片段展示了如何遍历 SMBIOS 结构并打印其 Type 和 Handle。
// 假设 smbios_table 指向结构表起始地址
uint8_t* current = smbios_table;
SMBIOS_HEADER* header;

while (1) {
    header = (SMBIOS_HEADER*)current;
    
    // 检查 End-of-Table
    if (header->type == 127) {
        break;
    }
    
    printf("Type: %d, Handle: %d\n", header->type, header->handle);
    
    // 计算字符串区域起始地址
    uint8_t* string_area = current + header->length;
    
    // 查找字符串区域结束位置(两个连续的 null 字节)
    uint8_t* end = string_area;
    while (!(*end == 0 && *(end+1) == 0)) {
        end++;
    }
    end += 2; // 跳过双 null 终止符
    
    // 移动到下一个结构
    current = end;
}