CPUID

CPUID指令是一条x86架构中的的扩展指令（此处的扩展指相对i80386），其操作码助记符缩写于“CPU识别”（CPU Identification），其作用是返回特定的CPU信息，使得软件可以在运行时检测CPU的硬件特性，以便于识别并决定运行哪些代码。首批支持CPUID指令的处理器是1993年发布的奔腾（i586）和486SL。

通过使用CPUID，软件可以确定处理器的类型和特性支持（例如MMX/SSE）。CPUID操作码为0Fh、A2h。在调用CPUID前EAX寄存器内的值作为其输入参数,Intel64指令集手册中称其为“叶”（Leaf)，AMD64指令集手册中称“功能码”（Function Number），执行CPUID后，返回值将被写入EAX，EBX，ECX和EDX寄存器内。此外，在使用一些特殊的输入参数时，必须同时于ECX内写入第二个输入参数，否则无法得到有意义的返回值。第二个参数也被称为“子叶”（Sub Leaf）或“子功能码”（Sub Function）。

 历史 

 CPUID前判断处理器型号的方法 
在CPUID指令被引入前，若要在运行时确定CPU的型号，需要编写晦涩的汇编语言代码以判断某些指令被执行后处理器的行为。

1990年以前，对于如何判断CPU的型号没有官方说明与实现方法，但软硬件工程师们总结了一套利用PUSHF/POPF指令检测CPU行为以判断其架构的方法。此方法在广泛应用的同时引起了巨大争议，反对者认为其并非万无一失，在某些需要高可靠性的软件平台上若处理器型号判断错误，则有可能造成巨大损失，但英特尔在1990年发布了《i486微处理器程序员参考手册》（i486 Microprocessor Programmer's Reference Manual Intel Corp. 1990），其中在手册的第22.10节中使用了上述方法判断CPU代数，并且英特尔声称“代码序列已经过 Intel 验证，可以检测 CPUID、数学协处理器功能，并进行相应的初始化。任何其他方法都可能在未来的处理器中产生不可预测的结果”。此后该方法被应用于大多数操作系统内核中。

以下所介绍之方法为英特尔手册中的官方算法。

该算法的核心是利用PUSHF/POPF指令将标志寄存器中的特定位设为1或0，并观察处理器随后的行为，每一代处理器的具体行为均有所不同。

 判断8086/80286 
8086/8088系列处理器在清除标志寄存器的12-15位后，无论向其中写入何值，处理器将始终把它们设为1，其检测方式如下        pushf                   ; 将标志寄存器内容压栈
        pop     ax              ; 将标志寄存器内容送入AX
        mov     cx, ax          ; 将标志寄存器内容送入CX备份
        and     ax, 0fffh       ; 将AX的12-15位置零
        push    ax              ; 将标志寄存器的新值压栈
        popf                    ; 送入FLAG
        pushf                   ; 获取弹栈后标志寄存器的内容
        pop     ax              ; 存入AX
        and     ax, 0f000h      ; 如果12-15位被处理器置1则为8086/8088
        cmp     ax, 0f000h     
        je      end_cpu_type_8688    ; 跳转至对应代码对于80286系列处理器，在实模式下这些位始终被清零，在保护模式下，其用于I/O特权级别和嵌套任务（NT），其检测方法如下，代码接上述代码执行        or      cx, 0f000h      ; 将12-15位置1
        push    cx              ; 压栈
        popf                    ; 将栈内容弹入标志寄存器
        pushf                   ; 获取标志寄存器并存入AX
        pop     ax             
        and     ax, 0f000h      ; 若12-15位位0则为286
        jz      end_cpu_type_286

 判断80386/80486 
