【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处理器，已知的返回值如下表所示：
制造商ID含有12个字符，以EBX-EDX-ECX的顺序拼接，例如在英特尔处理器上将返回“GenuineIntel”，具体的返回值如下表所示：
已知的制造商以及其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
已知的虚拟机处理器ID:
* "bhyve bhyve " bhyve
* " KVMKVMKVM  " KVM
* "TCGTCGTCGTCG" QEMU
* "Microsoft Hv" Hyper-V
* "MicrosoftXTA" –  微软ARM到x86转译器
* " lrpepyh  vr" Parallels
* "VMwareVMware" VMware
* "XenVMMXenVMM" Xen HVM
* "ACRNACRNACRN" Project ACRN
* " QNXQVMBSQG " QNX
* "GenuineIntel" 苹果 罗塞塔2 ARM到x86转译器
* "VirtualApple" –  苹果 罗塞塔2 ARM到x86转译器
使用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），一般而言，对于使用同一工艺制造的同一架构的处理器，其签名是相同的，故该信息可被用于判断架构和工艺。其包含的内容以及意义如下：
关于版本信息的注意事项：
*若系列号部分为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）的编码如下：
EBX中返回处理器的附加信息，其内容被分为以下几个字段：
ECX和EDX返回处理器实现的功能，一个二进制位代表一个功能，该位为1是表示处理器支持该功能，反之则不支持，各位对应功能或指令如下：
=== EAX=2: 缓存和TLB信息（英特尔） ===
对于英特尔处理器，EAX=2将返回处理器缓存和TLB信息，这些信息被编码为数个1字节信息返回于ABCD四个寄存器中。
==== 关于该参数的注意事项 ====
对于AMD处理器，EAX=2是保留值。执行后将四个寄存器均返回0。
== x86外的特定CPU识别信息 ==
一些非x86的CPU架构也提供了有关处理器能力的某种形式的结构化信息，通常作为一组特殊寄存器：
* ARM架构有一个CPUID协处理器寄存器。
* IBM System z大型机处理器自1983年的IBM 4381起支持“Store CPU ID”（STIDP）指令，用于查询处理器ID。
*  MIPS32架构定义了一个强制性的Processor Identification（PrId）和一系列菊花链“配置寄存器”。
*  PowerPC处理器有32位只读的PVR寄存器来识别使用的处理器型号。.