CPUIDFIELD:CPUID字段的统一编号、读取方案。范例:检查SSE4A、AES、PCLMULQDQ指令

http://blog.csdn.net/zyl910/article/details/7702466

除了基本的MMX和SSE系列指令集外,x86体系还有其他扩展指令集,例如SSE4A、AES、PCLMULQDQ等,它们也可以利用CPUID指令来检测。但是,这些指令集细碎杂多。如果像以前那样分别编写检测函数的话,那工作量太大,不值得。而且大量的函数名也会给使用带来麻烦。于是文篇探讨如何设计一套通用的检测方案。

零、指令简介

  SSE4A指令:是AMD提出的,最早出现在2007年的K10微架构的处理器上。它针对Intel的SSE4指令集修改而来,去除其中对I64优化的指令,保留图形、影音编码、3D运算、游戏等多媒体指令,并完全兼容。   AES指令:是Intel提出的,最早出现在2010年的Westmere微架构的 Core i7/i5 处理器上。能提高AES(Advanced Encryption Standard,高级加密标准)加解密性能。   PCLMULQDQ指令:是Intel提出的,最早出现在2010年的Westmere微架构处理器上。它是不进位乘法(Carryless multiply)运算,主要用于加解密处理。

  检查以下CPUID标志位可判断硬件是否支持—— CPUID(80000001).ECX.SSE4A[bit 6]=1 // 硬件支持SSE4A CPUID(1).ECX.AES[bit 25]=1 // 硬件支持AES CPUID(1).ECX.PCLMULQDQ[bit 1]=1 // 硬件支持PCLMULQDQ

  SSE4A、AES、PCLMULQDQ是基于XMM寄存器的,所以在使用前还应该检查操作系统是否支持SSE指令集。   对于支持AVX指令集的处理器(2011年的Sandy Bridge微架构),AES、PCLMULQDQ也能使用YMM寄存器。同理,在使用YMM寄存器前,应检查操作系统是否支持AVX指令集。

一、基本思路

  对于这种情况,最常见的处理办法是定义一个检测函数和一堆检测类型常数。   例如可以将检测类型常数顺序编号——

  1. #define CHECKTYPE_SSE4A 0  
  2. #define CHECKTYPE_AES   1  
  3. #define CHECKTYPE_PCLMULQDQ 2  
  4. ... 
  5. BOOL simd_check(int checktype) 
  6.     switch(checktype) 
  7.     { 
  8.         case CHECKTYPE_SSE4A: 
  9.             检测SSE4A 
  10.             break
  11.         case CHECKTYPE_AES: 
  12.             检测AES 
  13.             break
  14.         case CHECKTYPE_PCLMULQDQ: 
  15.             检测PCLMULQDQ 
  16.             break
  17.     } 
  18.     return FALSE; 
#define CHECKTYPE_SSE4A	0
#define CHECKTYPE_AES	1
#define CHECKTYPE_PCLMULQDQ	2
...
BOOL simd_check(int checktype)
{
	switch(checktype)
	{
		case CHECKTYPE_SSE4A:
			检测SSE4A
			break;
		case CHECKTYPE_AES:
			检测AES
			break;
		case CHECKTYPE_PCLMULQDQ:
			检测PCLMULQDQ
			break;
	}
	return FALSE;
}

  例如检测硬件是否支持SSE4A,就调用“simd_check(CHECKTYPE_SSE4A)”。   在实际使用这些指令时,还应该检测操作系统是否支持SSE,即这样做——

  1. if (simd_sse_level(NULL)>0) 
  2.     if (simd_check(CHECKTYPE_SSE4A)) 
  3.     { 
  4.         使用SSE4A指令 
  5.     } 
if (simd_sse_level(NULL)>0)
{
	if (simd_check(CHECKTYPE_SSE4A))
	{
		使用SSE4A指令
	}
}

 

  有了上述函数后,使用起来的确是方便了一些。但是该方案存在以下缺陷—— 1.编码量大。检测类型常数没有规律性,对于每一种检测类型,都得在switch的case分支中写一段监测代码。这些代码很相似,只是使用的常数不同。 2.扩充不易。万一以后Intel或AMD又增加基于XMM/YMM的新指令,那么又需要增加常数、修改simd_check函数。 3.功能单一。除了SIMD类指令的位标识外,CPUID还有很多丰富的信息,比如基于通用寄存器的运算指令(CRC32、POPCNT等)、系统标识等字段。对于这么多东西,如果分别编写不同的检测函数、定义好几套常数的话,那么不仅代码量大,而且用起来不方便。

