# kvm guest虚拟机时间管理

## 为什么guest虚拟机需要精确的时钟

虚拟化涉及到guest虚拟机时钟精度的多项挑战：

* 中断不能始终同时和即时发送给所有guest虚拟机。这就导致在虚拟机中的中断不是真实的中断。相反，虚拟机的中断是通过host主机注入到虚拟机中的。
* host主机可能在运行其他虚拟机，或者其他进程。精确的时钟通常依赖中断，就不能始终保证。

guest虚拟机失去精确的时钟可能会导致网络应用和进程，会话校验，迁移，以及其他依赖时间戳的网络活动出现异常。

KVM通过为guest虚拟机提供一个paravirtual时钟（`kvm-clock`）来避免上述问题。然而，在影响时钟精度的操作，例如guest虚拟机迁移，需要非常仔细测试时间。

> `重要！！！`
>
> 为避免上述问题，在host主机和guest虚拟机上，都要运行网络时钟协议（NTP）服务。在RHEL 6和早期版本，NTP是通过`ntpd`服务来是实现的。在最新的RHEL 7系统，时钟同步服务由`ntpd`或者`chronyd`服务实现。
>
> 注意：在虚拟机中，`chrony`有一些优势，详细可参考「Red Hat Enterprise Linux 7 System Administrator's Guide」章节[Configuring NTP Using the chrony Suite](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_the_chrony_Suite.html)和[Configuring NTP Using ntpd](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/ch-Configuring_NTP_Using_ntpd.html)。
>
> `chronyd`比`ntpd`具有优势的方便包括：
>
> * 只需要间歇性访问时钟源
> * 适合拥塞的网络而工作良好
> * 通常同步时钟更快更精确
> * 比`ntpd`可以更快调整主机时间
> * `chronyd`可以在Linux主机中以一个较大范围来调整时钟频率，也就是允许在主机时钟中断或不稳定时工作。例如，适合在虚拟机中
>
> 不过，`ntpd`在标准协议以及支持更多的时钟参考源上比`chronyd`具有优势。

## guest虚拟机时间同步的机制

默认情况下，guest使用以下方式和hypervisor之间进行时间同步：

* guest系统启动时，会从模拟的实时时钟（emulated Real Time Clock, RTC）读取时间
* 当NTP服务初始化之后，它将自动同步guest时钟。之后，随着常规的guest操作，NTP在guest内部执行时钟校准
* 当guest在暂停（pause）或者还原进程之后继续，通过管理软件（例如[virt-manager](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/chap-Managing_guests_with_the_Virtual_Machine_Manager_virt_manager)）发出一个同步guest时钟到指定值的指令。这个同步工作值在[QEMU guest agent](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/chap-QEMU_Guest_Agent)安装在guest系统中并且支持这个功能的时候才会工作。这个guest时钟同步的值通常就是host主机的时钟值。

## 稳定时间戳计数器（Constant Time Stamp Counter, TSC）

现代Intel和AMD处理器提供了一个稳定时间戳计数器（Constant Time Stamp Counter, TSC）。这个稳定的TSC的计数频率不会随着CPU核心更改频率而改变，例如，节电策略导致的cpu主频降低`不会`影响TSC计数。一个CPU具有稳定的TSC频率对于使用TSC作为KVM guest的时钟源时非常重要的。

要查看CPU是否具有稳定的时间戳计数器（constant TSC）需要检查`cpuinfo`中是否有`constant_tsc`标志：

```
cat /proc/cpuinfo | grep constant_tsc
```

如果上述命令没有任何输出，则表明cpu缺少稳定的TSC特性，需要采用其他时钟源。详见下文。

> windows虚拟机同时使用Real-Time Clock(RTC)和Time Stamp Counter(TSC)。对于Windows guest，Real-Time Clock可以用于取代TSC作为所有的时钟源来解决guest时间问题。详细设置和介绍见[Red Hat Enterprise Linux 5 Virtualization Guide: Chapter 17. KVM guest timing management](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/5/html/Virtualization/chap-Virtualization-KVM_guest_timing_management.html)

## 针对没有稳定时间戳计数器（constant tsc）的主机配置

对于没有constant TSC主频的系统不能使用TSC作为虚拟机的时钟源，并且需要附加的配置。对于guest虚拟机，带有精确时间保持功能的电源管理功能接口必须被禁用，以在KVM中精确维持时间。

> `重要`：这部分介绍只针对AMD revision F CPU

如果CPU缺少`constant_tsc`标志位，则需要关闭所有的电源管理功能。（电源管理会导致主频变化，如果CPU没有tsc会导致计时不准确）。

如果主机上TSC不稳定，深度的C状态或者迁移到其他具有更快TSC的主机就有可能会导致`cpufreq`变化。为了避免内核进入深度C状态（也就是深度节电状态），需要在内核启动参数加上`processor.max_cstate=1`。

举例：

```
term Red Hat Enterprise Linux Server (2.6.18-159.el5)
        root (hd0,0)
    kernel /vmlinuz-2.6.18-159.el5 ro root=/dev/VolGroup00/LogVol00 rhgb quiet processor.max_cstate=1
```

要持久化这个修改，则编辑`/etc/default/grub`文件中`GRUB_CMDLINE_LINUX`配置。例如希望更改成激活`emergency`模式：

```
GRUB_CMDLINE_LINUX="emergency"
```

要禁用`cpufeq`（注意：只对**没有**`constant_tsc`的主机需要这样设置），安装`kernel-tools`然后激活`cpupower.service`（`systemctl enable cpupower.service`）。如果想要在每次guest虚拟机启动时禁止这个服务，修改`/etc/sysconfig/cpupower`配置文件，修改`CPUPOWER_START_OPTS`和`CPUPOWER_STOP_OPTS`两个参数。有关限制值可以在`/sys/devices/system/cpu/<cpuid>/cpufreq/scaling_available_governors`文件可以查看。详细信息参考[Red Hat Enterprise Linux 7 Power Management Guide](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Power_Management_Guide/index.html)

## Red Hat Enterprise Linux Guest所需的时间管理参数

对于不同的Red Hat Enterprise Linux guest虚拟机，需要设置对应的内核参数以确保时间正确同步。这些参数可以设置在guest虚拟机的`/etc/grub2.cfg`配置的`/kernel`行。

> **注意**
>
> Red Hat Enterprise Linux 5.5及以后版本，Red Hat Enterprise Linux 6.0及以后版本，以及Red Hat Enterprise Linux 7默认使用`kvm-clock`作为guest系统的时钟源。运行`kvm-clock`避免了附加的内核参数，所以Red Hat推荐使用`kvm-clock`。

详细的不同guest操作系统设置内核参数见[Chapter 8. KVM Guest Timing Management](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/chap-kvm_guest_timing_management)。当前大多数新操作系统版本默认使用`kvm-clock`就可以了，避免繁琐的内核参数设置。

> **`更改系统的时钟源会影响系统性能，所以需要谨慎使用，实际解决方法建议排查影响系统稳定性的根本原因，避免修改虚拟机时钟源!`**

## `Clocksource tsc unstable (delta = xxxxxxxx ns)`

近期在线维护KVM服务器，发现在虚拟机创建的过程中，会出现物理服务器上正在运行的虚拟机时间短暂偏移数秒到10秒左右，虽然虚拟机的`ntp`服务能够最终纠正，但是对运行的业务系统造成了困扰。

排查发现虚拟机创建的时候，在物理服务器的`messages`日志中显示PassThrough模式的ixgbe网卡的VF驱动会做一次`Reload the VF driver to make this change effective`。此时，运行的虚拟机的`messages`日志显示虚拟时钟源不稳定：

```
[2423264.824259] Clocksource tsc unstable (delta = 6824818792 ns).  Enable clocksource failover by adding clocksource_failover kernel parameter.
```

检查kvm虚拟机时钟源

```
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
kvm-clock tsc acpi_pm

#cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock
```

