CPUID

来自osdev
跳到导航 跳到搜索

CPUID指令可用于检索有关cpu的各种信息,如供应商字符串和型号、内部缓存的大小以及(更有趣的是)支持的cpu功能列表。

如何使用CPUID

检查CPUID可用性

在使用CPUID指令之前,还应该通过测试eflags中的“ID”位(0x200000)来确保处理器支持该指令。 此位仅在支持CPUID指令时才可修改。 对于不支持CPUID的系统,更改 'ID' 位不会有任何影响。

注意:在示例C中实现此例程可能会导致问题,因为编译器可能随时更改EFLAG。

此汇编例程检查是否支持CPUID:

    pushfd                               ;保存EFLAGS
    pushfd                               ;存储EFLAGS
    xor dword [esp],0x00200000           ;反转存储的EFLAGS中的ID位
    popfd                                ;加载存储的电子标志 (ID位反转)
    pushfd                               ;再次存储EFLAGS(ID位可能反转,也可能不反转)
    pop eax                              ;eax=修改后的EFLAGS(ID位可能反转,也可能不反转)
    xor eax,[esp]                        ;eax = 更改了哪些位
    popfd                                ;恢复原始EFLAG
    and eax,0x00200000                   ;如果ID位不能更改,则eax=0,否则为非零值
    ret

注1: 有一些旧的cpu支持CPUID但eblags中的ID位不支持 (NexGen)。 当且仅当必须首先启用CPUID(Cyrix M1)时,这些CPU也支持CPUID。

注2:您可以简单地尝试执行CPUID指令,并查看是否得到无效操作码异常。 这避免了支持CPUID但不支持EBLAGS中的ID位的cpu的问题; 对于支持CPUID的CPU,速度可能会更快(对于不支持CPUID的CPU,速度可能会更慢)。


基本用法

CPUID指令的想法是,您可以在EAX中使用不同的值调用它,它将返回有关处理器的不同信息。 例如,如果我们想要供应商ID字符串(见下文),我们应该这样编码:

mov eax, 0x0
cpuid

AMD和英特尔之间存在差异。 根据Intel CPUID应用说明,在取出信息 (例如处理器签名,处理器功能标志等) 之前,我们应该首先检查供应商ID字符串中的 “GenuineIntel”。

CPU供应商ID字符串

在EAX=0的情况下调用时,CPUID返回EBX、EDX和ECX格式的供应商ID字符串。 按此顺序将它们写入内存会产生12个字符的字符串。 这些可以根据已知的供应商ID字符串进行测试:

/* Vendor strings from CPUs. */
#define CPUID_VENDOR_OLDAMD        "AMDisbetter!" // Early engineering samples of AMD K5 processor
#define CPUID_VENDOR_AMD           "AuthenticAMD"
#define CPUID_VENDOR_INTEL         "GenuineIntel"
#define CPUID_VENDOR_VIA           "VIA VIA VIA "
#define CPUID_VENDOR_OLDTRANSMETA  "TransmetaCPU"
#define CPUID_VENDOR_TRANSMETA     "GenuineTMx86"
#define CPUID_VENDOR_CYRIX         "CyrixInstead"
#define CPUID_VENDOR_CENTAUR       "CentaurHauls"
#define CPUID_VENDOR_NEXGEN        "NexGenDriven"
#define CPUID_VENDOR_UMC           "UMC UMC UMC "
#define CPUID_VENDOR_SIS           "SiS SiS SiS "
#define CPUID_VENDOR_NSC           "Geode by NSC"
#define CPUID_VENDOR_RISE          "RiseRiseRise"
#define CPUID_VENDOR_VORTEX        "Vortex86 SoC"
#define CPUID_VENDOR_OLDAO486      "GenuineAO486"
#define CPUID_VENDOR_AO486         "MiSTer AO486"
#define CPUID_VENDOR_ZHAOXIN       "  Shanghai  "
#define CPUID_VENDOR_HYGON         "HygonGenuine"
#define CPUID_VENDOR_ELBRUS        "E2K MACHINE "
 
/* Vendor strings from hypervisors. */
#define CPUID_VENDOR_QEMU          "TCGTCGTCGTCG"
#define CPUID_VENDOR_KVM           " KVMKVMKVM  "
#define CPUID_VENDOR_VMWARE        "VMwareVMware"
#define CPUID_VENDOR_VIRTUALBOX    "VBoxVBoxVBox"
#define CPUID_VENDOR_XEN           "XenVMMXenVMM"
#define CPUID_VENDOR_HYPERV        "Microsoft Hv"
#define CPUID_VENDOR_PARALLELS     " prl hyperv "
#define CPUID_VENDOR_PARALLELS_ALT " lrpepyh vr " //有时Parallels会因为字节顺序不匹配而将“PRL HyperV”错误地编码为“lrpepyh VR”。
#define CPUID_VENDOR_BHYVE         "bhyve bhyve "
#define CPUID_VENDOR_QNX           " QNXQVMBSQG "

您已经知道供应商ID字符串在EBX,ECX,EDX中返回。 让我们以英特尔处理器为例。 它应该返回“GenuineIntel”。 查看以下文本,以了解如何将字符串放置在寄存器中:

      MSB         LSB
EBX = 'u' 'n' 'e' 'G'
EDX = 'I' 'e' 'n' 'i'
ECX = 'l' 'e' 't' 'n' 
 
 其中,MSB代表“最高有效字节”,LSB代表“最低有效字节”。

此外,EAX设置为CPUID调用支持的最大EAX值,因为并非所有处理器都支持所有查询。

中央处理器功能

当使用EAX=1(CPUID_GETFEATURES)调用CPUID时,CPUID会在EDX中返回一个包含以下值的位字段。 请注意,不同品牌的CPU可能会赋予这些处理器不同的含义。 最近的一些处理器还将ECX用于功能 (形成不同的集合),由于某些旧cpu在此寄存器中返回虚假信息,因此您应该非常小心。

enum {
    CPUID_FEAT_ECX_SSE3         = 1 << 0, 
    CPUID_FEAT_ECX_PCLMUL       = 1 << 1,
    CPUID_FEAT_ECX_DTES64       = 1 << 2,
    CPUID_FEAT_ECX_MONITOR      = 1 << 3,  
    CPUID_FEAT_ECX_DS_CPL       = 1 << 4,  
    CPUID_FEAT_ECX_VMX          = 1 << 5,  
    CPUID_FEAT_ECX_SMX          = 1 << 6,  
    CPUID_FEAT_ECX_EST          = 1 << 7,  
    CPUID_FEAT_ECX_TM2          = 1 << 8,  
    CPUID_FEAT_ECX_SSSE3        = 1 << 9,  
    CPUID_FEAT_ECX_CID          = 1 << 10,
    CPUID_FEAT_ECX_SDBG         = 1 << 11,
    CPUID_FEAT_ECX_FMA          = 1 << 12,
    CPUID_FEAT_ECX_CX16         = 1 << 13, 
    CPUID_FEAT_ECX_XTPR         = 1 << 14, 
    CPUID_FEAT_ECX_PDCM         = 1 << 15, 
    CPUID_FEAT_ECX_PCID         = 1 << 17, 
    CPUID_FEAT_ECX_DCA          = 1 << 18, 
    CPUID_FEAT_ECX_SSE4_1       = 1 << 19, 
    CPUID_FEAT_ECX_SSE4_2       = 1 << 20, 
    CPUID_FEAT_ECX_X2APIC       = 1 << 21, 
    CPUID_FEAT_ECX_MOVBE        = 1 << 22, 
    CPUID_FEAT_ECX_POPCNT       = 1 << 23, 
    CPUID_FEAT_ECX_TSC          = 1 << 24, 
    CPUID_FEAT_ECX_AES          = 1 << 25, 
    CPUID_FEAT_ECX_XSAVE        = 1 << 26, 
    CPUID_FEAT_ECX_OSXSAVE      = 1 << 27, 
    CPUID_FEAT_ECX_AVX          = 1 << 28,
    CPUID_FEAT_ECX_F16C         = 1 << 29,
    CPUID_FEAT_ECX_RDRAND       = 1 << 30,
    CPUID_FEAT_ECX_HYPERVISOR   = 1 << 31,

