• [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.
  • 相关阅读:
    Ubuntu开机自动挂载Windows分区
    Ubuntu 修改hosts
    线程安全
    可重入
    java.lang.NoSuchMethodException: org.apache.catalina.deploy.WebXml addServlet问题
    两数相加
    2017-2018 ACM-ICPC, Central Europe Regional Contest (CERC 17)
    Anniversary party
    k倍区间
    算法训练 素因子去重
  • 原文地址:https://www.cnblogs.com/zyl910/p/getcpuid_gcc.html
Copyright © 2020-2023  润新知