二、CPUIDFIELD编号方案

  CPUID字段数据类型大致可分为4类—— 1.位。如是否支持某种指令(MMX、SSE1/2/3/3S/4.1/4.2/4A……),是否具有某种功能(PSE、PAE、APIC……)等。 2.整数。如处理器型号的Model/Family/BrandId信息,物理地址长度等。 3.字符串。如厂商、商标字符串。 4.其他。如CPUID的功能2获取缓存描述。

  第1类是最常见的,第2类也很多,而第3类、第4类就只有寥寥几种。   于是我想,有没有办法将第1类和第2类信息进行统一编号。这样就可以用一个函数获取CPUID的绝大多数信息了。

  观察CPUID文档,发现定位一个字段需要这些参数—— 1.功能号:即CPUID指令的EAX参数。常见范围是0~0Dh、80000000h~8000001Eh。(如AES是1) 2.子功能号:即CPUID指令的ECX参数。大多数时候为0,目前的最大值是62(功能0Dh)。(如AES是0) 3.寄存器:即CPUID指令返回的寄存器。是EAX、EBX、ECX、EDX这4个32位寄存器中的某一个。(如AES是ECX) 4.位偏移:该字段的最低位是32位寄存器中的哪一位。范围是0~31。(如AES是25) 5.位长:该字段的位长。对于位标识来说,位长总是1(如AES)。而对于整数型(如处理器型号的Model/Family/BrandId信息),范围是2~32。

  使用一个32位整数来对它们编号—— typedef INT32 CPUIDFIELD;

  分析一下上述参数需要多少位—— 1.功能号:理论上需要32位。 2.子功能号:理论上需要32位。但现在一般在0~62的范围内,即6位。 3.寄存器:4个寄存器,需要2位。 4.位偏移:0~31,需要5位。 5.位长:1~32,需要5位(+1编码。如0代表1,31代表32)。

  第3、4、5参数的位数已经确定,共2+5+5=12位。   对于第2个参数,虽然目前只用到6位,但考虑到十六进制的书写问题与未来发展,定为8位较好。(注:书写十六进制时,一个字符是4位,8位是两个4位)。   现在还剩下12位,可以对第1个参数进行编码。可以将高4位映射到功能号的高4位,以区分标准功能与扩展功能。然后再将低8位映射到功能号的低8位,以支持各个功能。   具体的编码方案为—— bits 31:28:功能号的高4位(bits 31:28)。 bits 27:20:功能号的低8位(bits 7:0)。 bits 19:12:子功能号的低8位(bits 7:0)。 bits 11:10:寄存器编号。0=EAX, 1=EBX, 2=ECX, 3=EDX。 bits 9:5:位长(+1编码)。 bits 4:0:位偏移。将位偏移放在最低位是为了十六进制的可读性。因为很多字段的位长为1,编码为0,这时看十六进制的最低2个字符就知道位偏移是多少。

  在C语言中定义它们的掩码与位移量——

  1. #define  CPUIDFIELD_MASK_POS    0x0000001F  // 位偏移. 0~31.  
  2. #define  CPUIDFIELD_MASK_LEN    0x000003E0  // 位长. 1~32  
  3. #define  CPUIDFIELD_MASK_REG    0x00000C00  // 寄存器. 0=EAX, 1=EBX, 2=ECX, 3=EDX.  
  4. #define  CPUIDFIELD_MASK_FIDSUB 0x000FF000  // 子功能号(低8位).  
  5. #define  CPUIDFIELD_MASK_FID    0xFFF00000  // 功能号(最高4位 和 低8位).  
  6.  
  7. #define CPUIDFIELD_SHIFT_POS    0  
  8. #define CPUIDFIELD_SHIFT_LEN    5  
  9. #define CPUIDFIELD_SHIFT_REG    10  
  10. #define CPUIDFIELD_SHIFT_FIDSUB 12  
  11. #define CPUIDFIELD_SHIFT_FID    20 
#define  CPUIDFIELD_MASK_POS	0x0000001F	// 位偏移. 0~31.
#define  CPUIDFIELD_MASK_LEN	0x000003E0	// 位长. 1~32
#define  CPUIDFIELD_MASK_REG	0x00000C00	// 寄存器. 0=EAX, 1=EBX, 2=ECX, 3=EDX.
#define  CPUIDFIELD_MASK_FIDSUB	0x000FF000	// 子功能号(低8位).
#define  CPUIDFIELD_MASK_FID	0xFFF00000	// 功能号(最高4位 和 低8位).

#define CPUIDFIELD_SHIFT_POS	0
#define CPUIDFIELD_SHIFT_LEN	5
#define CPUIDFIELD_SHIFT_REG	10
#define CPUIDFIELD_SHIFT_FIDSUB	12
#define CPUIDFIELD_SHIFT_FID	20

 

  然后再编写一些宏,用于参数的组成与拆解——

  1. #define CPUIDFIELD_MAKE(fid,fidsub,reg,pos,len) (((fid)&0xF0000000) \  
  2.     | ((fid)<<CPUIDFIELD_SHIFT_FID & 0x0FF00000) \ 
  3.     | ((fidsub)<<CPUIDFIELD_SHIFT_FIDSUB & CPUIDFIELD_MASK_FIDSUB) \ 
  4.     | ((reg)<<CPUIDFIELD_SHIFT_REG & CPUIDFIELD_MASK_REG) \ 
  5.     | ((pos)<<CPUIDFIELD_SHIFT_POS & CPUIDFIELD_MASK_POS) \ 
  6.     | (((len)-1)<<CPUIDFIELD_SHIFT_LEN & CPUIDFIELD_MASK_LEN) \ 
  7.     ) 
  8. #define CPUIDFIELD_FID(cpuidfield)  ( ((cpuidfield)&0xF0000000) | (((cpuidfield) & 0x0FF00000)>>CPUIDFIELD_SHIFT_FID) )  
  9. #define CPUIDFIELD_FIDSUB(cpuidfield)   ( ((cpuidfield) & CPUIDFIELD_MASK_FIDSUB)>>CPUIDFIELD_SHIFT_FIDSUB )  
  10. #define CPUIDFIELD_REG(cpuidfield)  ( ((cpuidfield) & CPUIDFIELD_MASK_REG)>>CPUIDFIELD_SHIFT_REG )  
  11. #define CPUIDFIELD_POS(cpuidfield)  ( ((cpuidfield) & CPUIDFIELD_MASK_POS)>>CPUIDFIELD_SHIFT_POS )  
  12. #define CPUIDFIELD_LEN(cpuidfield)  ( (((cpuidfield) & CPUIDFIELD_MASK_LEN)>>CPUIDFIELD_SHIFT_LEN) + 1 ) 
