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

[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__)__GNUC__ >= 3#define __cpuid(level, a, b, c, d)\ __asm__ (\\\: (a), (b), (c), (d) \: (level))#define __cpuid_count(level, count, a, b, c, d)\ __asm__ (\\\: (a), (b), (c), (d) \: (level), (count))#else/* Host GCCs older than 3.0 weren’t supporting Intel asm syntaxnor alternatives in i386 code. */#define __cpuid(level, a, b, c, d)\ __asm__ (\\\: (a), (b), (c), (d) \: (level))#define __cpuid_count(level, count, a, b, c, d)\ __asm__ (\\\: (a), (b), (c), (d) \: (level), (count))__cpuid(level, a, b, c, d)\ __asm__ (\: (a), (b), (c), (d) \: (level))#define __cpuid_count(level, count, a, b, c, d)\ __asm__ (\: (a), (b), (c), (d) \: (level), (count))#endif

  这一段有点长,我们先看看简单的。最后一个__cpuid_count的定义是——

#define __cpuid_count(level, count, a, b, c, d)\ __asm__ (\: (a), (b), (c), (d) \: (level), (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]);defined(_WIN64) || _MSC_VER>=1600 // 64位下不支持内联汇编. 1600: VS2010, 据说VC2008 SP1之后才支持__cpuidex.__cpuidex((int*)(void*)CPUInfo, (int)InfoType, (int)ECXValue);#elseif (NULL==CPUInfo) return;_asm{// load. 读取参数到寄存器.mov edi, CPUInfo; // 准备用edi寻址CPUInfomov eax, InfoType;mov ecx, ECXValue;// CPUIDcpuid;// save. 将寄存器保存到CPUInfomov [edi], eax;mov [edi+4], ebx;mov [edi+8], ecx;mov [edi+12], edx;}}void getcpuid(unsigned int CPUInfo[4], unsigned int InfoType){#if defined(__GNUC__) // GCC__cpuid(InfoType, CPUInfo[0],CPUInfo[1],CPUInfo[2],CPUInfo[3]);_MSC_VER>=1400 // VC2005才支持__cpuid__cpuid((int*)(void*)CPUInfo, (int)InfoType);#elsegetcpuidex(CPUInfo, InfoType, 0);}

这里的风景美不胜收,真让人流连忘返。

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

相关文章:

你感兴趣的文章:

标签云: