在之前写的文章中,仅简单讲了一下CPUID指令,通过该指令可以获取很多和处理器相关的信息,如处理器的系列、型号、内存地址是多少位,等等。本文在Linux环境下,使用C语言内嵌汇编的手段使用CPUID指令,进而在高级语言层面上看到获取的信息。
头文件cpuid.h代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| /* * 假设执行程序所在PC是支持CPUID指令的 */ #ifndef _CPUID_H #define _CPUID_H #include <stdint.h> void get_cpu_vendor(char* cpu_vendor, int* cpuid_level); void get_cpu_version(unsigned int* version); void get_cpu_fms(int* family, int* model, int* stepping); void get_cpu_name(char *processor_name); void get_address_bits(int* linear, int* physical); //////////////////////////////////////////// struct cpuid_result { uint32_t eax; uint32_t ebx; uint32_t ecx; uint32_t edx; }; /* * Generic CPUID function */ static inline struct cpuid_result cpuid(int op) { struct cpuid_result result; asm volatile( "mov %%ebx, %%edi;" "cpuid;" "mov %%ebx, %%esi;" "mov %%edi, %%ebx;" : "=a" (result.eax), "=S" (result.ebx), "=c" (result.ecx), "=d" (result.edx) : "0" (op) : "edi"); return result; }
static inline unsigned int cpuid_eax(unsigned int op) { struct cpuid_result regs; regs = cpuid(op); return regs.eax; }
|
注:感谢网友五年一剑指正cpuid.h文件缺少的cpuid_eax函数,已补充。
实现文件cpuid.c代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "cpuid.h" void get_cpu_vendor(char* cpu_vendor, int* cpuid_level) { char vendor_name[16]; vendor_name[0] = '\0'; /* Unset */ int level = 0; struct cpuid_result result; result = cpuid(0x00000000); // eax为0表示读取vendor id,一共12字节,依次在ebx、edx、ecx。 level = result.eax; vendor_name[ 0] = (result.ebx >> 0) & 0xff; vendor_name[ 1] = (result.ebx >> 8) & 0xff; vendor_name[ 2] = (result.ebx >> 16) & 0xff; vendor_name[ 3] = (result.ebx >> 24) & 0xff; vendor_name[ 4] = (result.edx >> 0) & 0xff; vendor_name[ 5] = (result.edx >> 8) & 0xff; vendor_name[ 6] = (result.edx >> 16) & 0xff; vendor_name[ 7] = (result.edx >> 24) & 0xff; vendor_name[ 8] = (result.ecx >> 0) & 0xff; vendor_name[ 9] = (result.ecx >> 8) & 0xff; vendor_name[10] = (result.ecx >> 16) & 0xff; vendor_name[11] = (result.ecx >> 24) & 0xff; vendor_name[12] = '\0'; //printf("vendor_name: %s\n", vendor_name); strcpy(cpu_vendor, vendor_name); *cpuid_level = level; } void get_cpu_version(unsigned int* version) { unsigned int tmp = 0; tmp = cpuid_eax(0x00000001); *version = tmp; } struct cpuinfo_x86 { uint8_t x86; /* CPU family */ uint8_t x86_vendor; /* CPU vendor */ uint8_t x86_model; /* CPU model */ uint8_t x86_step; /* CPU stepping */ }; // 参考IA32开发手册第2卷第3章。CPUID exa==0x01的图3-6 static inline void get_fms(struct cpuinfo_x86 *c, uint32_t tfms) { c->x86 = (tfms >> 8) & 0xf; c->x86_model = (tfms >> 4) & 0xf; c->x86_step = tfms & 0xf; if (c->x86 == 0xf) c->x86 += (tfms >> 20) & 0xff; if (c->x86 >= 0x6) c->x86_model += ((tfms >> 16) & 0xF) << 4; } void get_cpu_fms(int* family, int* model, int* stepping) { struct cpuinfo_x86 c; unsigned int ver = 0; ver = cpuid_eax(0x00000001); get_fms(&c, ver); *family = c.x86; *model = c.x86_model; *stepping = c.x86_step; } void get_cpu_name(char* processor_name) { struct cpuid_result regs; char temp_processor_name[49]; char* processor_name_start; unsigned int *name_as_ints = (unsigned int *)temp_processor_name; int i; /* 用cpuid指令,eax传入0x80000002/0x80000003/0x80000004, 共3个,每个4个寄存器,每个寄存器4字节,故一共48字节。 参考IA32开发手册第2卷第3章。 */ for (i = 0; i < 3; i++) { regs = cpuid(0x80000002 + i); name_as_ints[i * 4 + 0] = regs.eax; name_as_ints[i * 4 + 1] = regs.ebx; name_as_ints[i * 4 + 2] = regs.ecx; name_as_ints[i * 4 + 3] = regs.edx; } temp_processor_name[49] = '\0'; // 最后的字节为0,结束 /* Skip leading spaces. */ processor_name_start = temp_processor_name; while (*processor_name_start == ' ') processor_name_start++; memset(processor_name, 0, 49); strcpy(processor_name, processor_name_start); } void get_address_bits(int* linear, int* physical) { unsigned int tmp = 0; tmp = cpuid_eax(0x80000008); *linear = (tmp>>8) & 0xff; *physical = (tmp>>0) & 0xff; }
|
主函数实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "cpuid.h" #include "msr.h" int main(void) { char buffer[49] = {0}; unsigned int version = 0; int cpuid_level; get_cpu_vendor(buffer, &cpuid_level); printf("vendor_id\t: %s\n", buffer); printf("cpuid level\t: %d\n", cpuid_level); get_cpu_name(buffer); printf("model name\t: %s\n", buffer); get_cpu_version(&version); int f,m,s; get_cpu_fms(&f, &m, &s); printf("cpu family\t: %d(0x%X)\n", f, f); printf("model\t\t: %d(0x%X)\n", m, m); printf("stepping\t: %d(0x%X)\n", s, s); int phy_bits = 0; int vir_bits = 0; get_address_bits(&vir_bits, &phy_bits); printf("address sizes\t: %d bits physical, %d bits virtual\n", phy_bits, vir_bits); return 0; }
|
在Linux系统中,有现成的方法获取CPU信息,比如使用lscpu命令。另外还可以直接查看/proc/cpuinfo文件。使用“cat /proc/cpuinfo”命令查看CPU信息如下(有精简):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| # cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 58 model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz stepping : 9 physical id : 0 siblings : 8 core id : 3 cpu cores : 4 apicid : 7 initial apicid : 7 fdiv_bug : no hlt_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 13 wp : yes bogomips : 6784.28 clflush size : 64 cache_alignment : 64 address sizes : 36 bits physical, 48 bits virtual power management:
|
上述程序运行结果如下:
1 2 3 4 5 6 7
| vendor_id : GenuineIntel level : 13 model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz cpu family : 6(0x6) model : 58(0x3A) stepping : 9(0x9) address sizes : 36 bits physical, 48 bits virtual
|
可以看到,两者信息高度一致。
Intel处理器的CPUID远不止上文所述,详情请参考Intel IA32软件开发手册。
李迟 2016.3.7 周一 晚