#define CPUIDFIELD_MAKE(fid,fidsub,reg,pos,len)	(((fid)&0xF0000000) \
	| ((fid)<<CPUIDFIELD_SHIFT_FID & 0x0FF00000) \
	| ((fidsub)<<CPUIDFIELD_SHIFT_FIDSUB & CPUIDFIELD_MASK_FIDSUB) \
	| ((reg)<<CPUIDFIELD_SHIFT_REG & CPUIDFIELD_MASK_REG) \
	| ((pos)<<CPUIDFIELD_SHIFT_POS & CPUIDFIELD_MASK_POS) \
	| (((len)-1)<<CPUIDFIELD_SHIFT_LEN & CPUIDFIELD_MASK_LEN) \
	)
#define CPUIDFIELD_FID(cpuidfield)	( ((cpuidfield)&0xF0000000) | (((cpuidfield) & 0x0FF00000)>>CPUIDFIELD_SHIFT_FID) )
#define CPUIDFIELD_FIDSUB(cpuidfield)	( ((cpuidfield) & CPUIDFIELD_MASK_FIDSUB)>>CPUIDFIELD_SHIFT_FIDSUB )
#define CPUIDFIELD_REG(cpuidfield)	( ((cpuidfield) & CPUIDFIELD_MASK_REG)>>CPUIDFIELD_SHIFT_REG )
#define CPUIDFIELD_POS(cpuidfield)	( ((cpuidfield) & CPUIDFIELD_MASK_POS)>>CPUIDFIELD_SHIFT_POS )
#define CPUIDFIELD_LEN(cpuidfield)	( (((cpuidfield) & CPUIDFIELD_MASK_LEN)>>CPUIDFIELD_SHIFT_LEN) + 1 )

  为了检查这些宏是否正常工作,在main函数中编写一些测试代码——

  1. //CPUIDFIELD cpuf = CPUIDFIELD_MAKE(0x8000000D,62,0,0,32);  
  2. //printf("0x%.8X\n", cpuf);  
  3. //printf("fid:\t0x%X\n", CPUIDFIELD_FID(cpuf));  
  4. //printf("fidsub:\t0x%X\n", CPUIDFIELD_FIDSUB(cpuf));  
  5. //printf("reg:\t0x%X\n", CPUIDFIELD_REG(cpuf));  
  6. //printf("pos:\t0x%X\n", CPUIDFIELD_POS(cpuf));  
  7. //printf("len:\t0x%X\n", CPUIDFIELD_LEN(cpuf)); 
	//CPUIDFIELD cpuf = CPUIDFIELD_MAKE(0x8000000D,62,0,0,32);
	//printf("0x%.8X\n", cpuf);
	//printf("fid:\t0x%X\n", CPUIDFIELD_FID(cpuf));
	//printf("fidsub:\t0x%X\n", CPUIDFIELD_FIDSUB(cpuf));
	//printf("reg:\t0x%X\n", CPUIDFIELD_REG(cpuf));
	//printf("pos:\t0x%X\n", CPUIDFIELD_POS(cpuf));
	//printf("len:\t0x%X\n", CPUIDFIELD_LEN(cpuf));

  现在可以为SSE4A、AES、PCLMULQDQ定义常数了——

  1. #define CPUF_SSE4A  CPUIDFIELD_MAKE(0x80000001,0,2,6,1)  
  2. #define CPUF_AES    CPUIDFIELD_MAKE(1,0,2,25,1)  
  3. #define CPUF_PCLMULQDQ  CPUIDFIELD_MAKE(1,0,2,1,1) 
#define CPUF_SSE4A	CPUIDFIELD_MAKE(0x80000001,0,2,6,1)
#define CPUF_AES	CPUIDFIELD_MAKE(1,0,2,25,1)
#define CPUF_PCLMULQDQ	CPUIDFIELD_MAKE(1,0,2,1,1)


三、读取函数

  有了CPUIDFIELD编号方案后,读取函数就很容易编写了。   虽然可以将代码全部写在一个函数中。但是为了提高代码的可读性和可复用性,将它分成2个函数与1个宏会更好——

  1. // 取得位域  
  2. #ifndef __GETBITS32  
  3. #define __GETBITS32(src,pos,len)    ( ((src)>>(pos)) & (((UINT32)-1)>>(32-len)) )  
  4. #endif  
  5.  
  6. // 根据CPUIDFIELD从缓冲区中获取字段.  
  7. UINT32  getcpuidfield_buf(constINT32 dwBuf[4], CPUIDFIELD cpuf) 
  8.     return __GETBITS32(dwBuf[CPUIDFIELD_REG(cpuf)], CPUIDFIELD_POS(cpuf), CPUIDFIELD_LEN(cpuf)); 
  9.  
  10. // 根据CPUIDFIELD获取CPUID字段.  
  11. UINT32  getcpuidfield(CPUIDFIELD cpuf) 
  12.     INT32 dwBuf[4]; 
  13.     __cpuidex(dwBuf, CPUIDFIELD_FID(cpuf), CPUIDFIELD_FIDSUB(cpuf)); 
  14.     return getcpuidfield_buf(dwBuf, cpuf); 
// 取得位域
#ifndef __GETBITS32
#define __GETBITS32(src,pos,len)	( ((src)>>(pos)) & (((UINT32)-1)>>(32-len)) )
#endif

