如何通过 CPUID 命令使用 C/C++ 获取物理和虚拟地址位

How to get physical and virtual address bits with C/C++ by CPUID command

我在 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 运行此代码时,显示英特尔处理器品牌字符串。 我把 80000008 用于获取物理和虚拟地址位,但显示的随机数不断变化。 我想知道如何使用带有 80000008 的 cpuid commend 来获取这些地址位

我是编程和操作系统初学者。 请让我知道我必须做什么。

您使用的内联汇编可能是正确的;但这取决于它是哪个编译器。我认为它适用于 Microsoft 的 MSVC(但我从未使用过它,也不能确定)。对于 GCC(和 CLANG),您必须通知编译器您正在修改寄存器和内存的内容(通过 clobber 列表),并且告诉编译器您正在输出 4 个值会更有效在 4 个寄存器中。

主要问题是您试图将输出视为(以空字符结尾的)字符串;由 CPUID 编辑的数据 return 永远不会是空终止字符串(即使对于“获取供应商字符串”和“获取品牌名称字符串”,它也是一个没有零终止符的空格填充字符串)。

要解决这个问题,您可以:

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 leaf 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”不存在时,您可以 return 更正结果。对于所有不支持“CPUID leaf 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 等)遍历每个勘误表 sheet,以确保 CPUID 的结果实际上是正确的。例如,“Intel Pentium 4 Processor on 90 nm Process”有3个型号,其中“CPUID leaf 0x800000008”是错误的,说“物理地址是40位”,但实际上是36位。

对于所有这些情况,您需要实施 work-arounds(例如,从 CPUID 获取 CPU vendor/family/model/stepping 信息,如果它与 3 个越野车之一相匹配Pentium 4 型号,执行“if(physicalAddressSize == 40) physicalAddressSize = 36;”以修复 CPU 的错误。