检测386/486处理器的原理与上述代码相同，但检测的位有所区别，486处理器中引入了一个AC标志位，并将把它置1，其对于386而言是无效的，故386处理器将其置零，其实现如下        pushfd；EFLAGS压栈
        pop eax ；获取原始 EFLAGS 
        mov ecx, eax ；保存原来的 EFLAGS 
        xor eax, 40000h ; 翻转 EFLAGS 中的 AC 位
        push eax ；将新的 EFLAGS 压栈
        popfd 中；替换当前的 EFLAGS 值
        pushfd；获取新的 EFLAGS 
        pop eax ；将新的 EFLAGS 存储在 EAX 
        xor eax, ecx 中；无法反转AC则为386
        jz end_cpu_type_386 ；如果 80386 处理器此外，80386处理器的EDX寄存器在处理器被硬件复位后将被初始化为处理器的修订版本号。

 检测80486或奔腾（i586） 
奔腾处理器支持使用CPUID指令，因此其标志位中设置了一个被称为ID的特殊标志，若该标志存在，则对应的处理器支持CPUID，检测实现如下        mov eax, ecx ; 获取原始 EFLAGS 
        xor eax, 200000h ；翻转 EFLAGS 中的 ID 位
        push eax ；将新的 EFLAGS 值保存在堆栈
        popfd 中；替换当前的 EFLAGS 值
        pushfd ；获取新的 EFLAGS 
        pop eax ；将新的 EFLAGS 存储在 EAX 
        xor eax, ecx 中；可切换则为奔腾或486SL
        jne end_cpu_type_586

 关于上述方法的注意事项 
该算法无法检测80186处理器（虽然其并未被广泛使用），80186/88处理器包80286中包含的大部分新指令和异常，包括PUSHA/POPA、PUSH、SHL和无效操作码异常。80186/88中唯一没有实现的是专门用于保护模式的指令和异常。未能检测到此处理器可能会禁止使用某些可以利用这些新指令和异常的软件。

同时，由于PUSHF/POPF是Ring0指令，故该算法只能在实模式中执行，保护模式操作系统中运行的应用程序无法使用这个方法，除非处理器以及操作系统支持虚拟模式扩展（VME）。

以上算法仅供参考与学习，在使用非英特尔处理器，完全软件虚拟化（Full emulation）的虚拟机或其他平台上，该算法很有可能无法正确判断处理器支持的指令集。考虑到奔腾已经是近30年前的处理器，除非有必要在古老的计算机上检测CPU，否则不应该再使用这种算法，而是直接调用CPUID指令。

对于非x86架构的CPU，仍然需要精心设计的代码判断其差别（例如使用MOVE的特权要求判断摩托罗拉68000和68010），但大多数架构都要求具体实现者提供相应的寄存器以提供区分和判断处理器（例如ARM64的ID_AA64寄存器）。
 调用CPUID 
CPUID 指令的操作码是 0F A2。CPUID没有显式的操作数，其隐式使用EAX或EAX与ECX中的值作为参数，以确定要获取的信息。在英特尔指令集手册中，CPUID的输入参数（EAX），被称为“叶”（Leaf），AMD称其为“功能码”（Function Number），执行CPUID后，返回值将被写入EAX，EBX，ECX和EDX寄存器内。此外，在使用一些特殊的输入参数时，必须同时于ECX内写入第二个输入参数，否则无法得到有意义的返回值。第二个参数也被称为“子叶”（Sub Leaf）或“子功能码”（Sub Function）。

注意，当且仅当MSR寄存器IA32_MISC_ENABLE.BOOT_NT4 的第22位为0时，EAX大于3的输入才是有意义的。当且仅当该位被设为1时，Windows NT 4.0 SP6以前的操作系统才能正常启动。

截止到2022年，对于一些常见的处理器架构，最大的有效的基础输入是0x20(GoldenCove），0x10（Zen4);最大有效的扩展输入是0x80000008（GoldenCove），0x80000028（Zen4）。

 EAX=0: 最高基础输入参数与制造商ID 
输入参数位0时，CPUID返回对当前处理器有意义的（即被当前架构所实现的）最大输入参数以及ASCII编码的制造商ID（Manufacturer ID）。