// 根据CPUIDFIELD从缓冲区中获取字段.
UINT32	getcpuidfield_buf(const INT32 dwBuf[4], CPUIDFIELD cpuf)
{
	return __GETBITS32(dwBuf[CPUIDFIELD_REG(cpuf)], CPUIDFIELD_POS(cpuf), CPUIDFIELD_LEN(cpuf));
}

// 根据CPUIDFIELD获取CPUID字段.
UINT32	getcpuidfield(CPUIDFIELD cpuf)
{
	INT32 dwBuf[4];
	__cpuidex(dwBuf, CPUIDFIELD_FID(cpuf), CPUIDFIELD_FIDSUB(cpuf));
	return getcpuidfield_buf(dwBuf, cpuf);
}

  说明—— __GETBITS32:专门用于提取位域。它是常用的位运算操作,为了避免重复定义,用宏比较好。 getcpuidfield:标准的获取CPUID字段函数。用法很简单,只需传递一个CPUIDFIELD参数就行了。 getcpuidfield_buf:有时候需要一次获得多个CPUID字段,并且已经知道它们属于同一套功能号。这时为了提高效率,可以先用__cpuidex获得那4个寄存器的信息,然后分别调用getcpuidfield_buf。

  范例——

  1. printf("SSE4A: %d\n", getcpuidfield(CPUF_SSE4A)); 
  2. printf("AES: %d\n", getcpuidfield(CPUF_AES)); 
  3. printf("PCLMULQDQ: %d\n", getcpuidfield(CPUF_PCLMULQDQ)); 
	printf("SSE4A: %d\n", getcpuidfield(CPUF_SSE4A));
	printf("AES: %d\n", getcpuidfield(CPUF_AES));
	printf("PCLMULQDQ: %d\n", getcpuidfield(CPUF_PCLMULQDQ));

