前面我們探討了在16位的DOS實模式下使用CPUID指令(http://www.cnblogs.com/zyl910/archive/2012/05/14/dos16_getcpuid.html)。而現在64位Windows系統已經很流行了,在32/64位模式下如何使用CPUID呢?於是本文介紹了如何在各個版本的VC及64位下使用CPUID指令。
一、推薦使用__cpuid、__cpuidex等Intrinsics函數
在32位模式下,我們可以使用內嵌匯編來調用cpuid指令。但在64位模式下,VC編譯器不支持內嵌匯編。
於是微軟提供了Intrinsics函數——編譯器會將Intrinsics函數編譯為對應的機器指令,而且同時支持32位和64位。
例如CPUID指令的對應Intrinsics函數是——
// http://msdn.microsoft.com/en-us/library/hskdteyh.aspx void __cpuid( int CPUInfo[4], int InfoType ); void __cpuidex( int CPUInfo[4], int InfoType, int ECXValue );
__cpuidex函數的InfoType參數是CPUID指令的eax參數,即功能ID。ECXValue參數是CPUID指令的ecx參數,即子功能ID。CPUInfo參數用於接收輸出的eax, ebx, ecx, edx這四個寄存器。
早期的CPUID功能只需要一個功能ID參數(eax),這時可以使用__cpuid函數。
后來CPUID的功能越來越強大,一個功能ID參數(eax)參數不夠用,於是加了一個子功能ID(ecx)參數,這時應該采用__cpuidex。
二、用條件編譯判斷VC編譯器對Intrinsics函數的支持性(_MSC_VER)
在__cpuid、__cpuidex等Intrinsics函數時,會遇到以下問題——
1.低版本的VC編譯器沒有intrin.h頭文件。【注】:只有VC2005(或更高)才擁有intrin.h,支持__cpuid。
2.低版本的VC編譯器不支持__cpuidex。【注】:只有VC2008的部分版本及VS2010(或更高)的intrin.h中才有__cpuidex。
這時可以使用條件編譯來判斷VC編譯器的版本。
_MSC_VER是微軟C/C++編譯器——cl.exe編譯代碼時預定義的一個宏,它的值表示cl的版本,它的類型是“int”。例如——
#if _MSC_VER >=1200 // VC++6.0以上
#if _MSC_VER >=1300 // VC2003以上
#if _MSC_VER >=1400 // VC2005以上
#if _MSC_VER >=1500 // VC2008以上
#if _MSC_VER >=1600 // VC2010以上
例如發現_MSC_VER大於等於1400時,我們可以#include <intrin.h>。然后再利用_MSC_VER進一步判斷__cpuid、__cpuidex的支持性。
三、用條件編譯判斷64位模式(_WIN64)
使用_WIN64這個預處理宏可用來判斷目標平台是不是64位。
雖然在編譯x64平台的程序時,編譯器會自動推導出_WIN64。但是Visual Studio的語法高亮不清楚這些,它有可能仍是按32位代碼來做語法高亮。所以,建議還是手動在項目的預處理宏中增加_WIN64。
四、32位下用內嵌匯編實現__cpuidex函數
在32位模式下,我們可以使用內嵌匯編來實現__cpuidex函數。代碼如下——
void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue) { if (NULL==CPUInfo) return; _asm{ // load. 讀取參數到寄存器 mov edi, CPUInfo; // 准備用edi尋址CPUInfo mov eax, InfoType; mov ecx, ECXValue; // CPUID cpuid; // save. 將寄存器保存到CPUInfo mov [edi], eax; mov [edi+4], ebx; mov [edi+8], ecx; mov [edi+12], edx; } }
五、全部代碼
全部代碼——
#include <Windows.h> #include <stdio.h> #include <tchar.h> #if _MSC_VER >=1400 // VC2005才支持intrin.h #include <intrin.h> // 所有Intrinsics函數 #endif char szBuf[64]; INT32 dwBuf[4]; #if defined(_WIN64) // 64位下不支持內聯匯編. 應使用__cpuid、__cpuidex等Intrinsics函數。 #else #if _MSC_VER < 1600 // VS2010. 據說VC2008 SP1之后才支持__cpuidex void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue) { if (NULL==CPUInfo) return; _asm{ // load. 讀取參數到寄存器 mov edi, CPUInfo; // 准備用edi尋址CPUInfo mov eax, InfoType; mov ecx, ECXValue; // CPUID cpuid; // save. 將寄存器保存到CPUInfo mov [edi], eax; mov [edi+4], ebx; mov [edi+8], ecx; mov [edi+12], edx; } } #endif // #if _MSC_VER < 1600 // VS2010. 據說VC2008 SP1之后才支持__cpuidex #if _MSC_VER < 1400 // VC2005才支持__cpuid void __cpuid(INT32 CPUInfo[4], INT32 InfoType) { __cpuidex(CPUInfo, InfoType, 0); } #endif // #if _MSC_VER < 1400 // VC2005才支持__cpuid #endif // #if defined(_WIN64) // 取得CPU廠商(Vendor) // // result: 成功時返回字符串的長度(一般為12)。失敗時返回0。 // pvendor: 接收廠商信息的字符串緩沖區。至少為13字節。 int cpu_getvendor(char* pvendor) { INT32 dwBuf[4]; if (NULL==pvendor) return 0; // Function 0: Vendor-ID and Largest Standard Function __cpuid(dwBuf, 0); // save. 保存到pvendor *(INT32*)&pvendor[0] = dwBuf[1]; // ebx: 前四個字符 *(INT32*)&pvendor[4] = dwBuf[3]; // edx: 中間四個字符 *(INT32*)&pvendor[8] = dwBuf[2]; // ecx: 最后四個字符 pvendor[12] = '\0'; return 12; } // 取得CPU商標(Brand) // // result: 成功時返回字符串的長度(一般為48)。失敗時返回0。 // pbrand: 接收商標信息的字符串緩沖區。至少為49字節。 int cpu_getbrand(char* pbrand) { INT32 dwBuf[4]; if (NULL==pbrand) return 0; // Function 0x80000000: Largest Extended Function Number __cpuid(dwBuf, 0x80000000); if (dwBuf[0] < 0x80000004) return 0; // Function 80000002h,80000003h,80000004h: Processor Brand String __cpuid((INT32*)&pbrand[0], 0x80000002); // 前16個字符 __cpuid((INT32*)&pbrand[16], 0x80000003); // 中間16個字符 __cpuid((INT32*)&pbrand[32], 0x80000004); // 最后16個字符 pbrand[48] = '\0'; return 48; } int _tmain(int argc, _TCHAR* argv[]) { //__cpuidex(dwBuf, 0,0); //__cpuid(dwBuf, 0); //printf("%.8X\t%.8X\t%.8X\t%.8X\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]); cpu_getvendor(szBuf); printf("CPU Vendor:\t%s\n", szBuf); cpu_getbrand(szBuf); printf("CPU Name:\t%s\n", szBuf); return 0; }
六、兼容性說明
VC編譯器對32/64位的支持性——
32位:VC6是最早支持編譯32位Intrinsics函數的。
64位:VC2005是最早支持編譯64位Intrinsics函數的。
本文方法在32位編譯器下的兼容性——
__cpuid:兼容VC6(或更高)。
__cpuidex:兼容VC6(或更高)。
本文方法在64位編譯器下的兼容性——
__cpuid:兼容VC2005(或更高)。
__cpuidex:兼容VC2010(或更高)。
參考文獻——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. May 2012. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《Intel® Processor Identification and the CPUID Instruction》. April 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
《AMD CPUID Specification》. September 2010. http://support.amd.com/us/Embedded_TechDocs/25481.pdf
《__cpuid, __cpuidex》. http://msdn.microsoft.com/en-us/library/hskdteyh.aspx
《__cpuidex》. http://social.msdn.microsoft.com/Forums/en-US/vclanguage/thread/cac9c43b-ed72-4283-baa0-e7cd397591bc
《Predefined Macros》. http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.110).aspx
《預定義_MSC_VER宏》. OwnWaterloo著。http://www.cppblog.com/ownwaterloo/archive/2009/04/15/predefined_macro__MSC_VER.html
源碼下載——
http://files.cnblogs.com/zyl910/vcgetcpuid.rar
