时钟和定时器¶
arm64¶
在 arm64 上,Hyper-V 虚拟化了 ARMv8 架构系统计数器和定时器。Guest VM 使用此虚拟化硬件作为 Linux 时钟源和时钟事件,通过标准的 arm_arch_timer.c 驱动程序,就像在裸机上一样。Linux vDSO 对架构系统计数器的支持在 Hyper-V 上的 Guest VM 中正常运行。虽然 Hyper-V 还提供了合成系统时钟和四个合成的每 CPU 定时器,如 TLFS 中所述,但 Linux 内核在 arm64 上的 Hyper-V guest 中不使用它们。然而,早期版本的 arm64 Hyper-V 仅部分虚拟化 ARMv8 架构定时器,因此定时器不会在 VM 中生成中断。由于此限制,在这些早期 Hyper-V 版本上运行当前 Linux 内核版本需要一个树外补丁,以使用 Hyper-V 合成时钟/定时器代替。
x86/x64¶
在 x86/x64 上,Hyper-V 为 Guest VM 提供了一个合成系统时钟和四个合成的每 CPU 定时器,如 TLFS 中所述。Hyper-V 还通过 RDTSC 及相关指令提供对虚拟化 TSC 的访问。这些 TSC 指令不会陷入到 hypervisor,因此在 VM 中提供了出色的性能。Hyper-V 执行 TSC 校准,并通过合成 MSR 将 TSC 频率提供给 Guest VM。Linux 中的 Hyper-V 初始化代码读取此 MSR 以获取频率,因此它会跳过 TSC 校准并设置 tsc_reliable。Hyper-V 提供了虚拟化的 PIT(仅在 Hyper-V 第 1 代 VM 中)、本地 APIC 定时器和 RTC。Hyper-V 不在 Guest VM 中提供虚拟化的 HPET。
可以通过合成 MSR 读取 Hyper-V 合成系统时钟,但此访问会陷入到 hypervisor。作为一种更快的替代方案,guest 可以配置一个内存页,使其在 guest 和 hypervisor 之间共享。Hyper-V 使用 64 位比例值和偏移值填充此内存页。要读取合成时钟值,guest 读取 TSC,然后应用 TLFS 中所述的比例和偏移。生成的价值以恒定的 10 MHz 频率推进。如果实时迁移到具有不同 TSC 频率的主机,Hyper-V 会调整共享页面中的比例和偏移值,以便保持 10 MHz 频率。
从 Windows Server 2022 Hyper-V 开始,Hyper-V 使用硬件支持 TSC 频率缩放,以实现在 TSC 频率可能不同的 Hyper-V 主机之间实时迁移 VM。当 Linux guest 检测到此 Hyper-V 功能可用时,它更喜欢使用 Linux 的标准基于 TSC 的时钟源。否则,它使用通过共享页面实现的 Hyper-V 合成系统时钟的时钟源(标识为“hyperv_clocksource_tsc_page”)。
Hyper-V 合成系统时钟可以通过 vDSO 提供给用户空间,gettimeofday() 及相关系统调用可以完全在用户空间中执行。vDSO 通过将具有比例和偏移值的共享页面映射到用户空间来实现。用户空间代码执行相同的算法,读取 TSC 并应用比例和偏移以获得恒定的 10 MHz 时钟。
Linux 时钟事件基于 Hyper-V 合成定时器 0 (stimer0)。虽然 Hyper-V 为每个 CPU 提供 4 个合成定时器,但 Linux 仅使用定时器 0。在早期版本的 Hyper-V 中,来自 stimer0 的中断会导致 VMBus 控制消息,该消息由 vmbus_isr() 解复用,如 VMBus 文档中所述。在较新版本的 Hyper-V 中,stimer0 中断可以映射到架构中断,这被称为“直接模式”。Linux 更喜欢在可用时使用直接模式。由于 x86/x64 不支持每 CPU 中断,因此直接模式在所有 CPU 上静态分配一个 x86 中断向量 (HYPERV_STIMER0_VECTOR),并显式编码以调用 stimer0 中断处理程序。因此,来自 stimer0 的中断记录在 /proc/interrupts 中的“HVS”行上,而不是与 Linux IRQ 相关联。基于虚拟化 PIT 和本地 APIC 定时器的时钟事件也可以工作,但首选 Hyper-V stimer0。
Hyper-V 合成系统时钟和定时器的驱动程序是 drivers/clocksource/hyperv_timer.c。