四、全部代码

  全部代码——

  1. #include <windows.h>  
  2. #include <stdio.h>  
  3. #include <conio.h>  
  4. #include <tchar.h>  
  5.  
  6. #if _MSC_VER >=1400  // VC2005才支持intrin.h  
  7. #include <intrin.h>   // 所有Intrinsics函数  
  8. #else  
  9. #include <emmintrin.h>    // MMX, SSE, SSE2  
  10. #endif  
  11.  
  12.  
  13. // CPUIDFIELD  
  14. typedefINT32 CPUIDFIELD; 
  15.  
  16. #define  CPUIDFIELD_MASK_POS    0x0000001F  // 位偏移. 0~31.  
  17. #define  CPUIDFIELD_MASK_LEN    0x000003E0  // 位长. 1~32  
  18. #define  CPUIDFIELD_MASK_REG    0x00000C00  // 寄存器. 0=EAX, 1=EBX, 2=ECX, 3=EDX.  
  19. #define  CPUIDFIELD_MASK_FIDSUB 0x000FF000  // 子功能号(低8位).  
  20. #define  CPUIDFIELD_MASK_FID    0xFFF00000  // 功能号(最高4位 和 低8位).  
  21.  
  22. #define CPUIDFIELD_SHIFT_POS    0  
  23. #define CPUIDFIELD_SHIFT_LEN    5  
  24. #define CPUIDFIELD_SHIFT_REG    10  
  25. #define CPUIDFIELD_SHIFT_FIDSUB 12  
  26. #define CPUIDFIELD_SHIFT_FID    20  
  27.  
  28. #define CPUIDFIELD_MAKE(fid,fidsub,reg,pos,len) (((fid)&0xF0000000) \  
  29.     | ((fid)<<CPUIDFIELD_SHIFT_FID & 0x0FF00000) \ 
  30.     | ((fidsub)<<CPUIDFIELD_SHIFT_FIDSUB & CPUIDFIELD_MASK_FIDSUB) \ 
  31.     | ((reg)<<CPUIDFIELD_SHIFT_REG & CPUIDFIELD_MASK_REG) \ 
  32.     | ((pos)<<CPUIDFIELD_SHIFT_POS & CPUIDFIELD_MASK_POS) \ 
  33.     | (((len)-1)<<CPUIDFIELD_SHIFT_LEN & CPUIDFIELD_MASK_LEN) \ 
  34.     ) 
  35. #define CPUIDFIELD_FID(cpuidfield)  ( ((cpuidfield)&0xF0000000) | (((cpuidfield) & 0x0FF00000)>>CPUIDFIELD_SHIFT_FID) )  
  36. #define CPUIDFIELD_FIDSUB(cpuidfield)   ( ((cpuidfield) & CPUIDFIELD_MASK_FIDSUB)>>CPUIDFIELD_SHIFT_FIDSUB )  
  37. #define CPUIDFIELD_REG(cpuidfield)  ( ((cpuidfield) & CPUIDFIELD_MASK_REG)>>CPUIDFIELD_SHIFT_REG )  
  38. #define CPUIDFIELD_POS(cpuidfield)  ( ((cpuidfield) & CPUIDFIELD_MASK_POS)>>CPUIDFIELD_SHIFT_POS )  
  39. #define CPUIDFIELD_LEN(cpuidfield)  ( (((cpuidfield) & CPUIDFIELD_MASK_LEN)>>CPUIDFIELD_SHIFT_LEN) + 1 )  
  40.  
  41. // 取得位域  
  42. #ifndef __GETBITS32  
  43. #define __GETBITS32(src,pos,len)    ( ((src)>>(pos)) & (((UINT32)-1)>>(32-len)) )  
  44. #endif  
  45.  
  46.  
  47. #define CPUF_SSE4A  CPUIDFIELD_MAKE(0x80000001,0,2,6,1)  
  48. #define CPUF_AES    CPUIDFIELD_MAKE(1,0,2,25,1)  
  49. #define CPUF_PCLMULQDQ  CPUIDFIELD_MAKE(1,0,2,1,1)  
  50.  
  51.  
  52. // SSE系列指令集的支持级别. simd_sse_level 函数的返回值。  
  53. #define SIMD_SSE_NONE   0   // 不支持  
  54. #define SIMD_SSE_1  1   // SSE  
  55. #define SIMD_SSE_2  2   // SSE2  
  56. #define SIMD_SSE_3  3   // SSE3  
  57. #define SIMD_SSE_3S 4   // SSSE3  
  58. #define SIMD_SSE_41 5   // SSE4.1  
  59. #define SIMD_SSE_42 6   // SSE4.2  
  60.  
  61. constchar* simd_sse_names[] = { 
  62.     "None"
  63.     "SSE"
  64.     "SSE2"
  65.     "SSE3"
  66.     "SSSE3"
  67.     "SSE4.1"
  68.     "SSE4.2"
  69. }; 
  70.  
  71.  
  72.  
  73.  
  74. char szBuf[64]; 
  75. INT32 dwBuf[4]; 
  76.  
  77. #if defined(_WIN64)  
  78. // 64位下不支持内联汇编. 应使用__cpuid、__cpuidex等Intrinsics函数。  
  79. #else  
  80. #if _MSC_VER < 1600  // VS2010. 据说VC2008 SP1之后才支持__cpuidex  
  81. void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue) 
  82.     if (NULL==CPUInfo)  return
  83.     _asm{ 
  84.         // load. 读取参数到寄存器  
  85.         mov edi, CPUInfo;   // 准备用edi寻址CPUInfo  
  86.         mov eax, InfoType; 
  87.         mov ecx, ECXValue; 
  88.         // CPUID  
  89.         cpuid; 
  90.         // save. 将寄存器保存到CPUInfo  
  91.         mov [edi], eax; 
  92.         mov [edi+4], ebx; 
  93.         mov [edi+8], ecx; 
  94.         mov [edi+12], edx; 
  95.     } 
  96. #endif  // #if _MSC_VER < 1600   // VS2010. 据说VC2008 SP1之后才支持__cpuidex  
  97.  
  98. #if _MSC_VER < 1400  // VC2005才支持__cpuid  
  99. void __cpuid(INT32 CPUInfo[4], INT32 InfoType) 
  100.     __cpuidex(CPUInfo, InfoType, 0); 
  101. #endif  // #if _MSC_VER < 1400   // VC2005才支持__cpuid  
  102.  
  103. #endif  // #if defined(_WIN64)  
  104.  
  105. // 根据CPUIDFIELD从缓冲区中获取字段.  
  106. UINT32  getcpuidfield_buf(constINT32 dwBuf[4], CPUIDFIELD cpuf) 
  107.     return __GETBITS32(dwBuf[CPUIDFIELD_REG(cpuf)], CPUIDFIELD_POS(cpuf), CPUIDFIELD_LEN(cpuf)); 
  108.  
  109. // 根据CPUIDFIELD获取CPUID字段.  
  110. UINT32  getcpuidfield(CPUIDFIELD cpuf) 
  111.     INT32 dwBuf[4]; 
  112.     __cpuidex(dwBuf, CPUIDFIELD_FID(cpuf), CPUIDFIELD_FIDSUB(cpuf)); 
  113.     return getcpuidfield_buf(dwBuf, cpuf); 
  114.  
  115. // 取得CPU厂商(Vendor)  
  116. //  
  117. // result: 成功时返回字符串的长度(一般为12)。失败时返回0。  
  118. // pvendor: 接收厂商信息的字符串缓冲区。至少为13字节。  
  119. int cpu_getvendor(char* pvendor) 
  120.     INT32 dwBuf[4]; 
  121.     if (NULL==pvendor)  return 0; 
  122.     // Function 0: Vendor-ID and Largest Standard Function  
  123.     __cpuid(dwBuf, 0); 
  124.     // save. 保存到pvendor  
  125.     *(INT32*)&pvendor[0] = dwBuf[1];    // ebx: 前四个字符  
  126.     *(INT32*)&pvendor[4] = dwBuf[3];    // edx: 中间四个字符  
  127.     *(INT32*)&pvendor[8] = dwBuf[2];    // ecx: 最后四个字符  
  128.     pvendor[12] = '\0'
  129.     return 12; 
  130.  
  131. // 取得CPU商标(Brand)  
  132. //  
  133. // result: 成功时返回字符串的长度(一般为48)。失败时返回0。  
  134. // pbrand: 接收商标信息的字符串缓冲区。至少为49字节。  
  135. int cpu_getbrand(char* pbrand) 
  136.     INT32 dwBuf[4]; 
  137.     if (NULL==pbrand)   return 0; 
  138.     // Function 0x80000000: Largest Extended Function Number  
  139.     __cpuid(dwBuf, 0x80000000); 
  140.     if (dwBuf[0] < 0x80000004)   return 0; 
  141.     // Function 80000002h,80000003h,80000004h: Processor Brand String  
  142.     __cpuid((INT32*)&pbrand[0], 0x80000002);    // 前16个字符  
  143.     __cpuid((INT32*)&pbrand[16], 0x80000003);   // 中间16个字符  
  144.     __cpuid((INT32*)&pbrand[32], 0x80000004);   // 最后16个字符  
  145.     pbrand[48] = '\0'
  146.     return 48; 
  147.  
  148.  
  149. // 是否支持MMX指令集  
  150. BOOL    simd_mmx(BOOL* phwmmx) 
  151.     constINT32 BIT_D_MMX = 0x00800000; // bit 23  
  152.     BOOL    rt = FALSE; // result  
  153.     INT32 dwBuf[4]; 
  154.  
  155.     // check processor support  
  156.     __cpuid(dwBuf, 1);  // Function 1: Feature Information  
  157.     if ( dwBuf[3] & BIT_D_MMX ) rt=TRUE; 
  158.     if (NULL!=phwmmx)   *phwmmx=rt; 
  159.  
  160.     // check OS support  
  161.     if ( rt ) 
  162.     { 
  163. #if defined(_WIN64)  
  164.         // VC编译器不支持64位下的MMX。  
  165.         rt=FALSE; 
  166. #else  
  167.         __try  
  168.         { 
  169.             _mm_empty();    // MMX instruction: emms  
  170.         } 
  171.         __except (EXCEPTION_EXECUTE_HANDLER) 
  172.         { 
  173.             rt=FALSE; 
  174.         } 
  175. #endif  // #if defined(_WIN64)  
  176.     } 
  177.  
  178.     return rt; 
  179.  
  180. // 检测SSE系列指令集的支持级别  
  181. int simd_sse_level(int* phwsse) 
  182.     constINT32 BIT_D_SSE = 0x02000000; // bit 25  
  183.     constINT32 BIT_D_SSE2 = 0x04000000;    // bit 26  
  184.     constINT32 BIT_C_SSE3 = 0x00000001;    // bit 0  
  185.     constINT32 BIT_C_SSSE3 = 0x00000100;   // bit 9  
  186.     constINT32 BIT_C_SSE41 = 0x00080000;   // bit 19  
  187.     constINT32 BIT_C_SSE42 = 0x00100000;   // bit 20  
  188.     int rt = SIMD_SSE_NONE; // result  
  189.     INT32 dwBuf[4]; 
  190.  
  191.     // check processor support  
  192.     __cpuid(dwBuf, 1);  // Function 1: Feature Information  
  193.     if ( dwBuf[3] & BIT_D_SSE ) 
  194.     { 
  195.         rt = SIMD_SSE_1; 
  196.         if ( dwBuf[3] & BIT_D_SSE2 ) 
  197.         { 
  198.             rt = SIMD_SSE_2; 
  199.             if ( dwBuf[2] & BIT_C_SSE3 ) 
  200.             { 
  201.                 rt = SIMD_SSE_3; 
  202.                 if ( dwBuf[2] & BIT_C_SSSE3 ) 
  203.                 { 
  204.                     rt = SIMD_SSE_3S; 
  205.                     if ( dwBuf[2] & BIT_C_SSE41 ) 
  206.                     { 
  207.                         rt = SIMD_SSE_41; 
  208.                         if ( dwBuf[2] & BIT_C_SSE42 ) 
  209.                         { 
  210.                             rt = SIMD_SSE_42; 
  211.                         } 
  212.                     } 
  213.                 } 
  214.             } 
  215.         } 
  216.     } 
  217.     if (NULL!=phwsse)   *phwsse=rt; 
  218.  
  219.     // check OS support  
  220.     __try  
  221.     { 
  222.         __m128 xmm1 = _mm_setzero_ps(); // SSE instruction: xorps  
  223.         if (0!=*(int*)&xmm1)    rt = SIMD_SSE_NONE; // 避免Release模式编译优化时剔除上一条语句  
  224.     } 
  225.     __except (EXCEPTION_EXECUTE_HANDLER) 
  226.     { 
  227.         rt = SIMD_SSE_NONE; 
  228.     } 
  229.  
  230.     return rt; 
  231.  
  232.  
  233. int _tmain(int argc, _TCHAR* argv[]) 
  234.     //__cpuidex(dwBuf, 0,0);  
  235.     //__cpuid(dwBuf, 0);  
  236.     //printf("%.8X\t%.8X\t%.8X\t%.8X\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]);  
  237.  
  238.     cpu_getvendor(szBuf); 
  239.     printf("CPU Vendor:\t%s\n", szBuf); 
  240.  
  241.     cpu_getbrand(szBuf); 
  242.     printf("CPU Name:\t%s\n", szBuf); 
  243.  
  244.     BOOL bhwmmx;    // 硬件支持MMX.  
  245.     BOOL bmmx;  // 操作系统支持MMX.  
  246.     bmmx = simd_mmx(&bhwmmx); 
  247.     printf("MMX: %d\t// hw: %d\n", bmmx, bhwmmx); 
  248.  
  249.     int nhwsse; // 硬件支持SSE.  
  250.     int nsse;   // 操作系统支持SSE.  
  251.     nsse = simd_sse_level(&nhwsse); 
  252.     printf("SSE: %d\t// hw: %d\n", nsse, nhwsse); 
  253.     for(int i=1; i<sizeof(simd_sse_names); ++i) 
  254.     { 
  255.         if (nhwsse>=i)   printf("\t%s\n", simd_sse_names[i]); 
  256.     } 
  257.  
  258.     // test CPUIDFIELD  
  259.     //CPUIDFIELD cpuf = CPUIDFIELD_MAKE(0x8000000D,62,0,0,32);  
  260.     //printf("0x%.8X\n", cpuf);  
  261.     //printf("fid:\t0x%X\n", CPUIDFIELD_FID(cpuf));  
  262.     //printf("fidsub:\t0x%X\n", CPUIDFIELD_FIDSUB(cpuf));  
  263.     //printf("reg:\t0x%X\n", CPUIDFIELD_REG(cpuf));  
  264.     //printf("pos:\t0x%X\n", CPUIDFIELD_POS(cpuf));  
  265.     //printf("len:\t0x%X\n", CPUIDFIELD_LEN(cpuf));  
  266.  
  267.     // test SSE4A/AES/PCLMULQDQ  
  268.     printf("SSE4A: %d\n", getcpuidfield(CPUF_SSE4A)); 
  269.     printf("AES: %d\n", getcpuidfield(CPUF_AES)); 
  270.     printf("PCLMULQDQ: %d\n", getcpuidfield(CPUF_PCLMULQDQ)); 
  271.  
  272.     return 0; 
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#if _MSC_VER >=1400	// VC2005才支持intrin.h
#include <intrin.h>	// 所有Intrinsics函数
#else
#include <emmintrin.h>	// MMX, SSE, SSE2
#endif


