如何通过 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 在上面的页面中,在查看段落中的信息时,如果将值放入EAX并执行CPUID指令,物理地址位最多0-7位,虚拟地址位最多8-15位 据说是显示信息的。例如,假设它得到的值是 12345678,56 是虚拟地址,78 是物理地址。我认为是展示。 但是在你修改的第二个代码中,你使用的值是0x80000002,并且在这个状态下编译的时候,结果-virtual 67,physical 32-显示出来了。但是如果虚拟地址位的大小是 67 我不认为。如果这是您的拼写错误,那将是一个非常好的情况,我认为我的 cpu 是一个错误(感觉这个概率很低)或者我的 getcpuid 函数是错误(@_@)。即使将第二个修改代码的输入值更改为 80000008 或打印第 3 或第 4 个代码的值,虚拟和物理地址位都输出为 0。我正在等待您的感谢评论。 @kkkkkk:“虚拟:48,物理:39”对我来说听起来不错。所有较旧的 64 位 CPU 都将报告“虚拟:48”(由于最近的扩展,Ice Lake 和较新的 Intel CPU 将/应该报告 57 位 - 请参阅en.wikipedia.org/wiki/Intel_5-level_paging)。对于物理地址大小,从 32 位到 52 位都是可能的。 39 位足够 512 GiB(例如 256 GiB 的 RAM 和 256 GIB 的空间用于内存映射设备),但英特尔表示 Core i5-4670 仅支持 32 GiB 的 RAM,因此 39 位就足够了。

以上是关于如何通过 CPUID 命令使用 C/C++ 获取物理和虚拟地址位的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 上使用 JNA 调用 __cpuid 函数

如何使用 CPUID 指令正确获取 x86 CPU 功能? [复制]

WMI 如何获取 ProcessorId?

C或C++如何通过程序执行shell命令并获取命令执行结果?

如何使用 cpuid 获取 TLB 页面大小

如何使用 CPUID 查找主板信息?