如何在各個版本的VC及64位下使用CPUID指令


  前面我們探討了在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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM