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
//...