通用 vcpu 接口

虚拟 CPU “设备” 也接受 ioctl KVM_SET_DEVICE_ATTR、KVM_GET_DEVICE_ATTR 和 KVM_HAS_DEVICE_ATTR。该接口使用与其他设备相同的 struct kvm_device_attr,但针对的是 VCPU 范围内的设置和控制。

每个虚拟 CPU 的组和属性(如果有)是特定于架构的。

1. 组:KVM_ARM_VCPU_PMU_V3_CTRL

架构:

ARM64

1.1. 属性:KVM_ARM_VCPU_PMU_V3_IRQ

参数:

在 kvm_device_attr.addr 中,PMU 溢出中断的地址是指向 int 的指针

返回值

-EBUSY

PMU 溢出中断已设置

-EFAULT

读取中断号时出错

-ENXIO

不支持 PMUv3 或尝试获取溢出中断时未设置溢出中断

-ENODEV

VCPU 中缺少 KVM_ARM_VCPU_PMU_V3 功能

-EINVAL

提供的 PMU 溢出中断号无效或尝试在不使用内核 irqchip 的情况下设置 IRQ 号。

一个值,描述此 vcpu 的 PMUv3(性能监视单元 v3)溢出中断号。此中断可以是 PPI 或 SPI,但每个 vcpu 的中断类型必须相同。作为 PPI,所有 vcpu 的中断号相同,而作为 SPI,每个 vcpu 必须使用单独的编号。

1.2 属性:KVM_ARM_VCPU_PMU_V3_INIT

参数:

在 kvm_device_attr.addr 中没有额外的参数

返回值

-EEXIST

中断号已使用

-ENODEV

不支持 PMUv3 或未初始化 GIC

-ENXIO

不支持 PMUv3、缺少 VCPU 功能或未设置中断号

-EBUSY

PMUv3 已初始化

请求初始化 PMUv3。如果将 PMUv3 与内核虚拟 GIC 实现一起使用,则必须在初始化内核 irqchip 之后执行此操作。

1.3 属性:KVM_ARM_VCPU_PMU_V3_FILTER

参数:

在 kvm_device_attr.addr 中,PMU 事件过滤器的地址是指向 struct kvm_pmu_event_filter 的指针

返回值:

-ENODEV

不支持 PMUv3 或未初始化 GIC

-ENXIO

PMUv3 未正确配置或内核 irqchip 未按调用此属性之前的要求进行配置

-EBUSY

PMUv3 已初始化或 VCPU 已运行

-EINVAL

无效的过滤器范围

请求安装 PMU 事件过滤器,如下所述

struct kvm_pmu_event_filter {
        __u16       base_event;
        __u16       nevents;

#define KVM_PMU_EVENT_ALLOW 0
#define KVM_PMU_EVENT_DENY  1

        __u8        action;
        __u8        pad[3];
};

过滤器范围定义为范围 [@base_event, @base_event + @nevents),以及 @action(KVM_PMU_EVENT_ALLOW 或 KVM_PMU_EVENT_DENY)。第一个注册的范围定义全局策略(如果第一个 @action 是 DENY,则为全局 ALLOW;如果第一个 @action 是 ALLOW,则为全局 DENY)。可以编程多个范围,并且必须适应 PMU 架构定义的事件空间(ARMv8.0 上为 10 位,从 ARMv8.1 开始为 16 位)。

注意:通过为同一范围注册相反的操作来“取消”过滤器不会更改默认操作。例如,作为第一个过滤器为事件范围 [0:10) 安装 ALLOW 过滤器,然后对同一范围应用 DENY 操作会将整个范围保持为禁用状态。

限制:事件 0 (SW_INCR) 永远不会被过滤,因为它不计数硬件事件。过滤事件 0x1E (CHAIN) 也无效,因为它严格来说不是事件。可以使用事件 0x11 (CPU_CYCLES) 过滤循环计数器。

1.4 属性:KVM_ARM_VCPU_PMU_V3_SET_PMU

参数:

在 kvm_device_attr.addr 中,地址指向表示 PMU 标识符的 int。

返回值:

-EBUSY

PMUv3 已初始化、VCPU 已运行或已设置事件过滤器

-EFAULT

访问 PMU 标识符时出错

-ENXIO

未找到 PMU

-ENODEV

不支持 PMUv3 或未初始化 GIC

-ENOMEM

无法分配内存

请求 VCPU 在创建用于 PMU 仿真的访客事件时使用指定的硬件 PMU。PMU 标识符可以从 /sys/devices(或等效的 /sys/bus/even_source)下所需 PMU 实例的“type”文件中读取。此属性在系统上至少有两个 CPU PMU 的异构系统上特别有用。为一个 VCPU 设置的 PMU 将被所有其他 VCPU 使用。如果 PMU 事件过滤器已存在,则无法设置 PMU。

请注意,KVM 不会尝试在与此属性指定的 PMU 关联的物理 CPU 上运行 VCPU。这完全留给用户空间。但是,尝试在 PMU 不支持的物理 CPU 上运行 VCPU 将失败,并且 KVM_RUN 将返回 exit_reason = KVM_EXIT_FAIL_ENTRY 并通过将 hardare_entry_failure_reason 字段设置为 KVM_EXIT_FAIL_ENTRY_CPU_UNSUPPORTED 并将 cpu 字段设置为处理器 id 来填充 fail_entry 结构。

