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
已知的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），一般而言，對於使用同一工藝製造的同一架構的處理器，其簽名是相同的，故該信息可被用於判斷架構和工藝。其包含的內容以及意義如下：
關於版本信息的注意事項：
*若系列號部分為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字節信息返回於EAX，EBX，ECX和EDX四個寄存器中，編碼規則如下：
EAX寄存器返回值的最低位（LSB）一定是1，它不代表任何信息，軟件讀取時應當將其忽略。
每個寄存器返回值的最高位（MSB）代表該寄存器中的值是否有效，若MSB為0，則該寄存器包含有意義的信息，反之則代表該寄存器為保留值且無意義。
若某一寄存器內的返回值有意義，則其應當被分割為4個等長部分，每部分長度為8為，即一字節，每部分代表了一個緩存/TLB或預取器的信息，具體編碼見英特爾所發行的官方手冊 Intel 64 and IA-32 Architectures Software Developer Manuals date=20230602224827  卷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返回緩存類型信息，值分為以下幾個字段
EBX，ECX返回緩存尺寸信息，返回值各個字段如下：
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協處理器寄存器。- IBM System z大型機處理器自1983年的IBM 4381起支持“Store CPU ID”（STIDP）指令，用於查詢處理器ID。-  MIPS32架構定義了一個強制性的Processor Identification（PrId）和一系列菊花鏈“配置寄存器”。-  PowerPC處理器有32位只讀的PVR寄存器來識別使用的處理器型號。.

**參見**
 CPU-Z，一個使用CPUID等信息識別系統配置的Windows實用工具。- CPU-X，一個使用CPUID等信息識別系統配置的Linux實用工具。- cpuid，類Unix作業系統下的讀取CPUID的實用工具。

**參考資料 ==