跳转至

第5篇:半虚拟化 — pvclock、kvmclock、steal time 与 Hyper-V Enlightenment

源码:arch/x86/kvm/x86.c (14571行) | arch/x86/include/asm/pvclock-abi.h (48行) | arch/x86/include/asm/pvclock.h (108行) | arch/x86/include/asm/kvm_host.h (2547行) | Documentation/virt/kvm/x86/timekeeping.rst

系列目录:KVM 内核源码深度解析


一、半虚拟化概述

半虚拟化通过共享内存页实现 host-guest 高效通信,避免昂贵的 VM-exit。KVM 支持多种半虚拟化接口:

接口来源 核心功能 关键文件
KVM pvclock 精准时钟、steal time、PV TLB flush x86.c, pvclock-abi.h
Hyper-V Reference TSC 页、synthetic timers、EVMCS hyperv.c (78KB)
Xen PV Shared info 页、runstate、PV clock xen.c (66KB)
ARM64 Stolen time 结构 arm64/kvm/pvtime.c

本文聚焦 KVM pvclock 体系——所有 KVM guest 时钟基础设施的基石。


二、pvclock ABI:32 字节的精密协议

2.1 ABI 定义

// pvclock-abi.h:26-35
struct pvclock_vcpu_time_info {
    u32   version;            // seqlock 版本号
    u32   pad0;
    u64   tsc_timestamp;      // 更新时的 TSC 值
    u64   system_time;        // 更新时的系统时间(纳秒)
    u32   tsc_to_system_mul;  // TSC→纳秒 转换乘数
    s8    tsc_shift;          // TSC→纳秒 转换位移
    u8    flags;              // 标志位
    u8    pad[2];
} __attribute__((__packed__)); /* 32 bytes */

// pvclock-abi.h:37-41
struct pvclock_wall_clock {
    u32   version;
    u32   sec;
    u32   nsec;
} __attribute__((__packed__));

// pvclock-abi.h:43-44
#define PVCLOCK_TSC_STABLE_BIT   (1 << 0)  // TSC 跨 vCPU 稳定
#define PVCLOCK_GUEST_STOPPED    (1 << 1)  // guest 被停止

2.2 Seqlock 读取协议

// pvclock.h:22-38
static __always_inline
unsigned pvclock_read_begin(const struct pvclock_vcpu_time_info *src)
{
    unsigned version = src->version & ~1;  // 屏蔽"更新中"标记
    virt_rmb();   // version 必须在数据之前读取
    return version;
}

static __always_inline
bool pvclock_read_retry(const struct pvclock_vcpu_time_info *src,
                        unsigned version)
{
    virt_rmb();   // 数据必须在 version 之前读取
    return unlikely(version != src->version);
}

Host 写入:version++(奇数)→ 写屏障 → 更新字段 → 写屏障 → version++(偶数)


三、pvclock 数学:TSC delta → 纳秒

3.1 核心公式

current_time = pvti->system_time
             + pvclock_scale_delta(rdtsc() - pvti->tsc_timestamp,
                                   pvti->tsc_to_system_mul,
                                   pvti->tsc_shift)

pvclock_scale_delta (pvclock.h:44-81) 使用优化的内联汇编:

static __always_inline u64 pvclock_scale_delta(u64 delta, u32 mul_frac, int shift)
{
    u64 product;

    if (shift < 0)
        delta >>= -shift;
    else
        delta <<= shift;

    __asm__ (
        "mulq %[mul_frac] ; shrd $32, %[hi], %[lo]"
        : [lo]"=a"(product),
          [hi]"=d"(tmp)
        : "0"(delta),
          [mul_frac]"rm"((u64)mul_frac));

    return product;
}

mulq 执行 64位×64位→128位乘法,shrd 取高 64 位(等效于除以 2^32)。实质:ns = delta * mul_frac * 2^(shift - 32)

3.2 完整计算

// pvclock.h:83-90
static __always_inline
u64 __pvclock_read_cycles(const struct pvclock_vcpu_time_info *src, u64 tsc)
{
    u64 delta = tsc - src->tsc_timestamp;
    u64 offset = pvclock_scale_delta(delta, src->tsc_to_system_mul,
                                          src->tsc_shift);
    return src->system_time + offset;
}

