2. SMBIOS 规范结构:Entry Point、结构体表、类型与句柄

SMBIOS 规范的核心在于其标准化的数据布局。系统固件(BIOS/UEFI)在启动时,会在内存中构建一张只读的结构体表(Structure Table),并通过一个固定的入口点(Entry Point)让操作系统或管理软件找到这张表。本章将深入解析这三个核心概念:Entry Point 结构、结构体表的组织方式,以及结构体类型与句柄的映射规则。

2.1 SMBIOS Entry Point 结构

Entry Point 是访问 SMBIOS 信息的“大门”。它位于系统内存的特定区域(通常在 BIOS 数据区或扩展 BIOS 数据区),用于定位结构体表的物理基地址和长度。SMBIOS 规范定义了两种 Entry Point 结构:SMBIOS 2.1 (含) 之前的 32 位入口点和SMBIOS 3.0 (含) 之后的 64 位入口点。

2.1.1 32 位 Entry Point (SMBIOS 2.1~2.8)

该结构固定为 31 字节,以字符串 _SM_ 为签名。操作系统通过扫描内存区域(通常为 0xF0000 ~ 0xFFFFF)找到该签名,从而获取结构体表的物理地址。

偏移量 长度 (字节) 字段名 描述
0x00 4 Anchor String 固定为 _SM_,用于标识入口点。
0x04 1 Checksum 从偏移 0x00 到 0x1E 的累加和校验,结果应为 0。
0x05 1 Entry Point Length 入口点结构长度,固定为 0x1F (31)。
0x06 1 Major Version SMBIOS 主版本号,例如 2。
0x07 1 Minor Version SMBIOS 次版本号,例如 8。
0x08 2 Maximum Structure Size 结构体表中最大的结构体大小(字节)。
0x0A 1 Entry Point Revision 入口点修订版本,通常为 0。
0x0B 5 Formatted Area 保留区域,通常为全 0。
0x10 4 Intermediate Anchor String 固定为 _DMI_。
0x14 1 Intermediate Checksum 从偏移 0x10 到 0x1E 的累加和校验。
0x15 2 Structure Table Length 整个结构体表的总长度(字节)。
0x17 4 Structure Table Address 结构体表的物理基地址(32 位)。
0x1B 2 Number of SMBIOS Structures 结构体表中包含的结构体总数。
0x1D 1 SMBIOS BCD Revision BCD 格式的版本号,例如 0x28 表示 2.8。

2.1.2 64 位 Entry Point (SMBIOS 3.0+)

为了支持 64 位地址空间和更大的结构体表,SMBIOS 3.0 引入了新的入口点。它以 _SM3_ 为签名,结构长度可变,但通常为 24 字节。

偏移量 长度 (字节) 字段名 描述
0x00 5 Anchor String 固定为 _SM3_。
0x05 1 Checksum 整个入口点结构的累加和校验。
0x06 1 Entry Point Length 入口点结构长度,通常为 24。
0x07 1 Major Version SMBIOS 主版本号。
0x08 1 Minor Version SMBIOS 次版本号。
0x09 1 Doc Rev 文档修订版本。
0x0A 1 Entry Point Revision 入口点修订版本,通常为 1。
0x0B 1 Reserved 保留字节。
0x0C 4 Structure Table Maximum Size 结构体表的最大长度(字节)。
0x10 8 Structure Table Address 结构体表的物理基地址(64 位)。
开发提示: 在解析 SMBIOS 时,应优先扫描 _SM3_ 签名。如果未找到,再回退到 _SM_ 签名。这样可以确保兼容从 SMBIOS 2.1 到 3.x 的所有系统。

2.2 结构体表(Structure Table)

结构体表是 SMBIOS 信息的核心载体,它由一系列连续排列的结构体(Structures)组成,每个结构体描述系统的一个特定组件(如 BIOS、CPU、内存模块)。结构体表以结构体表终止符(End-of-Table)作为结尾。

2.2.1 结构体的通用头部

