Linux虚拟机计时简介(2017)

2020-10-25 10:31:17

在Linux中保持时间并非易事,虚拟化增加了额外的挑战和机遇。在本文中,我将回顾与KVM、Xen和Hyper-V相关的计时技术以及Linux内核的相应部分。

计时是记录某件事花费多长时间的过程或活动。我们需要测量时间的仪器。Linux内核有几个抽象来表示这样的设备:

Clocksource是一种可以在你需要的时候给出时间戳的设备。换句话说,Clocksource是任何允许您获取其值的滴答计数器。

ClockEvent Device是一个闹钟--您可以让该设备发出未来时间的信号(例如,在1毫秒内叫醒我),当闹钟被触发时,您就会收到该信号。

Sched_lock()函数类似于clocksource,但是这个函数的读取成本应该很低(这意味着可以快速获得它的值),因为sched_lock()用于任务调度,而且调度经常发生。我们准备牺牲精确度和其他特性来换取速度。

例如,假设您正在编写一个应用程序,并且需要获取当前时间来进行时间戳。你可能会想出这样的东西:

#include<;stdio.h>;#include<;time.h>;int TIMESTAMP_Function(...){*struct timespec tp;;#include_lt;stdio.h>;#include<;time.h>;int<;time.h>;int&…。对时间戳…执行一些操作。}。

什么是CLOCK_REALTIME?我们还有什么其他的时钟?MAN2 CLOCK_GETTIME给出了答案:

CLOCK_REALTIME CLOCK提供1970年1月1日以来经过的时间。此时钟受NTP调整的影响,可以在系统管理员调整系统时间时向前和向后跳转。

CLOCK_MONTONTON CLOCK提供自固定起始点以来的时间-通常是自引导系统以来。这个时钟受到NTP的影响,但它不能向后跳转。

CLOCK_MONTONTON_RAW CLOCK提供与CLOCK_MONTONTON相同的时间,但此时钟不受NTP调整的影响。

还有几个其他时钟与正在运行的进程或线程相关,但现在让我们跳过它们。

有些应用程序频繁地添加时间戳,每秒数千次,对于这些应用程序,我们必须确保lock_gettime()是快速的。它在Linux中是如何工作的?算法是:

从vDSO(虚拟动态共享对象)调用lock_gettime()。VDSO是由内核提供给每个应用程序的共享库,它包含无需切换到内核即可从用户空间运行的代码。

对于CLOCK_REALTIME_COARSE和CLOCK_MONTOTONIC_COARSE,通过读取适当的计时器结构(内核提供给用户空间以供只读),可以立即给出答案。

对于CLOCK_REALTIME和CLOCK_MONTOTONIC,检查当前使用的时钟源是否可以从用户空间读取,如果可以,则使用其读数来推断适当的计时器值。

如果无法从用户空间读取正在使用的时钟源,请通过执行系统调用切换到内核,并让内核读取当前的时钟源并外推适当的计时器值。

直接从用户空间读取时钟源时的vDSO优化非常重要。以下是我对执行1亿次lock_gettime()读取的测试程序的测试结果。这些测试是在启用和未启用vDSO优化的KVM来宾上执行的:

没有vDSO的kvmlock:#time./lock_gettime_any real:0m15.606s用户:0m2.684s sys和0m12.916s有vDSO的kvmlock:#time./lock_gettime_any real:0m2.365s用户:0m2.362s sys:0m0.001s。

纯用户空间方法比执行syscall快7倍。我们绝对想要这个。但是,是什么让时钟源适用于如此快速的阅读呢?

PC硬件有许多传统的计时设备,但这些设备缺乏上述特征。即:

CMOS RTC:低分辨率(1秒)时间时钟,可选的32768 Hz定时器;无法从用户空间读取。

所有现代x86虚拟机管理程序都会虚拟化此硬件,但所有访问的虚拟化成本太高,无法在Linux中将这些设备中的任何一个用作可靠的时钟源。

在裸x86硬件上,目前最常用的时钟源是TSC(时间戳计数器)。TSC是一种特殊的自动递增CPU寄存器,与使用前面提到的传统硬件相比,它具有许多优势。它具有很高的精度,可以用一条汇编指令(Rdtsc)读取,甚至可以从用户空间读取。然而,台积电也有自己的问题,包括:

其频率未知,需要使用PIT、CMOS或ACPI定时器进行测量。

TSC可以在处理器的一些低功耗C状态下停止。这在现代硬件上通常不会发生。

过去,在一些大型NUMA系统上观察到TSC变得不同步。幸运的是,这样的系统数量有限。