    CPUID_FEAT_EDX_FPU          = 1 << 0,  
    CPUID_FEAT_EDX_VME          = 1 << 1,  
    CPUID_FEAT_EDX_DE           = 1 << 2,  
    CPUID_FEAT_EDX_PSE          = 1 << 3,  
    CPUID_FEAT_EDX_TSC          = 1 << 4,  
    CPUID_FEAT_EDX_MSR          = 1 << 5,  
    CPUID_FEAT_EDX_PAE          = 1 << 6,  
    CPUID_FEAT_EDX_MCE          = 1 << 7,  
    CPUID_FEAT_EDX_CX8          = 1 << 8,  
    CPUID_FEAT_EDX_APIC         = 1 << 9,  
    CPUID_FEAT_EDX_SEP          = 1 << 11, 
    CPUID_FEAT_EDX_MTRR         = 1 << 12, 
    CPUID_FEAT_EDX_PGE          = 1 << 13, 
    CPUID_FEAT_EDX_MCA          = 1 << 14, 
    CPUID_FEAT_EDX_CMOV         = 1 << 15, 
    CPUID_FEAT_EDX_PAT          = 1 << 16, 
    CPUID_FEAT_EDX_PSE36        = 1 << 17, 
    CPUID_FEAT_EDX_PSN          = 1 << 18, 
    CPUID_FEAT_EDX_CLFLUSH      = 1 << 19, 
    CPUID_FEAT_EDX_DS           = 1 << 21, 
    CPUID_FEAT_EDX_ACPI         = 1 << 22, 
    CPUID_FEAT_EDX_MMX          = 1 << 23, 
    CPUID_FEAT_EDX_FXSR         = 1 << 24, 
    CPUID_FEAT_EDX_SSE          = 1 << 25, 
    CPUID_FEAT_EDX_SSE2         = 1 << 26, 
    CPUID_FEAT_EDX_SS           = 1 << 27, 
    CPUID_FEAT_EDX_HTT          = 1 << 28, 
    CPUID_FEAT_EDX_TM           = 1 << 29, 
    CPUID_FEAT_EDX_IA64         = 1 << 30,
    CPUID_FEAT_EDX_PBE          = 1 << 31
};

在GCC中使用CPUID

或者,可以使用GCC附带的__get_cpuid函数。 要使用此函数,请包含 <cpuid.h>。

#include <cpuid.h>

/*示例:获取CPU的型号*/
static int get_model(void)
{
    int ebx, unused;
    __cpuid(0, unused, ebx, unused, unused);
    return ebx;
}

/*示例:检查内置本地APIC。*/
static int check_apic(void)
{
    unsigned int eax, unused, edx;
    __get_cpuid(1, &eax, &unused, &unused, %edx);
    return edx & CPUID_FEAT_EDX_APIC;
}

本文或本节的事实准确性是有争议的
请看 讨论页 上的相关讨论。

CPUID可以用各种请求代码 (在eax中) 调用,并且将在一般寄存器中返回值 (很大程度上是内置的服务中断)。 以下代码由Clicker的x86/cpu.h公开。H

/*已弃用:应该使用GCC附带的<cpuid.h>头。*/

enum cpuid_requests {
  CPUID_GETVENDORSTRING,
  CPUID_GETFEATURES,
  CPUID_GETTLB,
  CPUID_GETSERIAL,
  
  CPUID_INTELEXTENDED=0x80000000,
  CPUID_INTELFEATURES,
  CPUID_INTELBRANDSTRING,
  CPUID_INTELBRANDSTRINGMORE,
  CPUID_INTELBRANDSTRINGEND,
};
    
/** 向CPUID发出单个请求。例如,适配 “intel功能特性”
 * 请注意,即使只关注“eax”和“edx”,其他寄存器
 * 将被操作修改,因此我们需要将其告知编译器。
 */
static inline void cpuid(int code, uint32_t *a, uint32_t *d) {
  asm volatile("cpuid":"=a"(*a),"=d"(*d):"a"(code):"ecx","ebx");
}
    
/** 发出完整请求,将通用寄存器输出存储为字符串
 */
static inline int cpuid_string(int code, uint32_t where[4]) {
  asm volatile("cpuid":"=a"(*where),"=b"(*(where+1)),
               "=c"(*(where+2)),"=d"(*(where+3)):"a"(code));
  return (int)where[0];
}

另见

文章

论坛文章

外部链接

de:CPUID