kvm-clockは信用できるか


先日, サーバーのGCCが古いからといってサーバーでGCCコンパイルを始めたり, IPv6に対応していないことを発見して指摘したりと, なにかと騒がせがちな例のあの人が新たな指摘を行った.

全く暇なのだろうと思う. さて, そこでNTPの設定をするかと考えたが, ふと, KVM上のLinuxシステムがどのように時刻同期されるか気になったので調べた. 信頼できる情報は少なく, 結局カーネルのドキュメンテーションとソースコードを読むことになり, ついにkvm-clockは当方の環境ではNTPと同等, あるいはそれ以上に信頼できると結論づけた. また, kvm-clockはオーバヘッドを最小限に留めるよう, 確実に設計されているという知見も得られた. 以下にその詳細を記す.

kvm-clockの信頼性

Linuxは様々な時刻情報の提供元をサポートしている. kvm-clockはそのx86 KVMに特有のものである. kvm-clockはKVMがMSRを通して提供されるインターフェイスを用いたものだ. MSR (Model-Specific Register)は本来, CPU機能の制御に用いられているが, KVMにおいてはハードウェア支援により仮想化されており, MSRへのアクセスが行われるとハイパーバイザーに転送される.

kvm-clockで特に重要になるのがMSR_KVM_SYSTEM_TIME_NEWだ. ドキュメンテーションに詳細が記されている.

kernel/git/torvalds/linux.git - Linux kernel source tree

これにより, その時点でのTSC (Time Stamp Counter) とmonotonic timeが指定されたメモリ領域に書き込まれる.

TSCは, CPUが内蔵するクリスタルによって提供されるもので, 通常のクロックのように変調されることがない. そのため, CPUやバスなどの速さの変動に依らない. また, 読み出す際のオーバーヘッドが非常に小さい. ただし, CPUがスリープしている際などには停止する可能性がある.

monotonic timeは, その名の通り単調に推移する時刻である. つまり, システムの時刻設定の変更やスリープなどに依らない. ただし, システムの電源が絶たれた際などには停止する可能性がある. 現実世界の時間を求めるには, monotonic timeの差がMSR_KVM_WALL_CLOCK_NEWによって与えられるので, これを用いれば良い.

さて, ここで問題になるのがTSCとmonotonic timeの取得手順だ. ハイパーバイザーでは, これらは独立して取得できることになっている. 実際にそのようなコードがある.

https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kvm/x86.c?h=v4.9#n1855

		host_tsc = rdtsc();
		kernel_ns = ktime_get_boot_ns();

つまり, TSCとmonotonic timeを取得する間の時間が誤差として加えられてしまうのだ. 果たしてこれはゲストがNTPで取得する時刻の誤差より大きいだろうか?

結論としては, これはNTPと同等, あるいはそれ以上に正確である. まず, rdtsc関数の実装を見てみると, 次のようにインラインアセンブラで実装されていることが分かる.

https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/msr.h?h=v4.9#n152

	asm volatile("rdtsc" : EAX_EDX_RET(val, low, high));

これには本当に小さなオーバーヘッドしか存在しないはずだ. ktime_get_boot_ns関数はカーネル内部で通常使われるものなので, これによるオーバーヘッドも十分に小さいはずだ. それに対して, ゲストからNTPで時刻を取得する場合, ネットワークのオーバーヘッドを受ける. 特に, 当方の環境ではネットワーク接続も仮想化されているだろうから, そのオーバヘッドは比較的大きいだろう. kvm_clockを用いている場合には, NTPは用いるべきではないだろう.

kvm-clockのオーバーヘッド

kvm-clockが用いるMSRによるVMからの脱出と, 再侵入はオーバーヘッドが大きく, 時刻を取得する際に毎度MSRにアクセスするような実装ではあまりにも非効率となってしまう. そのため, kvm-clockは必要なMSRへのアクセスは最小限に留められている.

まずMSR_KVM_WALL_CLOCK_NEW. 最終的にread_persistent_clock64関数に抽象化されるが, これは初期化, サスペンド, そして再開の際にのみ呼ばれる. オーバヘッドは無視できる.

MSR_KVM_SYSTEM_TIME_NEWarch/x86/kernel/kvmclock.c内のstatic関数, kvm_register_clockに抽象化される. これも初期化と再開の際にのみ呼ばれる. また, CPU構成が変更されるなどした場合には, 必要に応じてハイパーバイザー側でKVM_REQ_CLOCK_UPDATEが発行され, 先にMSR_KVM_SYSTEM_TIME_NEWによって指定されていたメモリ領域の内容を更新するようにしている.

このように, 特別なイベントが発生しない限り, オーバヘッドが発生しないように実装されている.

最後に

以上より, kvm-clockは正確で, オーバヘッドも小さいということが分かった. めでたしめでたし.

…本当にそうだろうか. 実は, こうやって書いているうちに1つ疑問点が残っていることがわかった. それは, wall clockの更新頻度である. 先に初期化やサスペンド, 再開の際にのみ呼ばれるとしたが, これではあまりにも少ない気がするのだ. NTPでそうするように定期的な同期をすべきではないか, ハイパーバイザー側での変更が伝搬する仕組みが必要ではないか.

謎だ. なお, うちのサーバーの時計は, 狂っている.

[top]