参考 [\[Howto\] Fixing unstable clocksource in virtualised CentOS](https://liquidat.wordpress.com/2013/04/02/howto-fixing-unstable-clocksource-in-virtualised-centos/)

在虚拟化之前的计算机时期是通过tick计数来测量时间的：操作系统初始化一个设备来发送终端 - 称为ticks - 作为一个持久的固定的频率。操作系统通过计算这个中断，例如每秒100次，来知晓经过了多少时间。

然而，在运行的虚拟机中，就不能保证虚拟机能够获得足够资源来保证固定的tick（滴答）速率。假设一个物理服务器运行了大量的虚拟机，就有可能在某个瞬间某些虚拟机不能活的足够资源来产生tick（滴答）。如果物理服务器的负载非常高，则ticks的一个backlog(后台日志)就会建立，并且可能不断增长。这就导致vm虚拟机的时钟延后。如果backlog过大，则这个ticks（滴答）甚至可能被抛弃，这样虚拟机的时钟源就会不稳定并且vm虚拟机时间就会落后。

当时钟源不稳定的时候，Linux就会尝试找出并报告这个现象，此时在日志中就会出现如下事件：

```
Clocksource tsc unstable (delta = -102057770 ns).  Enable clocksource failover by adding clocksource_failover kernel parameter.
```

要解决这个问题，我们需要首先找出哪些是可用的时钟源，以及当前使用的时钟源：

```
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
kvm-clock tsc hpet acpi_pm
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock
```

解决这个时钟源的问题通常是添加另外一个时钟源，也就是`failover clock source`，例如`hpet`或`acpi_pm`（这里可以看到报错显示是`tsc unstable`，所以接下来优选的顺序是`hpet`和`acpi_pm`）。[详细的时钟源解释](http://www.makelinux.net/books/ulk3/understandlk-CHP-6-SECT-1)见"Understanding the Linux Kernel, 3rd Edition" by Daniel P. Bovet, Marco Cesati。

* 具体解决步骤 - 在KVM虚拟机的启动内核参数中添加`failover clock source`如下

```
kernel /vmlinuz-2.6.32-358.18.1.el6.x86_64 ro root=UUID=a4eea0d1-3150-4b3f-bc4b-204413280ac7 <其他内核参数> clocksource_failover=acpi_pm
```

然后重启虚拟机。

如果对虚拟机的时钟保持问题想进一步了解，可参考vmware的文档[Timekeeping in VMware Virtual Machines](http://www.vmware.com/pdf/vmware_timekeeping.pdf)

> **`警告！`**
>
> 使用`hpet`和`acpi_pm`时钟源会导致虚拟机性能下降；使用`tsc`作为时钟源能够提高guest虚拟机性能，但是带来的缺点是guest虚拟机时钟会偏移，所以务必要在guest虚拟机中运行`ntpd`服务，确保虚拟机时间精准。

## guest虚拟机采用不同时钟源的性能测试

* 可用guest时钟源及默认时钟源`kvm-clock`

```
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
kvm-clock tsc acpi_pm
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock
```

* 测试性能指令：`sysbench cpu --threads=8 --time=300 --cpu-max-prime=100000 run`
* 设置时钟源为`kvm-clock`，性能测试输出如下：

```
echo "kvm-clock" > /sys/devices/system/clocksource/clocksource0/current_clocksource
```

`top`输出：

```
top - 11:39:53 up 23:27,  3 users,  load average: 6.74, 2.48, 0.94
Tasks: 192 total,   1 running, 191 sleeping,   0 stopped,   0 zombie
Cpu0  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu5  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu7  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32880448k total,   854516k used, 32025932k free,    67620k buffers
Swap:        0k total,        0k used,        0k free,   475092k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                                         
25089 root      20   0 88160 2476 1416 S 799.7  0.0  14:38.32 sysbench
```

`sysbench`输出

```
CPU speed:
    events per second:   278.78

General statistics:
    total time:                          300.0210s
    total number of events:              83639

Latency (ms):
         min:                                 27.98
         avg:                                 28.69
         max:                                126.48
         95th percentile:                     33.12
         sum:                            2399991.83

Threads fairness:
    events (avg/stddev):           10454.8750/37.61
    execution time (avg/stddev):   299.9990/0.01
```

* 设置时钟源为`tsc`，性能测试输出如下：

```
echo "tsc" > /sys/devices/system/clocksource/clocksource0/current_clocksource
```

```
CPU speed:
    events per second:   279.16

General statistics:
    total time:                          300.0256s
    total number of events:              83756

Latency (ms):
         min:                                 27.98
         avg:                                 28.65
         max:                                 43.79
         95th percentile:                     31.94
         sum:                            2399975.15

Threads fairness:
    events (avg/stddev):           10469.5000/37.98
    execution time (avg/stddev):   299.9969/0.01
```

* 设置时钟源为`acpi_pm`，性能测试输出如下：

```
echo "acpi_pmtsc" > /sys/devices/system/clocksource/clocksource0/current_clocksource
```

```
CPU speed:
    events per second:   272.72

General statistics:
    total time:                          300.0241s
    total number of events:              81824

Latency (ms):
         min:                                 28.57
         avg:                                 29.32
         max:                                 43.81
         95th percentile:                     31.94
         sum:                            2398952.96

Threads fairness:
    events (avg/stddev):           10228.0000/29.83
    execution time (avg/stddev):   299.8691/0.01
```

上述性能测试可以看到，300秒内完成测试性能显示： `tsc > kvm-clock > acpi_pm`

\| guest时钟源 | kvm-clock | tsc | acpi\_pm | | 300秒完成event | 83639 | 83756 | 81824 | | 性能 | `基准` | +0.14% | -2.17% |

> 注意：这个测试只是一个举例，实际上性能对比需要`case by case`，针对某些特定不断访问时钟的应用，上述调整guest虚拟机时钟源会有比较大的差异，普通应用则可能差距不大或不明显。

## 参考

* [Red Hat Enterprise Linux 7 Virtualization Deployment and Administration Guide: Chapter 8. KVM Guest Timing Management](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/chap-kvm_guest_timing_management)
* [Red Hat Enterprise Linux 5 Virtualization Guide: Chapter 17. KVM guest timing management](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/5/html/Virtualization/chap-Virtualization-KVM_guest_timing_management.html)
* [Timekeeping Virtualization for X86-Based Architectures](https://www.kernel.org/doc/Documentation/virtual/kvm/timekeeping.txt) 内核文档中Red Hat撰写的针对KVM的timekeeping.txt，提供了详尽的底层资料
* [\[Howto\] Fixing unstable clocksource in virtualised CentOS](https://liquidat.wordpress.com/2013/04/02/howto-fixing-unstable-clocksource-in-virtualised-centos/)
* [How to fix Clocksource tsc unstable](https://blog.laimbock.com/2013/09/12/how-to-fix-clocksource-tsc-unstable/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huataihuang.gitbook.io/cloud-atlas-draft/virtual/kvm/deployment_and_administration/kvm_guest_timing_management.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