最大输入参数被写入EAX寄存器中，对于英特尔和AMD处理器，已知的返回值如下表所示：
+英特尔处理器架构所实现的最高CPUID输入架构基础输入扩展输入80486及以前不支持CPUID指令486和P50x01不支持P60x02不支持P6（奔腾III）0x03不支持Netbrust0x020x8000 0004Netbrust（64位）0x050x8000 0008Core0x0A0x8000 0008Core（Penryn）0x0D0x8000 0008Bonnell0x0A0x8000 0008Nehalem0x0B0x8000 0008Sandy Bridge0x0D0x8000 0008Skylake0x160x8000 0008SunnyCove0x1B0x8000 0008SunnyCove(Alder Lake)0x200x8000 0008SunnyCove(Sapphire Rapids)0x8000 0008
+AMD处理器架构所实现的最高CPUID输入架构基础输入扩展输入K5以前不支持CPUID指令K50x1不支持K5（Krypton）0x10x8000 0005K60x10x8000 0006K70x10x8000 0007K80x10x8000 0018K100x50x8000 001BK10（Llanro）0x60x8000 001BBulldozer0xD0x8000 001EZen0xD0x8000 001FZen20x100x8000 0020Zen30x100x8000 0023Zen40x100x8000 0028
制造商ID含有12个字符，以EBX-EDX-ECX的顺序拼接，例如在英特尔处理器上将返回“GenuineIntel”，具体的返回值如下表所示：
寄存器16进制值ASCIIEBX756E6547GenuECX6C65746EntelEDX49656E69ineI
已知的制造商以及其ID如下：

 "AMDisbetter!" AMD K5的早期工程样品
 "AuthenticAMD" AMD
 "CentaurHauls" IDT/半人马座（Centaur）/兆芯
 "CyrixInstead" Cyrix/意法半导体/IBM
 "GenuineIntel" 英特尔
 "TransmetaCPU" 全美达
 "GenuineTMx86" 全美达
 "Geode by NSC" 国家半导体
 "NexGenDriven" NexGen
 "RiseRiseRise" Rise
 "SiS SiS SiS " 矽统
 "UMC UMC UMC " 联华电子（台联电）
 "VIA VIA VIA " 威盛
 "Vortex86 SoC" DM&P Vortex86
 "  Shanghai  "兆芯
 "HygonGenuine" 海光
 "Genuine  RDC" RDC
 "E2K MACHINE" MCST Elbrus

已知的软核x86处理器ID:

 "MiSTer AO486" ao486
 "GenuineIntel" v586

已知的x86虚拟机ID:

 "bhyve bhyve " bhyve
 " KVMKVMKVM  " KVM
 "TCGTCGTCGTCG" QEMU
 "Microsoft Hv" Hyper-V
 "MicrosoftXTA" –  微软x86到ARM转译器
 " lrpepyh  vr" Parallels
 "VMwareVMware" VMware
 "XenVMMXenVMM" Xen HVM
 "ACRNACRNACRN" Project ACRN
 " QNXQVMBSQG " QNX
 "GenuineIntel" 苹果 罗塞塔2 x86到ARM转译器
 "VirtualApple" –  苹果 罗塞塔2 x86到ARM转译器

使用GNU格式汇编语言获取制造商ID和最高输入的例子：	.data

s0:	.asciz	"CPUID: %x\n"
s1:	.asciz	"Largest basic function number implemented: %i\n"
s2:	.asciz	"Vendor ID: %.12s\n"

	.text

	.align	32
	.globl	main

main:
	pushq	%rbp
	movq	%rsp,%rbp
	subq	$16,%rsp

	movl	$1,%eax
	cpuid

	leaq	s0(%rip),%rdi
	movl	%eax,%esi
	xorl	%eax,%eax
	call	printf

	pushq	%rbx

	xorl	%eax,%eax
	cpuid

	movl	%ebx,8(%rsp)
	movl	%edx,12(%rsp)
	movl	%ecx,16(%rsp)

	popq	%rbx

	leaq	s1(%rip),%rdi
	movl	%eax,%esi
	xorl	%eax,%eax
	call	printf

	leaq	s2(%rip),%rdi
	movq	%rsp,%rsi
	xorl	%eax,%eax
	call	printf

	movq	%rbp,%rsp
	popq	%rbp
//	ret
	movl	$1,%eax
	int	$0x80

 EAX=1: 处理器信息和特性 
该参数在EAX中返回处理器的版本信息,这些信息也被称为“签名”（signature），一般而言，对于使用同一工艺制造的同一架构的处理器，其签名是相同的，故该信息可被用于判断架构和工艺。其包含的内容以及意义如下：
EAX313029282726252423222120191817161514131211109876543210保留=0扩展系列号（Extended Family ID）扩展型号（Extended Model）保留=0处理器类型（Type）系列号（Family）型号（Model）步进（Stepping）
关于版本信息的注意事项：

若系列号部分为0xF，则实际的系列号为EAX中系列号字段和扩展系列号字段的和，反之实际系列号与EAX中字段相同。

若系列号部分为0xF或6，则实际的型号为EAX中扩展型号字段左移四位后与型号字段的和，反之实际型号与EAX中字段相同。

上述内容用伪代码表示：IF Family_ID ≠ 0FH
THEN DisplayFamily = Family_ID;
ELSE DisplayFamily = Extended_Family_ID + Family_ID;
END;
IF (Family_ID = 06H or Family_ID = 0FH)
THEN DisplayModel = (Extended_Model_ID « 4) + Model_ID;
ELSE DisplayModel = Model_ID;
END;步进表示对处理器的修正或使用不同工艺制造，其具体意义应当参考处理器厂商所发布的手册。

其中，处理器类型（Type）的编码如下：
类型二进制编码默认，一般OEM处理器00Overdrive处理器01双处理器10保留11

EBX中返回处理器的附加信息，其内容被分为以下几个字段：
字段EBX说明7:0处理器名称索引号（Brand Index）15:8CLFLUSH指令清除缓存的大小仅当CPU支持CLFLUSH指令时
（CPUID.01.EDX.CLFSH [bit 19]= 1）有效23:16对于AMD处理器，若HTT标志（位于CPUID Fn0000_0001_EDX）为1，则该字段为该处理器的逻辑核心（即线程）数，反之，该字段无意义且为0。
对于Intel处理器，若与上述相同位置的HT标志有效，则该字段代表该架构处理器中可寻址的最大APIC ID数量，（注意：这个值不是逻辑处理器数），在奔腾4HT及以前的处理器中，该字段代表每个物理核心对应的逻辑处理器数量。无论是何种处理器，该字段只在HT标志有效时有意义。31:24初始Local APIC ID该值已被x2APIC ID取代
ECX和EDX返回处理器实现的功能，一个二进制位代表一个功能，该位为1是表示处理器支持该功能，反之则不支持，各位对应功能或指令如下：
位EDXECX简称描述简称描述0fpu集成FPU（Onboard x87 FPU）sse3流式SIMD扩展3（SSE3或Prescott新指令（PNI））1vme虚拟8086模式扩展（Virtual 8086 mode extensions）pclmulqdqPCLMULQDQ指令2de调试扩展（Debugging extensions CR4 bit 3)dtes6464位Debug store (edx bit 21)3pse页尺寸扩展（Page Size Extension）monitorMONITOR和MWAIT指令4tsc时钟周期计数器（Time Stamp Counter）ds-cplCPL qualified debug store5msr特殊模块寄存器（Model-Specific Registers）vmx虚拟机扩展（Virtual Machine eXtensions，VMX）6pae物理地址扩展（Physical Address Extension）smx安全模式扩展（Safer Mode Extensions）7mce机器检查异常（Machine Check Exception）est增强SpeedStep（Enhanced Intel SpeedStep，EIST）8cx8CMPXCHG8指令tm2温度传感器2（Thermal Monitor 2）9apic集成高级可编程中断控制器（Advanced Programmable Interrupt Controller）ssse3流式SIMD扩展3补充指令集（Supplemental SSE3，SSSE3）10保留cnxt-id一级缓存上下文ID（L1 Context ID）11sepSYSENTER和SYSEXIT指令sdbgSilicon Debug interface12mtrr内存类型范围寄存器（Memory Type Range Registers）fma3操作数融合乘加（Fused multiply-add 3，FMA3)13pge全局分页控制位（Page Global Enable bit in CR4）cx16CMPXCHG16B指令14mca机器检查架构（Machine check architecture）xtprCan disable sending task priority messages15cmov条件传送指令（CMOV）pdcmPerfmon & debug capability16pat页属性表（Page Attribute Table）保留17pse-3636位页尺寸扩展（36-bit page size extension）pcidProcess context identifiers (CR4 bit 17)18psn处理器序列号（Processor Serial Number）dcaDirect cache access for DMA writes19clfshCLFLUSH指令sse4.1流式SIMD扩展4.1（SSE4.1）20保留sse4.2流式SIMD扩展4.2（SSE4.2）21ds调试保存（Debug store）x2apicx2APIC22acpi集成ACPI温控寄存器（Onboard thermal control MSRs for ACPI）movbeMOVBE指令23mmx多媒体扩展（MMX）popcntPOPCNT指令24fxsrFXSAVE，FXRESTOR 指令tsc-deadlineAPIC implements one-shot operation using a TSC deadline value25sse流式SIMD扩展（SSE或Katmai新指令）aesAES指令集26sse2流式SIMD扩展2（SSE2）xsaveXSAVE, XRESTOR, XSETBV, XGETBV指令27ss自窥探缓存（self-snoop cache）osxsave操作系统启用XSAVE28htt超线程（Hyper-threading）avx高级矢量扩展（Advanced Vector Extensions，AVX）29tm自控温温度传感器（Thermal monitor automatically limits temperature）f16cF16C指令集30ia64基于IA64架构的处理器rdrnd集成随机数发生器on-chip random number generator）31pbe挂起中断（Pending Break Enable）hypervisor当前为虚拟机环境

 EAX=2: 缓存和TLB信息（英特尔） 
对于英特尔处理器，EAX=2将返回处理器缓存，TLB和预取器信息，这些信息被编码为数个1字节信息返回于EAX，EBX，ECX和EDX四个寄存器中，编码规则如下：

EAX寄存器返回值的最低位（LSB）一定是1，它不代表任何信息，软件读取时应当将其忽略。

每个寄存器返回值的最高位（MSB）代表该寄存器中的值是否有效，若MSB为0，则该寄存器包含有意义的信息，反之则代表该寄存器为保留值且无意义。

若某一寄存器内的返回值有意义，则其应当被分割为4个等长部分，每部分长度为8为，即一字节，每部分代表了一个缓存/TLB或预取器的信息，具体编码见英特尔所发行的官方手册Intel 64 and IA-32 Architectures Software Developer Manuals  卷2A中关于CPUID指令的表3-12。（截至2023.2，该表位于Vol2A，3-245到3-248页）。

 关于EAX=2的注意事项 
代表缓存/TLB/预取的编码字节没有特定的顺序，不存在“某个寄存器的某个字节对应于哪一级别的缓存”。

使用EAX=2是获取英特尔处理器缓存信息的传统方法，但由于缓存类型迅速增多，使用该方法已经无法高效的获取信息，故对于较新的（一般认为是Core及以后）的英特尔处理器，返回值的编码中可能包含类似0xFF或0的空描述符，对于此种情况，应当使用更新的方法（EAX=4）获取缓存信息。

对于AMD处理器，EAX=2是保留项。四个寄存器都将返回0。

 EAX=3处理器序列号（英特尔） 
以3作为输入参数时，CPUID将在ECX和EDX寄存器中返回处理器序列号。

 关于EAX=3的注意事项 
