时钟和定时器

arm64

在 arm64 上,Hyper-V 虚拟化了 ARMv8 架构系统计数器和定时器。访客虚拟机使用此虚拟化硬件作为 Linux 时钟源和时钟事件,通过标准的 arm_arch_timer.c 驱动程序,就像它们在裸机上一样。Linux vDSO 对架构系统计数器的支持在 Hyper-V 上的访客虚拟机中可用。虽然 Hyper-V 还提供了 TLFS 中描述的合成系统时钟和四个合成的每个 CPU 定时器,但 Linux 内核在 arm64 上 Hyper-V 访客虚拟机中不使用它们。然而,旧版本的 arm64 Hyper-V 仅部分虚拟化了 ARMv8 架构定时器,导致定时器在虚拟机中不生成中断。由于此限制,在这些旧版本的 Hyper-V 上运行当前 Linux 内核版本需要一个树外补丁来使用 Hyper-V 合成时钟/定时器。

x86/x64

在 x86/x64 上,Hyper-V 为访客虚拟机提供了 TLFS 中描述的合成系统时钟和四个合成的每个 CPU 定时器。Hyper-V 还提供了通过 RDTSC 和相关指令访问虚拟化的 TSC 的能力。这些 TSC 指令不会陷入到虚拟机监控程序,因此在虚拟机中提供了出色的性能。Hyper-V 执行 TSC 校准,并通过合成的 MSR 将 TSC 频率提供给访客虚拟机。Linux 中的 Hyper-V 初始化代码读取此 MSR 以获取频率,因此它会跳过 TSC 校准并设置 tsc_reliable。Hyper-V 提供了 PIT(仅在 Hyper-V Generation 1 虚拟机中)、本地 APIC 定时器和 RTC 的虚拟化版本。Hyper-V 在访客虚拟机中不提供虚拟化的 HPET。

Hyper-V 合成系统时钟可以通过合成的 MSR 读取,但此访问会陷入到虚拟机监控程序。作为一种更快的替代方法,访客可以配置一个在访客和虚拟机监控程序之间共享的内存页。Hyper-V 使用 64 位比例值和偏移值填充此内存页。要读取合成时钟值,访客读取 TSC,然后按照 Hyper-V TLFS 中所述应用比例和偏移。生成的值以恒定的 10 MHz 频率前进。如果实时迁移到 TSC 频率不同的主机,Hyper-V 会调整共享页面中的比例和偏移值,以保持 10 MHz 的频率。

从 Windows Server 2022 Hyper-V 开始,Hyper-V 使用硬件支持 TSC 频率缩放,以实现虚拟机在 TSC 频率可能不同的 Hyper-V 主机之间进行实时迁移。当 Linux 访客检测到此 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。