CPUID

codice_1指令是一條x86架構中的的擴充套件指令和486SL。透過使用codice_1，軟體可以確定處理器的型別和特性支援。歷史.CPUID前判斷處理器型號的方法.在codice_1指令被引入前，若要在執行時確定CPU的型號，需要編寫晦澀的組合語言程式碼以判斷某些指令被執行後處理器的行為。1990年以前，對於如何判斷CPU的型號沒有官方說明與實現方法，但軟硬體工程師們總結了一套利用PUSHF/POPF指令檢測CPU行為以判斷其架構的方法。此方法在廣泛應用的同時引起了巨大爭議，反對者認為其並非萬無一失，在某些需要高可靠性的軟體平臺上若處理器型號判斷錯誤，則有可能造成巨大損失，但英特爾在1990年釋出了《i486微處理器程式設計師參考手冊》，其中在手冊的第22.10節中使用了上述方法判斷CPU代數，並且英特爾聲稱“"程式碼序列已經過 Intel 驗證，可以檢測 CPUID、數學協處理器功能，並進行相應的初始化。任何其他方法都可能在未來的處理器中產生不可預測的結果"”。此後該方法被應用於大多數作業系統核心中。以下所介紹之方法為英特爾手冊中的官方演算法。該演算法的核心是利用PUSHF/POPF指令將標誌暫存器中的特定位設為1或0，並觀察處理器隨後的行為，每一代處理器的具體行為均有所不同。判斷8086/80286.8086/8088系列處理器在清除標誌暫存器的12-15位後，無論向其中寫入何值，處理器將始終把它們設為1，其檢測方式如下pushf ; 將標誌暫存器內容壓棧pop ax ; 將標誌暫存器內容送入AXmov cx, ax ; 將標誌暫存器內容送入CX備份and ax, 0fffh ; 將AX的12-15位置零push ax ; 將標誌暫存器的新值壓棧popf ; 送入FLAGpushf ; 獲取彈棧後標誌暫存器的內容pop ax ; 存入AXand ax, 0f000h ; 如果12-15位被處理器置1則為8086/8088cmp ax, 0f000hje end_cpu_type_8688 ; 跳轉至對應程式碼對於80286系列處理器，在真實模式下這些位始終被清零，在保護模式下，其用於I/O特權級別和巢狀任務，其檢測方法如下，程式碼接上述程式碼執行or cx, 0f000h ; 將12-15位置1push cx ; 壓棧popf ; 將棧內容彈入標誌暫存器pushf ; 獲取標誌暫存器並存入AXpop axand ax, 0f000h ; 若12-15位位0則為286jz end_cpu_type_286判斷80386/80486.檢測386/486處理器的原理與上述程式碼相同，但檢測的位有所區別，486處理器中引入了一個AC標誌位，並將把它置1，其對於386而言是無效的，故386處理器將其置零，其實現如下pushfd；EFLAGS壓棧pop eax ；獲取原始 EFLAGSmov ecx, eax ；儲存原來的 EFLAGSxor eax, 40000h ; 翻轉 EFLAGS 中的 AC 位push eax ；將新的 EFLAGS 壓棧popfd 中；替換當前的 EFLAGS 值pushfd；獲取新的 EFLAGSpop eax ；將新的 EFLAGS 儲存在 EAXxor eax, ecx 中；無法反轉AC則為386jz end_cpu_type_386 ；如果 80386 處理器此外，80386處理器的EDX暫存器在處理器被硬體復位後將被初始化為處理器的修訂版本號。檢測80486或奔騰.奔騰處理器支援使用CPUID指令，因此其標誌位中設定了一個被稱為ID的特殊標誌，若該標誌存在，則對應的處理器支援CPUID，檢測實現如下mov eax, ecx ; 獲取原始 EFLAGSxor eax, 200000h ；翻轉 EFLAGS 中的 ID 位push eax ；將新的 EFLAGS 值儲存在堆疊popfd 中；替換當前的 EFLAGS 值pushfd ；獲取新的 EFLAGSpop eax ；將新的 EFLAGS 儲存在 EAXxor eax, ecx 中；可切換則為奔騰或486SLjne end_cpu_type_586關於上述方法的注意事項.該演算法無法檢測80186處理器，80186/88處理器包80286中包含的大部分新指令和異常，包括"PUSHA/POPA、PUSH、SHL"和無效操作碼異常。80186/88中唯一沒有實現的是專門用於保護模式的指令和異常。未能檢測到此處理器可能會禁止使用某些可以利用這些新指令和異常的軟體。同時，由於PUSHF/POPF是Ring0指令，故該演算法只能在真實模式中執行，保護模式作業系統中執行的應用程式無法使用這個方法，除非處理器以及作業系統支援虛擬模式擴充套件。以上演算法僅供參考與學習，在使用非英特爾處理器，完全軟體虛擬化的虛擬機器或其他平臺上，該演算法很有可能無法正確判斷處理器支援的指令集。考慮到奔騰已經是近30年前的處理器，除非有必要在古老的計算機上檢測CPU，否則不應該再使用這種演算法，而是直接呼叫CPUID指令。對於非x86架構的CPU，仍然需要精心設計的程式碼判斷其差別。呼叫CPUID.codice_1 指令的操作碼是 codice_6。codice_1沒有顯式的運算元，其隱式使用EAX或EAX與ECX中的值作為引數，以確定要獲取的資訊。在英特爾指令集手冊中，codice_1的輸入引數。注意，當且僅當MSR暫存器"IA32_MISC_ENABLE.BOOT_NT4" 的第22位為0時，EAX大於3的輸入才是有意義的。當且僅當該位被設為1時，Windows NT 4.0 SP6以前的作業系統才能正常啟動。截止到2022年，對於一些常見的處理器架構，最大的有效的基礎輸入是0x20(GoldenCove），0x10。EAX=0: 最高基礎輸入引數與製造商ID.輸入引數位0時，CPUID返回對當前處理器有意義的。最大輸入引數被寫入EAX暫存器中，對於英特爾和AMD處理器，已知的返回值如下表所示：製造商ID含有12個字元，以EBX-EDX-ECX的順序拼接，例如在英特爾處理器上將返回“GenuineIntel”，具體的返回值如下表所示：已知的製造商以及其ID如下：已知的軟核x86處理器ID:已知的x86虛擬機器ID:使用GNU格式組合語言獲取製造商ID和最高輸入的例子：.datas0: .asciz "CPUID: %x\n"s1: .asciz "Largest basic function number implemented: %i\n"s2: .asciz "Vendor ID: %.12s\n".text.align 32.globl mainmain:pushq %rbpmovq %rsp,%rbpsubq $16,%rspmovl $1,%eaxcpuidleaq s0(%rip),%rdimovl %eax,%esixorl %eax,%eaxcall printfpushq %rbxxorl %eax,%eaxcpuidmovl %ebx,8(%rsp)movl %edx,12(%rsp)movl %ecx,16(%rsp)popq %rbxleaq s1(%rip),%rdimovl %eax,%esixorl %eax,%eaxcall printfleaq s2(%rip),%rdimovq %rsp,%rsixorl %eax,%eaxcall printfmovq %rbp,%rsppopq %rbp// retmovl $1,%eaxint $0x80EAX=1: 處理器資訊和特性.該引數在EAX中返回處理器的版本資訊,這些資訊也被稱為“簽名”，一般而言，對於使用同一工藝製造的同一架構的處理器，其簽名是相同的，故該資訊可被用於判斷架構和工藝。其包含的內容以及意義如下：關於版本資訊的注意事項：上述內容用偽程式碼表示：IF Family_ID ≠ 0FHTHEN 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;步進表示對處理器的修正或使用不同工藝製造，其具體意義應當參考處理器廠商所釋出的手冊。其中，處理器型別的編碼如下：EBX中返回處理器的附加資訊，其內容被分為以下幾個欄位：ECX和EDX返回處理器實現的功能，一個二進位制位代表一個功能，該位為1是表示處理器支援該功能，反之則不支援，各位對應功能或指令如下：EAX=2: 快取和TLB資訊.對於英特爾處理器，EAX=2將返回處理器快取，TLB和預取器資訊，這些資訊被編碼為數個1位元組資訊返回於EAX，EBX，ECX和EDX四個暫存器中，編碼規則如下：EAX暫存器返回值的最低位一定是1，它不代表任何資訊，軟體讀取時應當將其忽略。每個暫存器返回值的最高位代表該暫存器中的值是否有效，若MSB為0，則該暫存器包含有意義的資訊，反之則代表該暫存器為保留值且無意義。若某一暫存器內的返回值有意義，則其應當被分割為4個等長部分，每部分長度為8為，即一位元組，每部分代表了一個快取/TLB或預取器的資訊，具體編碼見英特爾所發行的官方手冊Intel 64 and IA-32 Architectures Software Developer Manuals 。關於EAX=2的注意事項.代表快取/TLB/預取的編碼位元組沒有特定的順序，不存在“某個暫存器的某個位元組對應於哪一級別的快取”。使用EAX=2是獲取英特爾處理器快取資訊的傳統方法，但由於快取型別迅速增多，使用該方法已經無法高效的獲取資訊，故對於較新的獲取快取資訊。對於AMD處理器，EAX=2是保留項。四個暫存器都將返回0。EAX=3處理器序列號.以3作為輸入引數時，CPUID將在ECX和EDX暫存器中返回處理器序列號。關於EAX=3的注意事項.該引數僅在使用核心代號為Katmai和Coppermine的奔騰III處理器上可用，處理器序列號是一個獨一無二的96位長的二進位制數，每個處理器僅對應一個序列號，透過序列號可用追蹤該處理器從生產到銷售使用的全部流程，英特爾自稱引入該功能的原因是“為了提高電子商務安全性，例如只能在某臺電腦上使用某張銀行卡”，但其在中美歐等多個國家和地區引起了強烈的隱私和國家安全問題爭議，民間認為該功能侵犯了使用者隱私權，居心不良者可用該功能追蹤他人的計算機，且一旦英特爾的序列號資料庫洩露，將帶來難以估量的損失，而多國的國家安全機關認為，敵對國家的情報部門可以使用該功能輕而易舉的記錄並分析計算機活動，迫於市場壓力，英特爾從核心代號Tulatin的奔騰III處理器開始禁用了這一功能，且在奔騰4以及Core處理器等後續產品上再未使用過該功能。對於AMD處理器，EAX=3是保留項。四個暫存器都將返回0。EAX=4快取引數.以4作為EAX輸入引數時，CPUID依據ECX暫存器中輸入的第二個引數返回處理器快取資訊。EAX返回快取型別資訊，值分為以下幾個欄位EBX，ECX返回快取尺寸資訊，返回值各個欄位如下：ECX返回組相聯快取的的組數。關於EAX=4的注意事項.關於子葉的意義以及最大支援的子葉.英特爾並未在其釋出的官方手冊中定義子葉與快取的絕對對應關係，也未說明最大支援的子葉，上述關係表格只是軟硬體工程師的總結，但截止到2023年的SunnyCove架構，所有的英特爾處理器都遵守這一對應關係。在面對一個全新的英特爾架構時，應當假定上述對應關係與最大子葉數不存在，並透過該指令判斷，其中對應關係由上述EAX中的返回值相應欄位確定，最大子葉值由以下方法判斷：在EAX輸入4，ECX中的初始值為0並執行CPUID指令，每次執行後將ECX內值加1，EAX仍為4，直到四個暫存器的返回值全部為0，則使返回值不為0的最大ECX輸入就是最大支援的子葉。對於AMD處理器，EAX=3是保留項。四個暫存器都將返回0。關於快取尺寸的計算.英特爾SDM手冊中未直接給出讀取快取尺寸的方法，但可以透過如下公式計算：formula_1其中C為以位元組為單位的快取大小，W，P，L，S為讀取該快取時返回值的相應欄位。x86外的特定CPU識別資訊.一些非x86的CPU架構也提供了有關處理器能力的某種形式的結構化資訊，通常作為一組特殊暫存器：