四、kvmclock 实现

4.1 核心数据结构

// kvm_host.h:1482-1506
struct kvm_arch {
    s64 kvmclock_offset;                     // 行1482: VM 全局时间偏移
    raw_spinlock_t tsc_write_lock;           // 行1488: TSC 写入保护
    seqcount_raw_spinlock_t pvclock_sc;      // 行1503: pvclock seqlock
    bool use_master_clock;                   // 行1504: 主时钟模式
    u64 master_kernel_ns;                    // 行1505: 参考内核时间
    u64 master_cycle_now;                    // 行1506: 参考 TSC 周期
};

4.2 时间基准

// x86.c:2398-2401
static s64 get_kvmclock_base_ns(void)
{
    /* Count up from boot time, but with the frequency of the raw clock. */
    return ktime_to_ns(ktime_add(ktime_get_raw(), pvclock_gtod_data.offs_boot));
}

ktime_get_raw() 是单调原始时间(不受 NTP 调整影响),加上启动偏移后得到基于原始时钟的启动时间。

4.3 时钟读取

// x86.c:3247-3264
static void get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
{
    struct kvm_arch *ka = &kvm->arch;
    unsigned seq;
    do {
        seq = read_seqcount_begin(&ka->pvclock_sc);
        __get_kvmclock(kvm, data);
    } while (read_seqcount_retry(&ka->pvclock_sc, seq));
}

u64 get_kvmclock_ns(struct kvm *kvm)
{
    struct kvm_clock_data data;
    get_kvmclock(kvm, &data);
    return data.clock;
}

内层 __get_kvmclock (x86.c:3212-3245) 主时钟模式核心逻辑:

    get_cpu();  // 确保连续操作在同一 CPU
    if (ka->use_master_clock && CONSTANT_TSC) {
        hv_clock.tsc_timestamp = ka->master_cycle_now;
        hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
        kvm_get_time_scale(NSEC_PER_SEC, get_cpu_tsc_khz() * 1000LL,
                           &hv_clock.tsc_shift, &hv_clock.tsc_to_system_mul);
        data->clock = __pvclock_read_cycles(&hv_clock, data->host_tsc);
    } else {
        data->clock = get_kvmclock_base_ns() + ka->kvmclock_offset;
    }
    put_cpu();

主时钟模式下,guest 只需读自己的 TSC,代入 tsc_timestamp + system_time + 缩放参数,即得当前时间。

4.4 时钟更新同步

// x86.c:3163-3193
static void kvm_start_pvclock_update(struct kvm *kvm)
{
    kvm_make_mclock_inprogress_request(kvm);  // 阻止所有 vCPU 进入 guest
    raw_spin_lock_irq(&kvm->arch.tsc_write_lock);
    write_seqcount_begin(&kvm->arch.pvclock_sc);
}

static void kvm_end_pvclock_update(struct kvm *kvm)
{
    write_seqcount_end(&ka->pvclock_sc);
    raw_spin_unlock_irq(&ka->tsc_write_lock);
    kvm_for_each_vcpu(i, vcpu, kvm)
        kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);   // 通知各 vCPU
    kvm_for_each_vcpu(i, vcpu, kvm)
        kvm_clear_request(KVM_REQ_MCLOCK_INPROGRESS, vcpu);
}

static void kvm_update_masterclock(struct kvm *kvm)  // 外部触发入口
{
    kvm_hv_request_tsc_page_update(kvm);
    kvm_start_pvclock_update(kvm);
    pvclock_update_vm_gtod_copy(kvm);
    kvm_end_pvclock_update(kvm);
}

五、Steal Time

5.1 概念

Steal time 度量 guest vCPU 被 host 调度抢占的累计时间。Guest OS 将其报告为 /proc/stat 的 steal 字段,用于正确的 CPU 使用率计算。

5.2 record_steal_time

