[C] 在GCC中獲取CPUID信息(兼容VC)


作者:zyl910

  前面我們嘗試過在VC中獲取CPUID信息。現在再來試試GCC編譯器。


一、調用CPUID指令

  怎么調用CPUID指令呢?有三種辦法——
1. 用匯編語言編寫一個cpuid函數,然后調整鏈接器配置,在C語言中調用該函數。
2. 使用內嵌匯編調用CPUID指令。
3. 使用編譯器提供的Intrinsics函數來調用CPUID等特定平台指令。

  我們一般優先使用第3種辦法,代碼量少、可讀性高、編譯維護簡單。例如VC(VC2005或更高)在“intrin.h”中聲明了 __cpuid函數。
  當編譯器沒有提供Intrinsics函數時,就只有使用前兩種辦法了。


1.1 查找函數

  首先,應該先檢查編譯器是否提供CPUID指令的Intrinsics函數。
  我裝的是Fedora 17,首先嘗試了——
1. 使用“man”命令查閱手冊,搜索“__cpuid”等關鍵字。沒有找到。
2. 在“/usr/include”目錄中的頭文件中搜索“cpuid” 。也沒有找到。

  手冊中沒有,include中也沒有,難道真的沒有?
  別着急,試一試“locate cpuid”,其中可以發現“/usr/lib/gcc/i686-redhat-linux/4.7.0/include/cpuid.h”。打開該文件,發現果然有“__cpuid”等聲明。

  再看一下MinGW(20120426版)。發現“cpuid.h”也是放在“\lib\gcc\mingw32\4.6.2\include”目錄中,而不是“\include”目錄。


1.2 代碼解讀

  雖然手冊上沒有__cpuid的說明,但我們可以通過閱讀代碼分析其用法。
  打開“cpuid.h”,找到__cpuid的聲明——

#if defined(__i386__) && defined(__PIC__)
/* %ebx may be the PIC register.  */
#if __GNUC__ >= 3
#define __cpuid(level, a, b, c, d)            \
  __asm__ ("xchg{l}\t{%%}ebx, %1\n\t"            \
       "cpuid\n\t"                    \
       "xchg{l}\t{%%}ebx, %1\n\t"            \
       : "=a" (a), "=r" (b), "=c" (c), "=d" (d)    \
       : "0" (level))

#define __cpuid_count(level, count, a, b, c, d)        \
  __asm__ ("xchg{l}\t{%%}ebx, %1\n\t"            \
       "cpuid\n\t"                    \
       "xchg{l}\t{%%}ebx, %1\n\t"            \
       : "=a" (a), "=r" (b), "=c" (c), "=d" (d)    \
       : "0" (level), "2" (count))
#else
/* Host GCCs older than 3.0 weren't supporting Intel asm syntax
   nor alternatives in i386 code.  */
#define __cpuid(level, a, b, c, d)            \
  __asm__ ("xchgl\t%%ebx, %1\n\t"            \
       "cpuid\n\t"                    \
       "xchgl\t%%ebx, %1\n\t"            \
       : "=a" (a), "=r" (b), "=c" (c), "=d" (d)    \
       : "0" (level))

#define __cpuid_count(level, count, a, b, c, d)        \
  __asm__ ("xchgl\t%%ebx, %1\n\t"            \
       "cpuid\n\t"                    \
       "xchgl\t%%ebx, %1\n\t"            \
       : "=a" (a), "=r" (b), "=c" (c), "=d" (d)    \
       : "0" (level), "2" (count))
#endif
#else
#define __cpuid(level, a, b, c, d)            \
  __asm__ ("cpuid\n\t"                    \
       : "=a" (a), "=b" (b), "=c" (c), "=d" (d)    \
       : "0" (level))

#define __cpuid_count(level, count, a, b, c, d)        \
  __asm__ ("cpuid\n\t"                    \
       : "=a" (a), "=b" (b), "=c" (c), "=d" (d)    \
       : "0" (level), "2" (count))
#endif

 

  這一段有點長,我們先看看簡單的。最后一個__cpuid_count的定義是——

#define __cpuid_count(level, count, a, b, c, d)        \
  __asm__ ("cpuid\n\t"                    \
       : "=a" (a), "=b" (b), "=c" (c), "=d" (d)    \
       : "0" (level), "2" (count))

 


  GCC內嵌匯編的格式是——
