準仮想化クロックの遅れの原因を追究してみます
前回、準仮想化クロックが5分で6秒くらい遅れることがわかりました。
少し細かな話になりますが、ソースコードを探索して原因の追究ととりあえずの対処法を紹介します。
VirtualBoxのソースコードを見てみる
まずはVirtualBoxのソースコードをダウンロードします。以前いじったときから5年ほど時間がたっていました。
Googleの検索でparavirtualizationを検索しているとき、VirtualBox 5.0 Beta2 releasedのリリースノートに
VMM: added support for using Paravirtualization providers with raw-mode VMs
とありました。
raw-mode VMsはなんだろうということで、svn logでraw-modeを検索してみます。
すると、
r55068 disble GIM for raw-mode VMs
とあって、GIMは何だろうということになりました。そこでソースコードを検索してみると、
vbox/src/VBox/VMM/VMMR3/GIM.cppに説明がありました。
GIM providerはparavirtualization interfaceとして参照される。
準仮想化インターフェースはソースコードではGIMと呼ばれているんですね。GIMとは、Guest Interface Manager Deviceの略で準仮想化インターフェースのすべてを扱っています。
ソースコードには次のような説明があります。
準仮想化の1つのゴールはゲストをもっと正確に、もっと効率的にすることです。たとえばゲストOSは、ホストプロセッサの正しいTSCの周波数がGIM providerから供給されることに依存します。これによりゲストはTSC自身をキャリブレートすることを避けることができ、正確さと効率がよくなります。
ということは、VirtualBoxがTSCの周波数を供給していることになります。
また、ゲストOSの設定ファイルのところにあるVBox.logというログを眺めて、TSCを探します。すると、TSCモードをVirtTscEmulatedからRealTSCOffsetに変えると出ています。このRealTSCOffsetがあやしいです。
これは、vbox/src/VBox/VMM/VMMR3/TM.cppにあります。
このファイルを見てみると、u64CpuHzがCPUの周波数を設定しているようです。また、tmR3CalibrateTSC()という関数がTSCのキャリブレーションをしていそうです。
また、kvm-clockはメモリーにTSCと、それをシステム時間に変換するスケールファクターを保存していたことを頼りに、どこで設定しているかを探します。
すると、vbox/src/VBox/VMM/include/GIMKvmInternal.hにstruct GIMKVMSYSTEMTIMEという構造体のu32TscScaleがTSCをシステム時間に変換するスケールファクターであることがわかります。今度は、u32TscScaleを設定しているところをソースコードで探します。
u32TscScaleは、vbox/src/VBox/VMM/VMMR3/GIMKvm.cppでpVM->cTscTicksPerSecondを代入しています。
また、いろいろな情報をログファイルに出力していることに気づき、VirtualBoxのゲストの設定ファイルのところにあるVBox.logをGIMやTSCを目安に眺めてみるとcTscTicksPerSecondも数値が記録されていることがわかります。
このように、関連するであろうファイル、変数やマクロ名をソースコードをから探し出しました。
完全に理解するところまでは行かなかったのですが、重要と思われる変数などは抜き出せたように思います。
VMware Playerで気づいたこと
VirtualBoxと平行して使っていたVMware Playerでdmesg |grep tscとdmesg |grep TSCをやると次のように出ます。
kazu@Mars:~$ dmesg |grep tsc
[ 0.000000] tsc: Detected 3400.109 MHz processor
[ 1.595236] Switched to clocksource tsc
kazu@Mars:~$ dmesg |grep TSC
[ 0.000000] TSC freq read from hypervisor : 3400.109 MHz
CPUの周波数3400MHzとでて、TSCの周波数はhypervisorから供給されていると出ています。これは、今使っているCPUの定格の周波数です。
じゃあVirtualBoxの場合はどうなんだとなりました。ログファイルでcTscTicksPerSecondを見てみると、
00:00:01.277674 TM: cTSCTicksPerSecond=3 478 274 491 (0xcf5241bb) enmTSCMode=1 (VirtTscEmulated)
3478274491Hzということみたいです。3478MHzということで、VMware Playerより大きいです。
また、この違いが1分でどれくらいの差になるのか計算すると、
60 * 3400109000 / 3478274491 = 58.65 (秒)
となりました。ほぼ、5分で6から7秒の遅れということで、前回の計測とあっています。
ここまでわかっていること
- ゲストOSのクロックソースがkvm-clockとtscの場合時計が遅れる。遅れるのは5分で6秒ほど。
- 準仮想化クロックでは、TSCの周波数はハイパバイザーがゲストOSに伝える。ゲストOSではキャリブレートしない。
- VMware Playerとの比較では、cTSCTicksPerSecondの値が大きい。その値は、時計の遅れと一致している。
ということがわかりました。
とりあえずの対処法
VirtualBoxのフォーラムをGoogleでcTSCTicksPerSecondを検索すると、VBoxManageでゲストOSのTSCTicksPerSecondを設定する項目があるという情報を見つけました。
[solved] detected frequency
この項目が、準仮想化にも関係するのかよくわかりませんでしたが、次のように設定してから立ち上げました。
VBoxManage setextradata "[VMName]" "VBoxInternal/TM/TSCTicksPerSecond" 3400109000
周波数の値は、VMware Playerの値を使っています。わからなければ、CPUの定格の周波数でいいと思います。すると、時計の遅れはなくなりました。
これは、とりあえずの対処になります。TSCの周波数を決め打ちしています。TSCの周波数がCPUの電源管理状態によらず一定でないとできない対処です。最近のCore iシリーズ以降であれば使えると思います。でもこれで、VirtualBoxのTSCの周波数を決めるところにバグがあることがわかりました。
原因がつかめたようでよかったです。早くバグが修正されるといいなと思います。