// x86.c:3748-3839
static void record_steal_time(struct kvm_vcpu *vcpu)
{
    struct kvm_steal_time __user *st;
    u64 steal;
    u32 version;

    if (kvm_xen_msr_enabled(vcpu->kvm)) {
        kvm_xen_runstate_set_running(vcpu);  // Xen: 使用 runstate 代替
        return;
    }

    if (!(vcpu->arch.st.msr_val & KVM_MSR_ENABLED))
        return;

    // ... 确保 HVA cache 有效 ...

    st = (struct kvm_steal_time __user *)ghc->hva;

    // PV TLB flush 探测
    if (guest_pv_has(vcpu, KVM_FEATURE_PV_TLB_FLUSH)) {
        u8 st_preempted = 0;
        asm volatile("1: xchgb %0, %2\n"
                     : "+q" (st_preempted), "+&r" (err), "+m" (st->preempted));
        if (st_preempted & KVM_VCPU_FLUSH_TLB)
            kvm_vcpu_flush_tlb_guest(vcpu);
    }

    // --- seqlock 写入 ---
    unsafe_get_user(version, &st->version, out);
    if (version & 1) version += 1;
    version += 1;
    unsafe_put_user(version, &st->version, out);  // 奇数:更新中
    smp_wmb();

    steal += current->sched_info.run_delay -      // 本次周期被偷走的时间
             vcpu->arch.st.last_steal;
    vcpu->arch.st.last_steal = current->sched_info.run_delay;
    unsafe_put_user(steal, &st->steal, out);

    version += 1;
    unsafe_put_user(version, &st->version, out);  // 偶数:完成
}

核心公式:steal += current_run_delay - last_recorded_run_delay

5.3 标记抢占

// x86.c:5240
static void kvm_steal_time_set_preempted(struct kvm_vcpu *vcpu)
{
    // 在 guest 共享页设置 KVM_VCPU_PREEMPTED 标志
    unsafe_put_user(KVM_VCPU_PREEMPTED, &st->preempted, out);
}

Guest 内核看到此标志后知道时间片被 host 打断,可优化锁等待(如 paravirt_spin_lockpv_lock_ops)。

5.4 Steal Time 结构

  Offset  Size  Field
  ──────  ────  ────────────────
  0       4     version (seqlock)
  4       4     pad
  8       8     steal (纳秒)
  16      1     preempted
  17      3     pad

5.5 调度钩子

vCPU 调度进/出时触发 KVM_REQ_STEAL_UPDATE(x86.c:5237),在下一轮 guest entry 时调用 record_steal_time


六、vDSO 快速路径

KVM 将 per-vCPU pvclock 页映射到 guest vDSO,实现用户态零开销时钟读取:

  Guest 用户态 clock_gettime()
  vDSO → vread_pvclock()
    ├─ pvclock_read_begin()  版本检查
    ├─ rdtsc()              当前 TSC
    ├─ pvclock_scale_delta()  TSC→纳秒
    ├─ system_time + delta   最终时间
    └─ pvclock_read_retry()  版本重试

  ⚡ 全程用户态,零 VM-Exit,零系统调用!
// pvclock.h:92-106
struct pvclock_vsyscall_time_info {
    struct pvclock_vcpu_time_info pvti;
} __attribute__((__aligned__(SMP_CACHE_BYTES)));

#define PVTI_SIZE sizeof(struct pvclock_vsyscall_time_info)

七、Hyper-V Enlightenment

7.1 Hyper-V Reference TSC Page

与 pvclock 类似但格式不同,使用 (sequence, tsc_scale, tsc_offset) 三元组:

time = rdtsc() * tsc_scale / 2^64 + tsc_offset

使用 128位×64位乘法 + 右移 64 位,无需 shift 参数。

7.2 Synthetic Timers

4 个 stimer(HV_X64_MSR_STIMER0-3_CONFIG),直接向 vCPU 投递中断而不经过 LAPIC timer。KVM 用 host hrtimer 实现。

7.3 Enlightened VMCS

hyperv_evmcs.c 实现 Enlightened VMCS:将 VMCS 字段放在共享 guest 物理页中,减少 L0→L1 的 VMREAD/VMWRITE VM-exit(嵌套虚拟化优化)。

7.4 其他 Enlightenments

  • VP Assist Page:host 写入事件信息
  • SynIC:虚拟设备中断轻量传递
  • TSC Emulated Control:RDMSR 访问 TSC 频率无 VM-exit
  • Reenlightenment Controls:guest TSC 频率迁移支持

八、Xen PV 时钟

8.1 架构

KVM 实现 Xen PV 接口的子集(arch/x86/kvm/xen.c, 66KB): - Shared Info Page:vcpu_info 数组 + pvclock + event channels - Runstate:各 vCPU 的状态时间统计 - Xen PV Clock:与 KVM pvclock 共享 ABI,调用 get_kvmclock_ns 作为时间源 (xen.c:272)

8.2 Runstate 结构

struct vcpu_runstate_info {
    uint32_t state;          // RUNNING / RUNNABLE / BLOCKED / OFFLINE
    uint64_t state_entry_time;
    uint64_t time[4];        // 各状态累计时间
};

九、ARM64 PV Time

arch/arm64/kvm/pvtime.c 实现了 ARM 的 stolen time 支持。结构与 x86 类似:

struct pvclock_vm_stolen_time {
    uint32_t revision;
    uint32_t attributes;
    uint64_t stolen_time;     // ns
};

ARM64 通过 SMCCC_ARCH_FEATURES 发现 PV time,通过 PV_TIME_ST IPA 映射共享页。


十、完整时钟架构图

╔══════════════════════════ Host (KVM) ══════════════════════════╗
║                                                                ║
║  kvm_update_masterclock() (x86.c:3187)                        ║
║    ├─ kvm_start_pvclock_update()                                ║
║    │    ├─ KVM_REQ_MCLOCK_INPROGRESS (阻止 guest entry)         ║
║    │    └─ write_seqcount_begin(&pvclock_sc)                   ║
║    ├─ pvclock_update_vm_gtod_copy()                            ║
║    │    ├─ ktime_get_raw() ─── 单调原始时间                     ║
║    │    ├─ rdtsc() ─────────── TSC 快照                        ║
║    │    └─ ktime_get_real_ns() ─ wall clock                    ║
║    └─ kvm_end_pvclock_update()                                  ║
║         ├─ write_seqcount_end(&pvclock_sc)                     ║
║         └─ KVM_REQ_CLOCK_UPDATE (通知各 vCPU)                  ║
║                       │                                        ║
║     ┌─────────────────┼─────────────────┐                     ║
║     ▼                 ▼                 ▼                     ║
║  KVM pvclock      Hyper-V Ref      Xen PV Clock               ║
║  page (per vCPU)  TSC page         (shared info)               ║
║     │                 │                 │                     ║
╚═════╪═════════════════╪═════════════════╪═════════════════════╝
      │                 │                 │
      │  Guest Memory   │                 │
      ▼                 ▼                 ▼
╔══════════════════════════ Guest VM ═══════════════════════════╗
║                                                                ║
║  ┌─ Clock Stack ──────────────────────────────────────────┐   ║
║  │ vDSO 快速路径 (用户态, 零 VM-Exit)                       │   ║
║  │   vread_pvclock()                                      │   ║
║  │     ├─ seqlock begin                                   │   ║
║  │     ├─ rdtsc()                                         │   ║
║  │     ├─ pvclock_scale_delta() ── TSC→ns                 │   ║
║  │     ├─ system_time + delta                             │   ║
║  │     └─ seqlock retry                                   │   ║
║  │                                                        │   ║
║  │ 内核态路径 (需要时)                                      │   ║
║  │   kvmclock 校准, wall clock 同步                        │   ║
║  └────────────────────────────────────────────────────────┘   ║
║                                                                ║
║  /proc/stat (guest):                                          ║
║    user + nice + system + idle + iowait + irq + softirq       ║
║    + steal ← 来自 KVM steal time 页                           ║
╚════════════════════════════════════════════════════════════════╝


Steal Time 更新:
  kvm_sched_out() → run_delay 记录 → KVM_REQ_STEAL_UPDATE
       ... vCPU 等待 CPU ...
  kvm_sched_in()  → KVM_REQ_STEAL_UPDATE
  record_steal_time() (x86.c:3748)
    steal += current_run_delay - last_steal
    写入 shared page (seqlock 协议)
  Guest 读取 → /proc/stat steal 字段


跨 vCPU TSC 同步 (Master Clock):
  CONSTANT_TSC CPU → 所有 pCPU 的 TSC 同速率增长
  master_cycle_now = rdtsc() @ t₀
  master_kernel_ns = ktime_get_raw() + offs_boot @ t₀

  Guest 读取:
    my_tsc     = rdtsc()
    delta      = my_tsc - master_cycle_now
    now        = master_kernel_ns + scale(delta)
  → 无论在哪颗 pCPU 上,时间一致

十一、关键文件和行号索引

文件 行号 功能
pvclock-abi.h:26 26 struct pvclock_vcpu_time_info — 32B ABI
pvclock-abi.h:37 37 struct pvclock_wall_clock
pvclock-abi.h:43 43 PVCLOCK_TSC_STABLE_BIT, PVCLOCK_GUEST_STOPPED
pvclock.h:23 23 pvclock_read_begin() — seqlock 读取开始
pvclock.h:32 32 pvclock_read_retry() — seqlock 重试
pvclock.h:44 44 pvclock_scale_delta() — 核心缩放算法
pvclock.h:84 84 __pvclock_read_cycles() — TSC→时间
pvclock.h:92 92 struct pvclock_vsyscall_time_info — vDSO
kvm_host.h:1482 1482 kvm_arch.kvmclock_offset
kvm_host.h:1503 1503 kvm_arch.pvclock_sc — seqlock
kvm_host.h:1504 1504 use_master_clock
x86.c:2398 2398 get_kvmclock_base_ns() — 时间基准
x86.c:3163 3163 kvm_start_pvclock_update()
x86.c:3171 3171 kvm_end_pvclock_update()
x86.c:3187 3187 kvm_update_masterclock()
x86.c:3247 3247 get_kvmclock() — seqlock 保护读取
x86.c:3258 3258 get_kvmclock_ns()
x86.c:3748 3748 record_steal_time() — steal 更新
x86.c:5240 5240 kvm_steal_time_set_preempted()

系列结语

五篇文章从 KVM 核心(VM/vCPU 生命周期)→ x86 虚拟化(VMX/VMCS、SVM/VMCB)→ MMU(EPT/NPT、shadow/TDP)→ 设备直通(VFIO、IOMMU、posted interrupts)→ 半虚拟化(pvclock、kvmclock、steal time),覆盖了 KVM 的完整内核栈。

搭配之前六个专栏(nvidia-svm、soc-unified-memory、pcie-deep-dive、arm-smmu、intel-iommu、GPU buddy allocator),读者现在拥有从 PCIe 物理层到 KVM 虚拟化层的完整 Linux 内核知识图谱:

  ┌───────────┐ ┌───────────┐ ┌───────────┐
  │ PCIe 深度 │ │ GPU 内存  │ │ ARM SMMU  │
  │ 解析      │ │ 管理      │ │ 深度解析  │
  └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
        └──────────────┼──────────────┘
              ┌────────────────┬────────────────┐
              │ Intel IOMMU    │  SOC Unified   │
              │ 深度解析       │  Memory 解析   │
              └───────┬────────┴───────┬────────┘
                      └───────┬────────┘
              ┌───────────────────────────────┐
              │        KVM 深度解析            │
              │  第1篇: VM/vCPU 生命周期       │
              │  第2篇: VMX/SVM 硬件虚拟化     │
              │  第3篇: EPT/NPT/TDP MMU       │
              │  第4篇: VFIO/IOMMU/中断重映射  │
              │  第5篇: pvclock/steal/Hyper-V  │
              └───────────────────────────────┘

愿这套知识体系助你在 Linux 内核虚拟化领域深耕远行。


💬 评论