该参数仅在使用核心代号为Katmai和Coppermine的奔腾III处理器上可用，处理器序列号是一个独一无二的96位长的二进制数（实际只使用了64位），每个处理器仅对应一个序列号，通过序列号可用追踪该处理器从生产到销售使用的全部流程，英特尔自称引入该功能的原因是“为了提高电子商务安全性，例如只能在某台电脑上使用某张银行卡”，但其在中美欧等多个国家和地区引起了强烈的隐私和国家安全问题争议，民间认为该功能侵犯了用户隐私权，居心不良者可用该功能追踪他人的计算机，且一旦英特尔的序列号数据库泄露，将带来难以估量的损失，而多国的国家安全机关认为，敌对国家的情报部门可以使用该功能轻而易举的记录并分析计算机活动，迫于市场压力，英特尔从核心代号Tulatin的奔腾III处理器开始禁用了这一功能，且在奔腾4以及Core处理器等后续产品上再未使用过该功能。

对于AMD处理器，EAX=3是保留项。四个寄存器都将返回0。

 EAX=4缓存参数（英特尔） 
以4作为EAX输入参数时，CPUID依据ECX寄存器中输入的第二个参数（也称为子叶）返回处理器缓存信息。

EAX返回缓存类型信息，值分为以下几个字段
+EAX位内容备注4:0缓存类型00：空
01：数据缓存
10：指令缓存
11：数据和指令缓存7:5缓存级别从1开始8自初始化缓存标志为1则支持自初始化，反之需要软件初始化9全相联缓存标志同上13:10保留=025:14最大共享此缓存的线程数1.指逻辑处理器，需考虑超线程
2.为最大值，不一定代表真实值
3.用于计算时将该字段值+131:26最大可寻址的核心ID同上
EBX，ECX返回缓存尺寸信息，返回值各个字段如下：
EBX位内容备注11:0缓存线尺寸（Line Size，L）1.以字节为单位
2.真实值为该字段+121:12物理缓存分区数（Physical Line partitions，P）真实值为该字段+131:22缓存组行数（Ways of associativity，W）同上
ECX返回组相联缓存的的组数（Sets，S）。

 关于EAX=4的注意事项 

 关于子叶的意义以及最大支持的子叶 
英特尔并未在其发布的官方手册中定义子叶与缓存的绝对对应关系，也未说明最大支持的子叶，上述关系表格只是软硬件工程师的总结，但截止到2023年的SunnyCove架构，所有的英特尔处理器都遵守这一对应关系。

在面对一个全新的英特尔架构时，应当假定上述对应关系与最大子叶数不存在，并通过该指令判断，其中对应关系由上述EAX中的返回值相应字段确定，最大子叶值由以下方法判断：在EAX输入4，ECX中的初始值为0并执行CPUID指令，每次执行后将ECX内值加1，EAX仍为4，直到四个寄存器的返回值全部为0，则使返回值不为0的最大ECX输入就是最大支持的子叶。

对于AMD处理器，EAX=3是保留项。四个寄存器都将返回0。

 关于缓存尺寸的计算 
英特尔SDM手册中未直接给出读取缓存尺寸的方法，但可以通过如下公式计算：

其中C为以字节为单位的缓存大小，W，P，L，S为读取该缓存时返回值的相应字段。

 x86外的特定CPU识别信息 
一些非x86的CPU架构也提供了有关处理器能力的某种形式的结构化信息，通常作为一组特殊寄存器：
 ARM架構有一个CPUID协处理器寄存器。
 大型机处理器自1983年的起支持“Store CPU ID”（STIDP）指令，用于查询处理器ID。
  MIPS32架构定义了一个强制性的Processor Identification（PrId）和一系列菊花链“配置寄存器”。
  PowerPC处理器有32位只读的PVR寄存器来识别使用的处理器型号。.

 参见 
 CPU-Z，一个使用CPUID等信息识别系统配置的Windows实用工具。
 CPU-X，一个使用CPUID等信息识别系统配置的Linux实用工具。
 cpuid，類Unix作業系統下的讀取CPUID的實用工具。

 参考资料 

Category:X86架構
Category:机器码