// CPUIDFIELD
typedef INT32 CPUIDFIELD;

#define  CPUIDFIELD_MASK_POS	0x0000001F	// 位偏移. 0~31.
#define  CPUIDFIELD_MASK_LEN	0x000003E0	// 位长. 1~32
#define  CPUIDFIELD_MASK_REG	0x00000C00	// 寄存器. 0=EAX, 1=EBX, 2=ECX, 3=EDX.
#define  CPUIDFIELD_MASK_FIDSUB	0x000FF000	// 子功能号(低8位).
#define  CPUIDFIELD_MASK_FID	0xFFF00000	// 功能号(最高4位 和 低8位).

#define CPUIDFIELD_SHIFT_POS	0
#define CPUIDFIELD_SHIFT_LEN	5
#define CPUIDFIELD_SHIFT_REG	10
#define CPUIDFIELD_SHIFT_FIDSUB	12
#define CPUIDFIELD_SHIFT_FID	20

#define CPUIDFIELD_MAKE(fid,fidsub,reg,pos,len)	(((fid)&0xF0000000) \
	| ((fid)<<CPUIDFIELD_SHIFT_FID & 0x0FF00000) \
	| ((fidsub)<<CPUIDFIELD_SHIFT_FIDSUB & CPUIDFIELD_MASK_FIDSUB) \
	| ((reg)<<CPUIDFIELD_SHIFT_REG & CPUIDFIELD_MASK_REG) \
	| ((pos)<<CPUIDFIELD_SHIFT_POS & CPUIDFIELD_MASK_POS) \
	| (((len)-1)<<CPUIDFIELD_SHIFT_LEN & CPUIDFIELD_MASK_LEN) \
	)
