KVM PV Features
hypervisor_cpuid_base
A guest running on a kvm host, can check some of its features using cpuid. This is not always guaranteed to work, since userspace can mask-out some, or even all KVM-related cpuid features before launching a guest.
How to check all the PV features in guest?
PV features 需要:
- Host KVM 支持;
- Userspace VMM 比如说 QEMU 会把这个 feature 暴露给 guest(如果
--cpu=host
的话会把所有的 CPUID 都暴露给 guest); - Guest kernel 有 pv 支持,会使用这个 feature
需要保证这三者都支持才行。
// cpuid -1 -l 0x40000001
// cpuid -l
// In guest kernel
hypervisor_cpuid_base("KVMKVMKVM\0\0\0", 0);
hypervisor_cpuid_base("ACRNACRNACRN", 0);
hypervisor_cpuid_base("XenVMMXenVMM", 2);
static inline uint32_t hypervisor_cpuid_base(const char *sig, uint32_t leaves)
{
uint32_t base, eax, signature[3];
for (base = 0x40000000; base < 0x40010000; base += 0x100) {
cpuid(base, &eax, &signature[0], &signature[1], &signature[2]);
if (!memcmp(sig, signature, 12) &&
(leaves == 0 || ((eax - base) >= leaves)))
return base;
}
return 0;
}
下面这个脚本可以从 Host KVM 上列出所有支持的 PV features:
下面这个脚本可以从 Guest OS 上列出所有支持的 PV features:
#!/usr/bin/env python3
import re
import subprocess
# PV 特性列表,格式为 (特性名, 位值)
PV_FEATURES = [
("KVM_FEATURE_CLOCKSOURCE", 0),
("KVM_FEATURE_NOP_IO_DELAY", 1),
("KVM_FEATURE_MMU_OP", 2),
("KVM_FEATURE_CLOCKSOURCE2", 3),
("KVM_FEATURE_ASYNC_PF", 4),
("KVM_FEATURE_STEAL_TIME", 5),
("KVM_FEATURE_PV_EOI", 6),
("KVM_FEATURE_PV_UNHALT", 7),
("KVM_FEATURE_PV_TLB_FLUSH", 9),
("KVM_FEATURE_ASYNC_PF_VMEXIT", 10),
("KVM_FEATURE_PV_SEND_IPI", 11),
("KVM_FEATURE_POLL_CONTROL", 12),
("KVM_FEATURE_PV_SCHED_YIELD", 13),
("KVM_FEATURE_ASYNC_PF_INT", 14),
("KVM_FEATURE_MSI_EXT_DEST_ID", 15),
("KVM_FEATURE_HC_MAP_GPA_RANGE", 16),
("KVM_FEATURE_MIGRATION_CONTROL", 17),
("KVM_FEATURE_CLOCKSOURCE_STABLE_BIT", 24),
]
def parse_cpuid_output(output):
"""解析 cpuid 命令输出,提取 eax 的值"""
match = re.search(r"eax=0x([0-9a-f]+)", output)
if match:
return int(match.group(1), 16)
return 0
def check_features(eax_value):
"""检查每个 PV 特性是否支持"""
supported = []
unsupported = []
for feature, bit in PV_FEATURES:
if eax_value & (1 << bit):
supported.append(feature)
else:
unsupported.append(feature)
return supported, unsupported
def main():
try:
# 执行 cpuid 命令
result = subprocess.run(['cpuid', '-1', '-l', '0x40000001'], capture_output=True, text=True, check=True)
output = result.stdout
eax_value = parse_cpuid_output(output)
supported, unsupported = check_features(eax_value)
print("支持的 PV 特性:")
for feature in supported:
print(f" - {feature}")
print("\n不支持的 PV 特性:")
for feature in unsupported:
print(f" - {feature}")
except FileNotFoundError:
print("错误: 未找到 'cpuid' 命令,请确保已安装该命令。")
except subprocess.CalledProcessError as e:
print(f"执行 cpuid 命令时出错: {e.stderr}")
if __name__ == "__main__":
main()
查看 arch/x86/include/uapi/asm/kvm_para.h
,里面有所有 PV features 的定义。Documentation/virt/kvm/x86/cpuid.rst
这里有 CPUID 文档。
Pv sched yield kvm-pv-sched-yield
kvm_guest_init
if (pv_sched_yield_supported()) {
smp_ops.send_call_func_ipi = kvm_smp_send_call_func_ipi;
pr_info("setup PV sched yield\n");
}
static bool pv_sched_yield_supported(void)
{
return (kvm_para_has_feature(KVM_FEATURE_PV_SCHED_YIELD) &&
!kvm_para_has_hint(KVM_HINTS_REALTIME) &&
kvm_para_has_feature(KVM_FEATURE_STEAL_TIME));
}
PV qspinlock, PV spinlock, kvm-pv-unhalt
kvm_spinlock_init
pr_info("PV spinlocks enabled\n");
CONFIG_PARAVIRT_SPINLOCKS
。
这些应该都是一样的,只不过在演进的过程中有了不同的名字。 都是 spinlock,只不过这些 spinlock 在 kernel 里实现不一样,对应的 pv 实现也是不一样的。
PV qspinlock 指的是 queue spinlock^。
pvqspinlock
是一个 ARM 的 guest kernel parameter,x86 没有此 kernel parameter,默认是开启的。可以看下面的代码来验证:
#if defined(CONFIG_PARAVIRT_SPINLOCKS)
/* Lock ops. */
#ifdef CONFIG_SMP
.lock.queued_spin_lock_slowpath = native_queued_spin_lock_slowpath,
.lock.queued_spin_unlock =
PV_CALLEE_SAVE(__native_queued_spin_unlock),
.lock.wait = paravirt_nop,
.lock.kick = paravirt_nop,
.lock.vcpu_is_preempted =
PV_CALLEE_SAVE(__native_vcpu_is_preempted),
#endif /* SMP */
#endif
//...
🗞️ Recent Posts