kvm-clockは信用できるか
先日, サーバーのGCCが古いからといってサーバーでGCCコンパイルを始めたり, IPv6に対応していないことを発見して指摘したりと, なにかと騒がせがちな例のあの人が新たな指摘を行った.
あれ、かぐちょのサーバーってntp同期していない・・・?それともこのコマンドが違うだけ・・・?@173210 pic.twitter.com/Q2P3fK8zvY
— yumetodo-C++erだけど化学科 (@yumetodo) 2017年1月11日
全く暇なのだろうと思う. さて, そこで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
関数の実装を見てみると,
次のようにインラインアセンブラで実装されていることが分かる.
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_NEW
はarch/x86/kernel/kvmclock.c
内のstatic関数, kvm_register_clock
に抽象化される.
これも初期化と再開の際にのみ呼ばれる. また, CPU構成が変更されるなどした場合には,
必要に応じてハイパーバイザー側でKVM_REQ_CLOCK_UPDATE
が発行され,
先にMSR_KVM_SYSTEM_TIME_NEW
によって指定されていたメモリ領域の内容を更新するようにしている.
このように, 特別なイベントが発生しない限り, オーバヘッドが発生しないように実装されている.
最後に
以上より, kvm-clock
は正確で, オーバヘッドも小さいということが分かった. めでたしめでたし.
…本当にそうだろうか. 実は, こうやって書いているうちに1つ疑問点が残っていることがわかった. それは, wall clockの更新頻度である. 先に初期化やサスペンド, 再開の際にのみ呼ばれるとしたが, これではあまりにも少ない気がするのだ. NTPでそうするように定期的な同期をすべきではないか, ハイパーバイザー側での変更が伝搬する仕組みが必要ではないか.
謎だ. なお, うちのサーバーの時計は, 狂っている.