#define CPUIDFIELD_FID(cpuidfield)	( ((cpuidfield)&0xF0000000) | (((cpuidfield) & 0x0FF00000)>>CPUIDFIELD_SHIFT_FID) )
#define CPUIDFIELD_FIDSUB(cpuidfield)	( ((cpuidfield) & CPUIDFIELD_MASK_FIDSUB)>>CPUIDFIELD_SHIFT_FIDSUB )
#define CPUIDFIELD_REG(cpuidfield)	( ((cpuidfield) & CPUIDFIELD_MASK_REG)>>CPUIDFIELD_SHIFT_REG )
#define CPUIDFIELD_POS(cpuidfield)	( ((cpuidfield) & CPUIDFIELD_MASK_POS)>>CPUIDFIELD_SHIFT_POS )
#define CPUIDFIELD_LEN(cpuidfield)	( (((cpuidfield) & CPUIDFIELD_MASK_LEN)>>CPUIDFIELD_SHIFT_LEN) + 1 )

// 取得位域
#ifndef __GETBITS32
#define __GETBITS32(src,pos,len)	( ((src)>>(pos)) & (((UINT32)-1)>>(32-len)) )
#endif


#define CPUF_SSE4A	CPUIDFIELD_MAKE(0x80000001,0,2,6,1)
#define CPUF_AES	CPUIDFIELD_MAKE(1,0,2,25,1)
#define CPUF_PCLMULQDQ	CPUIDFIELD_MAKE(1,0,2,1,1)


// SSE系列指令集的支持级别. simd_sse_level 函数的返回值。
#define SIMD_SSE_NONE	0	// 不支持
#define SIMD_SSE_1	1	// SSE
#define SIMD_SSE_2	2	// SSE2
#define SIMD_SSE_3	3	// SSE3
#define SIMD_SSE_3S	4	// SSSE3
#define SIMD_SSE_41	5	// SSE4.1
#define SIMD_SSE_42	6	// SSE4.2

const char*	simd_sse_names[] = {
	"None",
	"SSE",
	"SSE2",
	"SSE3",
	"SSSE3",
	"SSE4.1",
	"SSE4.2",
};




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)

// 根据CPUIDFIELD从缓冲区中获取字段.
UINT32	getcpuidfield_buf(const INT32 dwBuf[4], CPUIDFIELD cpuf)
{
	return __GETBITS32(dwBuf[CPUIDFIELD_REG(cpuf)], CPUIDFIELD_POS(cpuf), CPUIDFIELD_LEN(cpuf));
}

// 根据CPUIDFIELD获取CPUID字段.
UINT32	getcpuidfield(CPUIDFIELD cpuf)
{
	INT32 dwBuf[4];
	__cpuidex(dwBuf, CPUIDFIELD_FID(cpuf), CPUIDFIELD_FIDSUB(cpuf));
	return getcpuidfield_buf(dwBuf, cpuf);
}

// 取得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;
}