每个 SMBIOS 结构体都以一个 4 字节的通用头部开始:

偏移量 长度 (字节) 字段名 描述
0x00 1 Type 结构体类型编号,例如 0 表示 BIOS 信息。
0x01 1 Length 结构体格式化区域的长度(字节),不包括字符串部分。
0x02 2 Handle 结构体的唯一标识符,用于交叉引用。

2.2.2 结构体的组成

一个完整的 SMBIOS 结构体由三部分组成:

  1. 格式化区域(Formatted Area):由通用头部和类型特定的字段组成,长度由 Length 字段指定。
  2. 字符串区域(String Area):紧跟在格式化区域之后,包含以 \0 结尾的字符串。字符串区域以两个连续的 \0 字节结束。
  3. 终止符(Terminator):整个结构体表以一个特殊的结构体(Type 127)结束,该结构体没有字符串区域。
关键规则: 结构体之间的间距不是固定的。解析器必须通过 Length 字段加上字符串区域的实际长度来计算下一个结构体的起始地址。字符串区域从 Length 偏移处开始,直到遇到两个连续的 \0 字节。

2.3 结构体类型与句柄

2.3.1 结构体类型(Type)

SMBIOS 规范为每种系统组件分配了一个唯一的类型编号。类型编号决定了结构体格式化区域的字段布局。以下是常见的类型:

类型编号 名称 描述
0 BIOS Information BIOS 供应商、版本、起始地址等。
1 System Information 制造商、产品名称、序列号等。
2 Baseboard Information 主板制造商、型号、版本等。
3 System Enclosure 机箱类型、锁扣状态等。
4 Processor Information CPU 类型、频率、核心数、缓存等。
7 Cache Information 缓存大小、类型、支持的操作模式等。
16 Physical Memory Array 物理内存阵列的位置、用途、错误修正类型等。
17 Memory Device 具体内存条的大小、速度、制造商、序列号等。
19 Memory Array Mapped Address 内存阵列的物理地址映射范围。
41 Onboard Devices Extended Information 板载设备(如网卡、声卡)的详细信息。
127 End-of-Table 结构体表终止符。

2.3.2 句柄(Handle)

句柄是一个 16 位的唯一标识符,用于在结构体之间建立关联。例如,一个 Memory Device (Type 17) 结构体会通过其 Memory Array Handle 字段指向一个 Physical Memory Array (Type 16) 结构体的句柄。句柄值在结构体表中是唯一的,但系统重启后可能会发生变化。

句柄使用注意事项: 句柄仅在当前系统启动周期内有效。不要将句柄值持久化到磁盘或用于跨启动周期的设备识别。如果需要持久标识,应使用结构体中的其他唯一字段(如序列号或 UUID)。

2.4 结构体表遍历流程

下图展示了从 Entry Point 到遍历所有结构体的完整流程:

SMBIOS 结构体表遍历流程 SMBIOS 结构体表遍历流程 1. 扫描内存,定位 Entry Point 2. 解析 Entry Point,获取表地址 3. 从表基地址开始遍历结构体 4. 解析 Type / Length / Handle 5. 判断 Type == 127 ? 是 遍历结束 否 说明:当 Type 不为 127 时,根据 Length 和字符串区域计算下一个结构体偏移,继续遍历。

2.5 结构体表在内存中的布局示例

下图展示了结构体表在物理内存中的实际排列方式,以及如何通过 Entry Point 找到它们:

SMBIOS 结构体表内存布局 SMBIOS 结构体表内存布局示例 Entry Point (0xF0000 区域) 指向 结构体表 (Structure Table) 物理基地址: 0x7FE00000 Type 0 BIOS Info Handle: 0x0000 Type 1 System Info Handle: 0x0001 Type 4 Processor Handle: 0x0002 Type 17 Memory Device Handle: 0x1000 Type 127 (End-of-Table) — 结构体表结束 说明:每个结构体之间通过 Length + 字符串区域计算偏移,连续排列,直到遇到 Type 127。

