CPUID 指令检查特性的方式其实非常直接:通过“功能码(Leaf)”查询,并将结果以比特位(Bit)的形式返回。它的工作方式不涉及复杂的内部“探测”逻辑,而更像一个“问答机”——你告诉它你想知道哪方面的能力,它就把对应的答案通过寄存器告诉你。
指令执行:输入与输出
-
输入(提问):执行
CPUID前,需要将主功能码(EAX) 设置为特定值。对于更细分的特性,还需要设置子功能码(ECX)。 -
输出(答案):CPU 执行后,会将特性信息填入
EAX、EBX、ECX、EDX四个寄存器。每个寄存器的不同比特位(Bit)代表一项特性的支持状态(1 表示支持,0 表示不支持)。
功能码示例:基础特性查询(功能码 1)
这是最常用的查询。将 EAX 设为 1 后执行 CPUID,CPU 会在 ECX 和 EDX 中返回大量基础特性标志。下面是一些常见标志位及其含义:
EDX 寄存器(部分常用位):
| 位 | 特性 | 描述 |
|---|---|---|
| 0 | FPU | 是否有 x87 浮点单元 |
| 4 | TSC | 是否有时间戳计数器 (RDTSC) |
| 5 | MSR | 是否支持型号特定寄存器 (RDMSR/WRMSR) |
| 6 | PAE | 是否支持物理地址扩展 (CR4.PAE) |
| 15 | CMOV | 是否支持条件传送指令 |
| 23 | MMX | 是否支持 MMX 指令集 |
| 25 | SSE | 是否支持 SSE 指令集 |
| 26 | SSE2 | 是否支持 SSE2 指令集 |
ECX 寄存器(部分常用位):
| 位 | 特性 | 描述 |
|---|---|---|
| 0 | SSE3 | 是否支持 SSE3 指令集 |
| 1 | PCLMULQDQ | 是否支持 PCLMULQDQ 指令 |
| 9 | SSSE3 | 是否支持 SSSE3 指令集 |
| 19 | SSE4.1 | 是否支持 SSE4.1 指令集 |
| 20 | SSE4.2 | 是否支持 SSE4.2 指令集 |
| 22 | MOVBE | 是否支持 MOVBE 指令 |
| 23 | POPCNT | 是否支持 POPCNT 指令 |
| 5 | VMX | 是否支持 Intel VT-x 硬件虚拟化 (CR4.VMXE) |
| 12 | XSAVE | 是否支持 XSAVE 指令集 |
扩展特性查询(功能码 7, 子功能码 0)
一些更新、更高级的特性需要通过 EAX=7, ECX=0 来查询。结果同样通过 EBX、ECX、EDX 返回。
EBX 寄存器(部分位):
| 位 | 特性 | 描述 |
|---|---|---|
| 0 | FSGSBASE | 是否支持 RDFSBASE/WRFSBASE 指令 (CR4.FSGSBASE) |
| 3 | BMI1 | 位操作指令集扩展 |
| 4 | HLE | 硬件锁省略 (Transactional Synchronization) |
| 5 | AVX2 | 高级向量扩展 2 |
| 6 | BMI2 | 位操作指令集扩展 2 |
| 7 | SMEP | 是否支持监管模式执行保护 (CR4.SMEP) |
| 8 | SMAP | 是否支持监管模式访问保护 (CR4.SMAP) |
| 9 | PKU | 内存保护密钥 (Memory Protection Keys) |
| 10 | UMIP | 是否支持用户模式指令预防 (CR4.UMIP) |
ECX 寄存器(部分位):
| 位 | 特性 | 描述 |
|---|---|---|
| 0 | PREFETCHWT1 | PREFETCHWT1 指令支持 |
| 1 | AVX512VBMI | AVX-512 VBMI 指令集 |
在 GDB 中模拟 CPUID 查询
在 GDB 中,可以手动模拟 CPU 的行为,查看某个特定 CPU 会返回什么 CPUID 信息(这通常用于调试虚拟机或分析二进制兼容性问题)。
# 进入 GDB 后,我们模拟一个功能码 1 的查询 (gdb) set $eax = 1 (gdb) cpuid # 执行后,GDB 会模拟 CPU,将结果填充到 eax, ebx, ecx, edx (gdb) info registers eax ebx ecx edx
更常见的是使用 GDB 的 disassemble 配合 x/i 查看已编译好的 CPUID 指令,然后手动计算它的结果。
进阶:多个“子功能码”的概念
有时,即使 EAX 相同,通过设置不同的 ECX(子功能码)也能查询到完全不同的信息。这被称为子叶(Sub-leaf)。
示例:查询扩展拓扑信息 (Leaf 0x1F)
; 查询 CPU 拓扑的层级 0 (ECX=0) mov eax, 0x1F mov ecx, 0 ; 子功能码:SMT 层级 cpuid ; EAX 返回该层级的位宽等信息 ; 查询 CPU 拓扑的层级 1 (ECX=1) mov eax, 0x1F mov ecx, 1 ; 子功能码:Core 层级 cpuid
这种设计让 CPUID 可以返回非常精细和层次化的硬件拓扑信息,对 NUMA 感知和 KVM 虚拟 CPU 拓扑模拟至关重要。
Linux 内核中的封装
在 Linux 内核中,你不会看到直接使用内联汇编来反复执行 CPUID。内核通过一个引导时(Boot-time)的一次性探测,将结果缓存到全局变量中,后续的检查只是一个简单的比特位测试。
// Linux 内核代码简化示意
#include <asm/cpufeature.h>
// 检查 CPU 是否支持 SMAP 特性
if (cpu_has(c, X86_FEATURE_SMAP)) {
// 设置 CR4.SMAP 位
cr4_set_bits(X86_CR4_SMAP);
}
cpu_has() 宏背后是一个对 boot_cpu_data.x86_capability 数组的快速查询,这个数组在系统启动时已经通过 CPUID 填充好了,因此运行时检查的开销几乎为零。
总结
CPUID 检查特性就是这样一个机制:
-
查询:设置
EAX(和ECX)作为功能码。 -
获取:CPU 将特性标志以比特位形式填回
EAX、EBX、ECX、EDX。 -
解析:软件(如操作系统或 VMM)读取这些位,决定如何配置自己的功能。
它简单、高效,是 CPU 与系统软件之间最基础、最核心的通信协议之一。

359

被折叠的 条评论
为什么被折叠?