// 是否支持MMX指令集
BOOL	simd_mmx(BOOL* phwmmx)
{
	const INT32	BIT_D_MMX = 0x00800000;	// bit 23
	BOOL	rt = FALSE;	// result
	INT32 dwBuf[4];

	// check processor support
	__cpuid(dwBuf, 1);	// Function 1: Feature Information
	if ( dwBuf[3] & BIT_D_MMX )	rt=TRUE;
	if (NULL!=phwmmx)	*phwmmx=rt;

	// check OS support
	if ( rt )
	{
#if defined(_WIN64)
		// VC编译器不支持64位下的MMX。
		rt=FALSE;
#else
		__try 
		{
			_mm_empty();	// MMX instruction: emms
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			rt=FALSE;
		}
#endif	// #if defined(_WIN64)
	}

	return rt;
}

// 检测SSE系列指令集的支持级别
int	simd_sse_level(int* phwsse)
{
	const INT32	BIT_D_SSE = 0x02000000;	// bit 25
	const INT32	BIT_D_SSE2 = 0x04000000;	// bit 26
	const INT32	BIT_C_SSE3 = 0x00000001;	// bit 0
	const INT32	BIT_C_SSSE3 = 0x00000100;	// bit 9
	const INT32	BIT_C_SSE41 = 0x00080000;	// bit 19
	const INT32	BIT_C_SSE42 = 0x00100000;	// bit 20
	int	rt = SIMD_SSE_NONE;	// result
	INT32 dwBuf[4];

	// check processor support
	__cpuid(dwBuf, 1);	// Function 1: Feature Information
	if ( dwBuf[3] & BIT_D_SSE )
	{
		rt = SIMD_SSE_1;
		if ( dwBuf[3] & BIT_D_SSE2 )
		{
			rt = SIMD_SSE_2;
			if ( dwBuf[2] & BIT_C_SSE3 )
			{
				rt = SIMD_SSE_3;
				if ( dwBuf[2] & BIT_C_SSSE3 )
				{
					rt = SIMD_SSE_3S;
					if ( dwBuf[2] & BIT_C_SSE41 )
					{
						rt = SIMD_SSE_41;
						if ( dwBuf[2] & BIT_C_SSE42 )
						{
							rt = SIMD_SSE_42;
						}
					}
				}
			}
		}
	}
	if (NULL!=phwsse)	*phwsse=rt;

	// check OS support
	__try 
	{
		__m128 xmm1 = _mm_setzero_ps();	// SSE instruction: xorps
		if (0!=*(int*)&xmm1)	rt = SIMD_SSE_NONE;	// 避免Release模式编译优化时剔除上一条语句
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		rt = SIMD_SSE_NONE;
	}

	return rt;
}


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);

	BOOL bhwmmx;	// 硬件支持MMX.
	BOOL bmmx;	// 操作系统支持MMX.
	bmmx = simd_mmx(&bhwmmx);
	printf("MMX: %d\t// hw: %d\n", bmmx, bhwmmx);

	int	nhwsse;	// 硬件支持SSE.
	int	nsse;	// 操作系统支持SSE.
	nsse = simd_sse_level(&nhwsse);
	printf("SSE: %d\t// hw: %d\n", nsse, nhwsse);
	for(int i=1; i<sizeof(simd_sse_names); ++i)
	{
		if (nhwsse>=i)	printf("\t%s\n", simd_sse_names[i]);
	}

	// test CPUIDFIELD
	//CPUIDFIELD cpuf = CPUIDFIELD_MAKE(0x8000000D,62,0,0,32);
	//printf("0x%.8X\n", cpuf);
	//printf("fid:\t0x%X\n", CPUIDFIELD_FID(cpuf));
	//printf("fidsub:\t0x%X\n", CPUIDFIELD_FIDSUB(cpuf));
	//printf("reg:\t0x%X\n", CPUIDFIELD_REG(cpuf));
	//printf("pos:\t0x%X\n", CPUIDFIELD_POS(cpuf));
	//printf("len:\t0x%X\n", CPUIDFIELD_LEN(cpuf));

	// test SSE4A/AES/PCLMULQDQ
	printf("SSE4A: %d\n", getcpuidfield(CPUF_SSE4A));
	printf("AES: %d\n", getcpuidfield(CPUF_AES));
	printf("PCLMULQDQ: %d\n", getcpuidfield(CPUF_PCLMULQDQ));

	return 0;
}

 

  在以下编译器中成功编译—— VC6(32位) VC2003(32位) VC2005(32位) VC2010(32位、64位)

五、测试结果

  在64位的win7中运行“x64\Release\getcpuidfield_2010.exe”,运行效果——

  利用cmdarg_ui运行“Debug\getcpuidfield.exe”,顺便测试WinXP与VC6——

 

参考文献—— 《Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B, and 3C》. May 2012. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.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 《AMD64 Architecture Programmer's Manual Volume 3: General Purpose and System
Instructions》. December 2011. http://support.amd.com/us/Processor_TechDocs/24594_APM_v3.pdf 《AMD CPUID Specification》. September 2010. http://support.amd.com/us/Embedded_TechDocs/25481.pdf 《x86 architecture CPUID》. http://www.sandpile.org/x86/cpuid.htm 《[x86]SIMD指令集发展历程表(MMX、SSE、AVX等)》. http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html 《如何在各个版本的VC及64位下使用CPUID指令》. http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html 《[VC兼容32位和64位] 检查MMX和SSE系列指令集的支持级别》. http://www.cnblogs.com/zyl910/archive/2012/05/25/checksimd64.html 《[C#] cmdarg_ui:“简单参数命令行程序”的通用图形界面》.  http://www.cnblogs.com/zyl910/archive/2012/06/19/cmdarg_ui.html

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

posted @ 2012-12-23 16:19  Goncely  阅读(767)  评论(0)    收藏  举报