KVM搭配QEMU运行时缺失扩展处理器信息与特性位(pdpe1gb等)问题排查
场景说明
- 联调KVM与QEMU协同运行时发现,QEMU视角可见的CPU特性远少于主机实际支持的范围,经核对QEMU源码确认,QEMU获取的CPU特性列表完全来自KVM接口返回值。
- 为定位特性缺失问题,编写C程序通过
KVM_GET_SUPPORTED_CPUID接口检测KVM层对外暴露的可用CPU特性,核心测试代码如下:
#include <stdio.h> #include <fcntl.h> #include <linux/kvm.h> #include <stdint.h> static struct kvm_cpuid_entry2 *cpuid_find_entry(struct kvm_cpuid2 *cpuid, uint32_t function, uint32_t index); void main(){ int status, fd, max=64; uint32_t ret = 0; fd = open("/dev/kvm", O_RDWR | O_CLOEXEC); if (fd == -1) errx("cannot open /dev/kvm"); status = ioctl(fd, KVM_GET_API_VERSION,0); printf("kvm api=%d\n", status); struct kvm_cpuid2 *cpuid; int r, size; size = sizeof(*cpuid) + max * sizeof(*cpuid->entries); cpuid = malloc(size); cpuid->nent = max; r = ioctl(fd , KVM_GET_SUPPORTED_CPUID, cpuid); if (r == 0 && cpuid->nent >= max) { errx("2 big\n"); } if (r < 0){ errx("problem\n"); } struct kvm_cpuid_entry2 *entry = cpuid_find_entry(cpuid, 0x80000001, 0); if (entry) { ret = entry->edx; } printf("edx = %x\n", ret); if (ret & (1U << 0)) printf(" fpu"); if (ret & (1U << 1)) printf(" vme"); if (ret & (1U << 2)) printf(" de"); if (ret & (1U << 3)) printf(" pse"); if (ret & (1U << 4)) printf(" tsc"); if (ret & (1U << 5)) printf(" msr"); if (ret & (1U << 6)) printf(" pae"); if (ret & (1U << 7)) printf(" mce"); if (ret & (1U << 8)) printf(" cx8"); if (ret & (1U << 9)) printf(" apic"); if (ret & (1U << 11)) printf(" syscall"); if (ret & (1U << 12)) printf(" mtrr"); if (ret & (1U << 13)) printf(" pge"); if (ret & (1U << 14)) printf(" mca"); if (ret & (1U << 15)) printf(" cmov"); if (ret & (1U << 16)) printf(" pat"); if (ret & (1U << 17)) printf(" pse36"); if (ret & (1U << 18)) printf(" mp"); if (ret & (1U << 20)) printf(" nx"); if (ret & (1U << 22)) printf(" mmxext"); if (ret & (1U << 23)) printf(" mmx"); if (ret & (1U << 24)) printf(" fxsr"); if (ret & (1U << 25)) printf(" fxsr_opt"); if (ret & (1U << 26)) printf(" pdpe1gb"); if (ret & (1U << 27)) printf(" rdtscp"); if (ret & (1U << 29)) printf(" lm"); if (ret & (1U << 30)) printf(" 3dnowext"); if (ret & (1U << 31)) printf(" 3dnow"); printf("\n"); } static struct kvm_cpuid_entry2 *cpuid_find_entry(struct kvm_cpuid2 *cpuid, uint32_t function, uint32_t index) { int i; for (i = 0; i < cpuid->nent; ++i) { if (cpuid->entries[i].function == function && cpuid->entries[i].index == index) { return &cpuid->entries[i]; } } /* not found: */ return NULL; }
- 测试程序运行输出:
kvm api=12 edx = 28100800 syscall nx rdtscp lm
- 主机侧实际CPU特性(取自系统命令返回值):
cat /proc/cpuinfo | grep flags | uniq
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi ept vpid ept_ad fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xsaves arat pku ospke flush_l1d arch_capabilities
- 显然,对应EAX=0x80000001h功能的“扩展处理器信息与特性位”项中大量特性未被KVM暴露,其中重点关注的pdpe1gb(1GB大页物理地址扩展)特性未被透传。查阅KVM官方API文档未发现相关限制说明。
- 运行环境参数:
- CPU型号:Intel(R) Xeon(R) Gold 6130 CPU @ 2.10GHz,vendor_id为GenuineIntel,cpu family 6,model 85
- KVM API版本:12
- 系统版本:Ubuntu 18,内核版本5.4.0-70-generic
- 已开启嵌套虚拟化,当前主机本身为上层Hypervisor运行的虚拟机
问题与对应解答
1. 可在哪里查询KVM支持与不支持的CPU特性清单?是否存在KVM不支持相关特性的情况?
- 没有官方对外发布的统一KVM CPU特性清单文档。KVM对CPU特性的过滤、透传逻辑全部实现在对应版本内核的
arch/x86/kvm/cpuid.c文件中,所有被屏蔽的特性、触发透传的条件都在该文件的特性匹配规则中明确标注,直接查阅对应内核版本的源码是最准确的查询方式。 - 确实存在KVM不会向上层暴露全部主机原生CPU特性的情况。出于兼容性、稳定性、安全隔离的要求,KVM会对主机侧的CPU特性做分层过滤:部分特性会被直接屏蔽,部分特性需要匹配特定硬件/固件条件才会透传,还有部分特性需要上层调用方主动传递对应配置才会在CPUID返回值中体现。
2. 本次检查使用的CPUID第26位定义是参考QEMU代码得出的,可在哪里查询Intel CPU各CPUID位的官方定义文档?
- Intel官方的软件开发手册(SDM)是x86架构CPU所有CPUID位定义的权威来源,手册逐位说明了每个CPUID leaf、每个返回寄存器对应的功能含义、触发条件,所有特性位的定义都可以在手册中直接查到,不需要从QEMU等第三方代码反推定义。
- 本次用到的
0x80000001扩展CPUID leaf的EDX寄存器中,第26位对应的pdpe1gb功能,在官方手册中有明确标注。
3. 为什么上述测试示例中,KVM未对外暴露pdpe1gb对应的特性位?
- 结合当前嵌套虚拟化的运行环境,该特性缺失的核心原因有两点:
- 嵌套虚拟化场景下的特性过滤规则:当当前主机本身是上层Hypervisor运行的虚拟机时,KVM会增加一层判断逻辑——只有上层Hypervisor向当前嵌套VM透传了1GB页表支持能力时,下层KVM才会将pdpe1gb特性位加入返回的CPUID列表。
- 内核版本的前置校验逻辑:5.4.0-70-generic版本的内核中,pdpe1gb特性的透传有单独的校验条件,即使主机硬件本身支持1GB大页,也需要内核启动参数未关闭大页支持、对应的大页功能模块正常加载,KVM才会透传该特性。结合测试输出的EDX值
0x28100800,二进制展开后第26位值为0,说明当前嵌套场景下上层Hypervisor未向当前VM传递1GB页表支持,因此下层KVM自然不会暴露该特性位。
内容的提问来源于stack exchange,提问作者yehudahs