1.5 属性:KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS

参数:

在 kvm_device_attr.addr 中,地址指向表示 PMCR_EL0.N 采用的最大值的 unsigned int

返回值:

-EBUSY

PMUv3 已初始化、VCPU 已运行或已设置事件过滤器

-EFAULT

访问 addr 指向的值时出错

-ENODEV

不支持 PMUv3 或未初始化 GIC

-EINVAL

未显式选择 PMUv3,或 N 值超出范围

设置虚拟 PMU 中实现的事件计数器的数量。这要求通过 KVM_ARM_VCPU_PMU_V3_SET_PMU 显式选择 PMU,并且在未显式选择 PMU 或计数器数量超出所选 PMU 的范围时将失败。选择新的 PMU 会取消设置此属性的效果。

2. 组:KVM_ARM_VCPU_TIMER_CTRL

架构:

ARM64

2.1. 属性:KVM_ARM_VCPU_TIMER_IRQ_{VTIMER,PTIMER,HVTIMER,HPTIMER}

参数:

在 kvm_device_attr.addr 中,定时器中断的地址是指向 int 的指针

返回值

-EINVAL

无效的定时器中断号

-EBUSY

一个或多个 VCPU 已运行

一个值,描述连接到内核虚拟 GIC 时的架构定时器中断号。这些必须是 PPI (16 <= intid < 32)。设置属性会覆盖默认值(见下文)。

KVM_ARM_VCPU_TIMER_IRQ_VTIMER

EL1 虚拟定时器 intid(默认:27)

KVM_ARM_VCPU_TIMER_IRQ_PTIMER

EL1 物理定时器 intid(默认:30)

KVM_ARM_VCPU_TIMER_IRQ_HVTIMER

EL2 虚拟定时器 intid(默认:28)

KVM_ARM_VCPU_TIMER_IRQ_HPTIMER

EL2 物理定时器 intid(默认:26)

为不同的定时器设置相同的 PPI 将阻止 VCPU 运行。在一个 VCPU 上设置中断号会将当时创建的所有 VCPU 配置为使用为给定定时器提供的编号,从而覆盖其他 VCPU 上先前配置的值。用户空间应在创建所有 VCPU 后并在运行任何 VCPU 之前,至少在一个 VCPU 上配置中断号。

3. 组:KVM_ARM_VCPU_PVTIME_CTRL

架构:

ARM64

3.1 属性:KVM_ARM_VCPU_PVTIME_IPA

参数:

64 位基地址

返回值

-ENXIO

未实现被盗时间

-EEXIST

已为此 VCPU 设置基地址

-EINVAL

基地址未按 64 字节对齐

指定此 VCPU 的被盗时间结构体的基地址。基地址必须按 64 字节对齐,并且存在于有效的访客内存区域中。有关被盗时间结构的布局等更多信息,请参见arm64 的准虚拟化时间支持

4. 组:KVM_VCPU_TSC_CTRL

架构:

x86

4.1 属性:KVM_VCPU_TSC_OFFSET

参数:

64 位无符号 TSC 偏移量

返回值

-EFAULT

读取/写入提供的参数地址时出错。

-ENXIO

不支持该属性

指定访客的 TSC 相对于主机的 TSC 的偏移量。访客的 TSC 然后通过以下公式推导出来

guest_tsc = host_tsc + KVM_VCPU_TSC_OFFSET

此属性可用于在实时迁移时调整访客的 TSC,以便 TSC 计数 VM 暂停期间的时间。以下描述了为此目的可能使用的算法。

从源 VMM 进程

  1. 调用 KVM_GET_CLOCK ioctl 以记录主机 TSC (tsc_src)、kvmclock 纳秒 (guest_src) 和主机 CLOCK_REALTIME 纳秒 (host_src)。

  2. 读取每个 vCPU 的 KVM_VCPU_TSC_OFFSET 属性以记录访客 TSC 偏移量 (ofs_src[i])。

  3. 调用 KVM_GET_TSC_KHZ ioctl 以记录访客 TSC 的频率 (freq)。

从目标 VMM 进程

  1. 调用 KVM_SET_CLOCK ioctl,在其各自的字段中提供来自 kvmclock (guest_src) 和 CLOCK_REALTIME (host_src) 的源纳秒。确保在提供的结构中设置了 KVM_CLOCK_REALTIME 标志。

    KVM 将提前 VM 的 kvmclock 以考虑自记录时钟值以来经过的时间。请注意,除非 CLOCK_REALTIME 在源和目标之间同步,并且源暂停 VM 和目标执行步骤 4-7 之间经过了相当短的时间,否则这将在访客中引起问题(例如,超时)。

  2. 调用 KVM_GET_CLOCK ioctl 以记录主机 TSC (tsc_dest) 和 kvmclock 纳秒 (guest_dest)。

  3. 调整每个 vCPU 的访客 TSC 偏移量以考虑 (1) 自记录状态以来经过的时间和 (2) 源机器和目标机器之间的 TSC 差异

    ofs_dst[i] = ofs_src[i] -

    (guest_src - guest_dest) * freq + (tsc_src - tsc_dest)

    (“ofs[i] + tsc - guest * freq” 是对应于 kvmclock 中时间 0 的访客 TSC 值。以上公式确保它在目标上与在源上相同)。

  4. 使用上一步中导出的相应值写入每个 vCPU 的 KVM_VCPU_TSC_OFFSET 属性。