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 位)。 |
_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 结构体由三部分组成:
- 格式化区域(Formatted Area):由通用头部和类型特定的字段组成,长度由
Length字段指定。 - 字符串区域(String Area):紧跟在格式化区域之后,包含以
\0结尾的字符串。字符串区域以两个连续的\0字节结束。 - 终止符(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) 结构体的句柄。句柄值在结构体表中是唯一的,但系统重启后可能会发生变化。
2.4 结构体表遍历流程
下图展示了从 Entry Point 到遍历所有结构体的完整流程:
2.5 结构体表在内存中的布局示例
下图展示了结构体表在物理内存中的实际排列方式,以及如何通过 Entry Point 找到它们:
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和字符串区域的实际长度动态计算下一个结构体的偏移。