如何通过 CPUID 命令使用 C/C++ 获取物理和虚拟地址位
Posted
技术标签:
【中文标题】如何通过 CPUID 命令使用 C/C++ 获取物理和虚拟地址位【英文标题】:How to get physical and virtual address bits with C/C++ by CPUID command 【发布时间】:2021-02-07 07:37:16 【问题描述】:我通过在 Windows 中使用 CPUID 命令获取 C 语言的物理和虚拟地址位大小。 我可以通过这种方式获取处理器信息,但我对获取地址位感到困惑。 看起来我应该给你 80000008 指令,但我这样做,只显示 7-8 位连续变化。 我想了解这个命令是如何工作的并解决这个问题
#include <stdio.h>
void getcpuid(int T, int* val)
int reg_ax;
int reg_bx;
int reg_cx;
int reg_dx;
__asm
mov eax, T;
cpuid;
mov reg_ax, eax;
mov reg_bx, ebx;
mov reg_cx, ecx;
mov reg_dx, edx;
*(val + 0) = reg_ax;
*(val + 1) = reg_bx;
*(val + 2) = reg_cx;
*(val + 3) = reg_dx;
int main()
int val[5]; val[4] = 0;
getcpuid(0x80000002, val);
printf("%s\r\n", &val[0]);
getcpuid(0x80000003, val);
printf("%s\r\n", &val[0]);
getcpuid(0x80000004, val);
printf("%s\r\n", &val[0]);
return 0;
当使用 EAX = 80000002, 80000003, 80000004 操作此代码时,会显示 Intel 处理器品牌字符串。 我将 80000008 用于获取物理和虚拟地址位,但会显示不断变化的随机数。 我想知道如何使用这个带有 80000008 的 cpuid 指令来获取这些地址位
我是编程和操作系统初学者。 请让我知道我必须做什么。
【问题讨论】:
val 是字符串吗?你期望得到什么,你实际得到了什么? 我得到了处理器信息并通过 val 打印出来。我使用getcpuid函数3次,0x80000002、0x80000003、0x80000004,显示处理器名称 Windows 有一个cpuid intrinsic,这可能是比自己编写更好的选择。 不明白什么是“只显示7-8位连续变化”。方法。请注意,您当前的代码正在尝试将结果值打印为字符串,这对处理器 ID 有一定意义,但对于打包为 8 位二进制整数的地址大小则完全没有意义。如果您对 0x80000008 案例使用不同的代码,请发布该代码,以便我们了解您实际在说什么。 现在我知道我错了什么以及如何修复此代码,这要归功于您的 cmets,我很难解决这个问题。具体来说,你是说我写的getcpuid函数和我用80000008获取地址位的方式是对的吗?那么错误的方法是用数组表示地址位,如 -int value[5]-?根据这个假设,我能做些什么来表达这些地址位?我很难理解用这种方式表达我想要的东西。 【参考方案1】:您使用的内联汇编可能是正确的;但这取决于它是哪个编译器。我认为它适用于 Microsoft 的 MSVC(但我从未使用过,也无法确定)。对于 GCC(和 CLANG),您必须通知编译器您正在修改寄存器和内存的内容(通过 clobber 列表),并且告诉编译器您正在输出 4 个值会更有效在 4 个寄存器中。
主要问题是您试图将输出视为(以空结尾的)字符串;并且 CPUID 返回的数据绝不是一个以空结尾的字符串(即使对于“获取供应商字符串”和“获取品牌名称字符串”,它也是一个没有零终止符的空格填充字符串)。
要解决这个问题,您可以:
void getcpuid(int T, int* val)
unsigned int reg_ax;
unsigned int reg_bx;
unsigned int reg_cx;
unsigned int reg_dx;
__asm
mov eax, T;
cpuid;
mov reg_ax, eax;
mov reg_bx, ebx;
mov reg_cx, ecx;
mov reg_dx, edx;
*(val + 0) = reg_ax;
*(val + 1) = reg_bx;
*(val + 2) = reg_cx;
*(val + 3) = reg_dx;
int main()
uint32_t val[5]; val[4] = 0;
getcpuid(0x80000002U, val);
printf("0x%08X\r\n", val[0]);
getcpuid(0x80000003U, val);
printf("0x%08X\r\n", val[1]);
getcpuid(0x80000004U, val);
printf("0x%08X\r\n", val[2]);
return 0;
下一个问题是提取虚拟地址大小和物理地址大小值。这些是封装在eax
的第一个和第二个字节中的 8 位值;所以:
int main()
uint32_t val[5]; val[4] = 0;
int physicalAddressSize;
int virtualAddressSize;
getcpuid(0x80000008U, val);
physicalAddressSize = val[0] & 0xFF;
virtualAddressSize= (val[0] >> 8) & 0xFF;
printf("Virtual %d, physical %d\r\n", virtualAddressSize, physicalAddressSize);
return 0;
这应该适用于最新的 CPU;这意味着它在旧 CPU 上仍然很糟糕并且损坏。
要开始修复您想要检查 CPU 是否支持“CPUID 叶 0x80000008”,然后再假设它存在:
int main()
uint32_t val[5]; val[4] = 0;
int physicalAddressSize;
int virtualAddressSize;
getcpuid(0x80000000U, val);
if(val(0) < 0x80000008U)
physicalAddressSize = -1;
virtualAddressSize = -1;
else
getcpuid(0x80000008U, val);
physicalAddressSize = val[0] & 0xFF;
virtualAddressSize= (val[0] >> 8) & 0xFF;
printf("Virtual %d, physical %d\r\n", virtualAddressSize, physicalAddressSize);
return 0;
当“CPUID 叶 0x80000008”不存在时,您可以返回正确的结果。对于所有不支持“CPUID 叶 0x80000008”的 CPU;虚拟地址大小为 32 位,物理地址大小为 36 位(如果支持 PAE)或 32 位(如果不支持 PAE)。你可以使用CPUID来判断CPU是否支持PAE,所以最终有点像这样:
int main()
uint32_t val[5]; val[4] = 0;
int physicalAddressSize;
int virtualAddressSize;
getcpuid(0x80000000U, val);
if(val(0) < 0x80000008U)
getcpuid(0x00000000U, val);
if(val[0] == 0)
physicalAddressSize = 32; // "CPUID leaf 0x00000001" not supported
else
getcpuid(0x00000001U, val);
if( val[3] & (1 << 6) != 0)
physicalAddressSize = 36; // PAE is supported
else
physicalAddressSize = 32; // PAE not supported
virtualAddressSize = 32;
else
getcpuid(0x80000008U, val);
physicalAddressSize = val[0] & 0xFF;
virtualAddressSize= (val[0] >> 8) & 0xFF;
printf("Virtual %d, physical %d\r\n", virtualAddressSize, physicalAddressSize);
return 0;
另一个问题是有时 CPUID 是错误的;这意味着您必须遍历每个 CPU(来自 Intel、AMD、VIA 等)的每个勘误表,以确保 CPUID 的结果实际上是正确的。例如,有 3 种型号的“Intel Pentium 4 Processor on 90 nm Process”,其中“CPUID 叶 0x800000008”是错误的,当它们实际上是 36 位时说“物理地址是 40 位”。
对于所有这些情况,您需要实施变通方法(例如,从 CPUID 获取 CPU 供应商/系列/型号/步进信息,如果它与 Pentium 4 的 3 个错误型号之一匹配,请执行“if(physicalAddressSize == 40) physicalAddressSize = 36;
" 来修复 CPU 的 bug)。
【讨论】:
非常感谢您的回答。这些天我发现学习这些东西非常困难,我觉得一个人做起来要困难得多。我真的很感谢你的帮助。我注册 Stack Overflow 才一天,但我很享受学习。还有一些地方我还是不明白,很抱歉,但我想再问你一些问题。取决于特定品牌和 CPU 型号的错误以及如何修复它们现在对我来说似乎有点困难。 其实,看来要等很久了,先问一个对我来说真的很难也很好奇的问题。我目前正在使用 MSVC(IDE) 并使用 Windows 8,我的 CPU 使用的是 22nm Intel i5-4670(3.40 GHz) 处理器。发现用cpuid函数(80000008)得到的值怎么表达完全搞错了,从学习编程和操作系统的角度,学到了一个非常简单和创新的新方法。但实际上,我又想知道了。 sandpile.org/x86/cpuid.htm 在上面的页面中,在查看以上是关于如何通过 CPUID 命令使用 C/C++ 获取物理和虚拟地址位的主要内容,如果未能解决你的问题,请参考以下文章
在 Windows 上使用 JNA 调用 __cpuid 函数
如何使用 CPUID 指令正确获取 x86 CPU 功能? [复制]