2.6 开发案例:解析 Entry Point 并遍历结构体

以下代码展示了如何在 UEFI 环境下,通过扫描 _SM3_ 签名来定位 Entry Point,并遍历所有结构体:

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>

// SMBIOS 3.0 64位 Entry Point 结构
typedef struct {
  CHAR8   AnchorString[5];    // "_SM3_"
  UINT8   Checksum;
  UINT8   EntryPointLength;
  UINT8   MajorVersion;
  UINT8   MinorVersion;
  UINT8   DocRev;
  UINT8   EntryPointRevision;
  UINT8   Reserved;
  UINT32  StructureTableMaxSize;
  UINT64  StructureTableAddress;
} SMBIOS_ENTRY_POINT_3;

// SMBIOS 结构体通用头部
typedef struct {
  UINT8   Type;
  UINT8   Length;
  UINT16  Handle;
} SMBIOS_STRUCTURE_HEADER;

EFI_STATUS
EnumerateSmbiosStructures (
  VOID
  )
{
  EFI_STATUS          Status;
  UINT64              SmbiosTableAddress;
  SMBIOS_ENTRY_POINT_3 *EntryPoint;
  SMBIOS_STRUCTURE_HEADER *StructHeader;

  // 1. 扫描内存区域查找 "_SM3_" 签名
  EntryPoint = (SMBIOS_ENTRY_POINT_3 *) (UINTN) 0xF0000;
  while ((UINTN) EntryPoint < 0x100000) {
    if (AsciiStrCmp (EntryPoint->AnchorString, "_SM3_") == 0) {
      break;
    }
    EntryPoint = (SMBIOS_ENTRY_POINT_3 *) ((UINT8 *) EntryPoint + 0x10);
  }

  if ((UINTN) EntryPoint >= 0x100000) {
    Print (L"SMBIOS 3.0 Entry Point not found.\n");
    return EFI_NOT_FOUND;
  }

  // 2. 获取结构体表基地址
  SmbiosTableAddress = EntryPoint->StructureTableAddress;
  Print (L"SMBIOS Table Address: 0x%lx\n", SmbiosTableAddress);

  // 3. 遍历结构体表
  StructHeader = (SMBIOS_STRUCTURE_HEADER *) (UINTN) SmbiosTableAddress;
  while (StructHeader->Type != 127) {
    Print (L"Type: %d, Length: %d, Handle: 0x%04x\n",
           StructHeader->Type,
           StructHeader->Length,
           StructHeader->Handle);

    // 计算下一个结构体地址
    // 跳过格式化区域和字符串区域
    UINT8 *StringArea = (UINT8 *) StructHeader + StructHeader->Length;
    UINTN StringLength = 0;
    while (StringArea[StringLength] != 0 || StringArea[StringLength + 1] != 0) {
      StringLength++;
    }
    StringLength += 2; // 两个连续的 \0

    StructHeader = (SMBIOS_STRUCTURE_HEADER *) ((UINT8 *) StructHeader +
                                                StructHeader->Length +
                                                StringLength);
  }

  Print (L"End of SMBIOS table reached.\n");
  return EFI_SUCCESS;
}
代码要点:
  • 扫描步长通常为 16 字节,因为 Entry Point 结构在内存中对齐到 16 字节边界。
  • 字符串区域的结束标志是两个连续的 \0 字节,计算长度时必须包含这两个字节。
  • Type 127 结构体没有字符串区域,其 Length 字段固定为 4。

2.7 本章小结

  • Entry Point 是访问 SMBIOS 的入口,32 位版本以 _SM_ 签名,64 位版本以 _SM3_ 签名。
  • 结构体表 由一系列连续的结构体组成,每个结构体包含格式化区域和字符串区域。
  • 结构体类型 决定了格式化区域的字段布局,句柄 用于结构体之间的交叉引用。
  • 遍历结构体表时,必须通过 Length 和字符串区域的实际长度动态计算下一个结构体的偏移。