zoukankan      html  css  js  c++  java
  • [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


    源码下载——
    https://files.cnblogs.com/zyl910/getcpuid_gcc.rar

    作者:zyl910
    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.
  • 相关阅读:
    apache安全—用户访问控制
    hdu 3232 Crossing Rivers 过河(数学期望)
    HDU 5418 Victor and World (可重复走的TSP问题,状压dp)
    UVA 11020 Efficient Solutions (BST,Splay树)
    UVA 11922 Permutation Transformer (Splay树)
    HYSBZ 1208 宠物收养所 (Splay树)
    HYSBZ 1503 郁闷的出纳员 (Splay树)
    HDU 5416 CRB and Tree (技巧)
    HDU 5414 CRB and String (字符串,模拟)
    HDU 5410 CRB and His Birthday (01背包,完全背包,混合)
  • 原文地址:https://www.cnblogs.com/zyl910/p/getcpuid_gcc.html
Copyright © 2011-2022 走看看