__asm__("asm statements" : outputs : inputs : registers-modified);

  對於上面的__cpuid_count——
asm statements: "cpuid\n\t"。匯編代碼為——調用cpuid指令
outputs:"=a" (a), "=b" (b), "=c" (c), "=d" (d)。表示有4個輸出參數——參數0為eax(綁定到變量a)、參數1為ebx(綁定到變量b)、參數2為ecx(綁定到變量c)、參數3為edx(綁定到變量d)。
inputs:"0" (level), "2" (count))。表示有2個輸入參數——將變量level賦給參數0(eax),將變量count賦給參數2(ecx)。
registers-modified:(無)。

  所以__cpuid_count的執行過程是——
將變量level的值賦給eax // inputs
將變量count的值賦給ecx
調用cpuid指令 // asm statements
將返回的eax賦給a // outputs
將返回的ebx賦給b
將返回的ecx賦給c
將返回的edx賦給d

  翻閱一下Intel和AMD的手冊,得知CPUID指令的輸入參數是eax、ecx,輸出參數是eax、ebx、ecx、edx。所以__cpuid_count的參數含義是——
level:輸入的eax,CPUID功能號。
count:輸入的ecx,CPUID子功能號。
a:返回的eax。
b:返回的ebx。
c:返回的ecx。
d:返回的edx。

  而__cpuid少了count參數,不支持子功能號。某些CPUID功能不需要填寫子功能號,這時使用__cpuid會比較方便。

  弄懂其功能之后,前面的另外幾種宏定義就很容易明白了。因為PIC使用了ebx寄存器,於是將b綁定到其他寄存器,再利用xchgl指令來備份與恢復ebx寄存器。


二、封裝函數

  為了提高程序的可移植性,不建議直接調用__cpuid,而應該將其封裝為一個函數。
  對於函數的參數格式。感覺VC中的__cpuid的參數格式似乎更好——用數組接收輸出的eax、ebx、ecx、edx。
  對於參數的數據類型。覺得還是統一使用無符號數比較好。

  根據上面的思路,我編寫了getcpuidex、getcpuid函數——

void getcpuidex(unsigned int CPUInfo[4], unsigned int InfoType, unsigned int ECXValue)
{
#if defined(__GNUC__)    // GCC
    __cpuid_count(InfoType, ECXValue, CPUInfo[0],CPUInfo[1],CPUInfo[2],CPUInfo[3]);
#elif defined(_MSC_VER)    // MSVC
    #if defined(_WIN64) || _MSC_VER>=1600    // 64位下不支持內聯匯編. 1600: VS2010, 據說VC2008 SP1之后才支持__cpuidex.
        __cpuidex((int*)(void*)CPUInfo, (int)InfoType, (int)ECXValue);
    #else
        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
#endif    // #if defined(__GNUC__)
}

void getcpuid(unsigned int CPUInfo[4], unsigned int InfoType)
{
#if defined(__GNUC__)    // GCC
    __cpuid(InfoType, CPUInfo[0],CPUInfo[1],CPUInfo[2],CPUInfo[3]);
#elif defined(_MSC_VER)    // MSVC
    #if _MSC_VER>=1400    // VC2005才支持__cpuid
        __cpuid((int*)(void*)CPUInfo, (int)InfoType);
    #else
        getcpuidex(CPUInfo, InfoType, 0);
    #endif
#endif    // #if defined(__GNUC__)
}

 

  首先根據__GNUC__判斷是不是GCC,若是便調用GCC中的__cpuid_count(或__cpuid);
  然后根據_MSC_VER判斷是不是VC。再根據VC的版本,選擇是調用__cpuidex(或__cpuid),還是內嵌匯編(或調用getcpuidex)。


三、全部代碼

  全部代碼——

#include <stdio.h>

// intrinsics
#if defined(__GNUC__)    // GCC
#include <cpuid.h>
#elif defined(_MSC_VER)    // MSVC
    #if _MSC_VER >=1400    // VC2005
#include <intrin.h>
    #endif    // #if _MSC_VER >=1400
#else
#error Only supports MSVC or GCC.
#endif    // #if defined(__GNUC__)

void getcpuidex(unsigned int CPUInfo[4], unsigned int InfoType, unsigned int ECXValue)
{
#if defined(__GNUC__)    // GCC
    __cpuid_count(InfoType, ECXValue, CPUInfo[0],CPUInfo[1],CPUInfo[2],CPUInfo[3]);
#elif defined(_MSC_VER)    // MSVC
    #if defined(_WIN64) || _MSC_VER>=1600    // 64位下不支持內聯匯編. 1600: VS2010, 據說VC2008 SP1之后才支持__cpuidex.
        __cpuidex((int*)(void*)CPUInfo, (int)InfoType, (int)ECXValue);
    #else
        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
#endif    // #if defined(__GNUC__)
}

void getcpuid(unsigned int CPUInfo[4], unsigned int InfoType)
{
#if defined(__GNUC__)    // GCC
    __cpuid(InfoType, CPUInfo[0],CPUInfo[1],CPUInfo[2],CPUInfo[3]);
#elif defined(_MSC_VER)    // MSVC
    #if _MSC_VER>=1400    // VC2005才支持__cpuid
        __cpuid((int*)(void*)CPUInfo, (int)InfoType);
    #else
        getcpuidex(CPUInfo, InfoType, 0);
    #endif
#endif    // #if defined(__GNUC__)
}

// 取得CPU廠商(Vendor).
//
// result: 成功時返回字符串的長度(一般為12)。失敗時返回0.
// pvendor: 接收廠商信息的字符串緩沖區。至少為13字節.
int cpu_getvendor(char* pvendor)
{
    unsigned int dwBuf[4];
    if (NULL==pvendor)    return 0;
    // Function 0: Vendor-ID and Largest Standard Function
    getcpuid(dwBuf, 0);
    // save. 保存到pvendor
    *(unsigned int *)&pvendor[0] = dwBuf[1];    // ebx: 前四個字符.
    *(unsigned int *)&pvendor[4] = dwBuf[3];    // edx: 中間四個字符.
    *(unsigned int *)&pvendor[8] = dwBuf[2];    // ecx: 最后四個字符.
    pvendor[12] = '\0';
    return 12;
}

// 取得CPU商標(Brand).
//
// result: 成功時返回字符串的長度(一般為48)。失敗時返回0.
// pbrand: 接收商標信息的字符串緩沖區。至少為49字節.
int cpu_getbrand(char* pbrand)
{
    unsigned int dwBuf[4];
    if (NULL==pbrand)    return 0;
    // Function 0x80000000: Largest Extended Function Number
    getcpuid(dwBuf, 0x80000000U);
    if (dwBuf[0] < 0x80000004U)    return 0;
    // Function 80000002h,80000003h,80000004h: Processor Brand String
    getcpuid((unsigned int *)&pbrand[0], 0x80000002U);    // 前16個字符.
    getcpuid((unsigned int *)&pbrand[16], 0x80000003U);    // 中間16個字符.
    getcpuid((unsigned int *)&pbrand[32], 0x80000004U);    // 最后16個字符.
    pbrand[48] = '\0';
    return 48;
}


char szBuf[64];
unsigned int dwBuf[4];

int main(int argc, char* argv[])
{
    // test
    //getcpuidex(dwBuf, 0,0);
    //getcpuid(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;
}

 

  在以下編譯器中成功編譯——
VC6
VC2003
VC2005
VC2010(x86與x64)
BCB6
GCC 4.7.0(Fedora 17)
GCC 4.6.2(MinGW (20120426))


四、測試結果

  GCC 4.7.0(Fedora 17)——


  GCC 4.6.2(MinGW (20120426))——


  VC2010(x64)——

 

參考文獻——
《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
《AT&T匯編語言與GCC內嵌匯編簡介》. http://blog.csdn.net/freerock/article/details/1771143
《gcc指定-fPIC編譯的時候內嵌匯編需要注意的問題》. clkrst.http://space.itpub.net/67063/viewspace-169190/
《[C/C++] 顯示各種C/C++編譯器的預定義宏(C11標准、C++11標准、VC、BCB、Intel、GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/02/printmacro.html
《如何在各個版本的VC及64位下使用CPUID指令》. http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html


源碼下載——
http://files.cnblogs.com/zyl910/getcpuid_gcc.rar


免責聲明!

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



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