虚拟化带来了额外的挑战。当虚拟机迁移到另一台主机时,其Tsc值会有所不同,因此我们会看到该值中有一个跳转。此外,我们在引导时测量的频率不再是实际的TSC频率。引入了两种类似的方法来解决这些问题:针对Xen和KVM虚拟机管理程序来宾的pvlock(准虚拟化时钟),以及针对Hyper-V来宾的TSC页面。这些是固定频率的时钟,因此除了读取TSC值之外,我们还需要做一些数学运算才能获得读数。

Xen和KVM虚拟机管理程序提出了所谓的pvlock协议来增强TSC,使其适用于虚拟化的来宾。该协议基于主机和来宾之间共享的简单的每CPU结构:

Struct pvlock_vcpu_time_info{*u32*版本;*u32*pad0;*U64*TSC_TIMESTAMP;**U64*SYSTEM_TIME;*u32*tsc_to_system_mul;**s8*tsc_Shift;*U8*标志;*U8*pad[2];};

标志字段指示即使在不同CPU上进行后续调用时,我们是否可以信任读数保持单调承诺,这决定了我们使用来自vDSO的时钟源的能力。如果不能保证单调性,Linux需要跟踪最后一次读取,以确保即使在从一个CPU迁移到另一个CPU时,应用程序也不会看到时间倒退。幸运的是,这在现代硬件上并不经常发生,而且我们的读数很快。

微软用他们自己的TSC页面协议重新发明了PV_CLOCK协议,它类似于PV_CLOCK,但有很大的不同。TSC页是每个虚拟机(而不是每个CPU)的单一结构,因此它无法补偿TSC在多个CPU上不同步的情况。我们不能确定,但猜测在这种情况下,虚拟机管理程序将尝试同步TSC或完全禁用TSC页面机制。

Struct ms_HyperV_TSC_PAGE{*易失性u32 TSC_SEQUENCE;*u32 RESERVED 1;**易失性U64 TSC_SCALE;**易失性s64 TSC_OFFSET;*U64 RESERVED 2[509];};

TSC_SEQUENCE字段中的特殊值0表示该方法被禁用,我们应该重新从Hyper-V提供的另一个虚拟化MSR(特定于型号的寄存器)读取值。这在用户空间代码中是不可能的,而且通常要慢得多。

从硬件辅助虚拟化的早期开始,英特尔就提供了在硬件中对虚拟来宾执行TSC偏移的选项,这意味着来宾的rdtsc读数将返回主机的TSC值+偏移量。不幸的是,这不足以支持不同主机之间的迁移,因为TSC频率可能不同,所以引入了pvlock和TSC页面协议。2015年底,英特尔引入了TSC缩放功能(AMD处理器中已经存在了几年),从理论上讲,这是一个游戏规则改变者,使pvlock和TSC页面协议成为冗余。但是,立即切换到使用普通TSC作为虚拟化来宾的时钟源似乎不切实际;必须确保所有潜在的迁移接收主机都支持该功能,但它尚未广泛使用。还必须执行广泛的测试,以确保从半虚拟化协议切换没有缺点。

因此,我们正在KVM上运行虚拟化来宾并使用kvmlock(实现pvlock协议),或者我们正在运行Hyper-V来宾并将Hyper-V TSC页面作为时钟源。我们一天中的时间在客人和主人之间(或者在同一主机上的不同客人之间)是同步的吗?我们正在读取相同的TSC值,因此结果时间应该是相同的,对吗?嗯,不完全是。主机和来宾的CLOCK_REALTIME时钟都会受到NTP调整的影响,并且可能会随着时间的推移而不同。

为了解决这个问题,Linux-4.11中引入了一个解决方案:用于KVM和Hyper-V的PTP设备。这些设备实际上与PTP时间同步协议无关,不与网络设备一起工作,但它们以PTP(/dev/ptp*)设备的形式出现,因此它们可供现有的时间同步软件使用。

对于KVM来宾,我们需要加载ptp_kvm模块。要使其在重启后加载,我们可以执行如下操作:(在Fedora/RHEL7上)。这对于Hyper-V来宾不是必需的,因为实现设备的模块会自动加载。

将/dev/ptp0作为参考时钟添加到NTP守护程序配置。如果是按年计算,则为:

众所周知,KVM来宾比Hyper-V来宾产生更好的结果,因为设备背后的机制非常不同。Hyper-V主机每五秒向其来宾发送一次时间样本,而KVM来宾可以选择直接对虚拟机管理程序执行超级调用以获取其时间。

虽然Hyper-V PTP设备的精确度不如KVM,但与NTP相比,它的精确度仍然非常高。正如您从上面看到的,客户的系统时间通常与主机的系统时间保持在10US以内,这是一个很好的结果。

Vitaly Kuznetsov将于2017年6月20日在北京LinuxCon ContainerCon CloudOpen China上谈论Linux虚拟机中的计时问题。