热门问题
时间线
聊天
视角
CPUID
来自维基百科,自由的百科全书
Remove ads
CPUID指令是一條x86架構中的的擴充指令(此處的擴充指相對i80386),其操作碼輔助記憶碼縮寫於「CPU辨識」(CPU Identification),其作用是返回特定的CPU資訊,使得軟件可以在執行時檢測CPU的硬件特性,以便於辨識並決定執行哪些代碼。首批支援CPUID指令的處理器是1993年發布的奔騰(i586)和486SL。[1]
|  | 
通過使用CPUID,軟件可以確定處理器的類型和特性支援(例如MMX/SSE)。CPUID操作碼為0Fh、A2h。在呼叫CPUID前EAX暫存器內的值作為其輸入參數,Intel64指令集手冊中稱其為「葉」(Leaf),AMD64指令集手冊中稱「功能碼」(Function Number),執行CPUID後,返回值將被寫入EAX,EBX,ECX和EDX暫存器內。此外,在使用一些特殊的輸入參數時,必須同時於ECX內寫入第二個輸入參數,否則無法得到有意義的返回值。第二個參數也被稱為「子葉」(Sub Leaf)或「子功能碼」(Sub Function)。
Remove ads
歷史
在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,並觀察處理器隨後的行為,每一代處理器的具體行為均有所不同。
Remove ads
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
檢測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暫存器在處理器被硬件復位後將被初始化為處理器的修訂版本號。
Remove ads
奔騰處理器支援使用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暫存器)。
Remove ads
呼叫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)。
Remove ads
輸入參數碼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
Remove ads
該參數在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是表示處理器支援該功能,反之則不支援,各位對應功能或指令如下:
Remove ads
對於英特爾處理器,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頁)。
Remove ads
代表快取/TLB/預取的編碼位元組沒有特定的順序,不存在「某個暫存器的某個位元組對應於哪一級別的快取」。
使用EAX=2是取得英特爾處理器快取資訊的傳統方法,但由於快取類型迅速增多,使用該方法已經無法高效的取得資訊,故對於較新的(一般認為是Core及以後)的英特爾處理器,返回值的編碼中可能包含類似0xFF或0的空描述符,對於此種情況,應當使用更新的方法(EAX=4)取得快取資訊。
對於AMD處理器,EAX=2是保留項。四個暫存器都將返回0。
以3作為輸入參數時,CPUID將在ECX和EDX暫存器中返回處理器序列號。
該參數僅在使用核心代號為Katmai和Coppermine的奔騰III處理器上可用,處理器序列號是一個獨一無二的96位長的二進制數(實際只使用了64位元),每個處理器僅對應一個序列號,通過序列號可用追蹤該處理器從生產到銷售使用的全部流程,英特爾自稱引入該功能的原因是「為了提高電子商務安全性,例如只能在某台電腦上使用某張銀行卡」,但其在中美歐等多個國家和地區引起了強烈的私隱和國家安全問題爭議,民間認為該功能侵犯了用戶私隱權,居心不良者可用該功能追蹤他人的電腦,且一旦英特爾的序列號資料庫泄露,將帶來難以估量的損失,而多國的國家安全機關認為,敵對國家的情報部門可以使用該功能輕而易舉的記錄並分析電腦活動,迫於市場壓力,英特爾從核心代號Tulatin的奔騰III處理器開始禁用了這一功能,且在奔騰4以及Core處理器等後續產品上再未使用過該功能。
對於AMD處理器,EAX=3是保留項。四個暫存器都將返回0。
以4作為EAX輸入參數時,CPUID依據ECX暫存器中輸入的第二個參數(也稱為子葉)返回處理器快取資訊。
EAX返回快取類型資訊,值分為以下幾個欄位
EBX,ECX返回快取尺寸資訊,返回值各個欄位如下:
ECX返回組相聯快取的的組數(Sets,S)。
英特爾並未在其發布的官方手冊中定義子葉與快取的絕對對應關係,也未說明最大支援的子葉,上述關係表格只是軟硬件工程師的總結,但截止到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架構也提供了有關處理器能力的某種形式的結構化資訊,通常作為一組特殊暫存器:
參見
參考資料
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads
