ティックレスLinuxカーネルと準仮想化クロック

ゲストOSの時計の遅れにはティックレスLinuxカーネルが関係していた

このところVirtualBoxを契機として、準仮想化クロックについて調べてきました。
ところが、SUSEのマニュアルがゲストOSでNTPを使わないことを推奨していることの理由がどうしてもわかりませんでした。
昔QEMUにかかわったとき、Linuxの割り込み頻度が1000Hzから250Hzに変わったことを思い出しました。

すると、こんな記事が見つかりました。

仮想化しても時刻ずれが発生しないCentOS6

最近のLinuxカーネルはティックレスになっていて、仮想化しても時刻がずれないというのです。そこで、ティックレスカーネルについて調べてみました。

Linuxの割り込み頻度の変遷

VMwareのサイトにLinuxの割り込み頻度の変遷がまとめられています。

Linuxの2.4ではHZ=100Hzだったのが、2.6ではHZ=1000Hzに変わり、2.6.14以降はHZ=250Hzになっています。

ティックレスカーネルのサポートはLinux 2.6.18より始まっています。

ティックレスカーネルのしくみ

昔はPIT(Programmable Interval Timer)を使って定期的に割り込みをかけていました。100Hzなら10msに一度カーネルに割り込みが発生するようにプログラムします。割り込みが発生したら10msたったとして時計を進めていたわけです。

Linuxの割り込み頻度は、多いほうが時計の精度が高いように思います。しかし割り込みの時間内に処理が終わらず次の割り込みが起こってしまうと割り込みが失われた状態になり時計が狂ってきます。

仮想化環境で時計の狂いが大きかったのはこれが大きな原因です。

ティックレスカーネルは時間を決めて定期的に割り込みをかけることをやめてしまうものです。その代わり1度きりのone shot timerというタイマーを使って割り込みまでの時間を設定して割り込みをかけます。動いているプロセスが少ないときは長い時間割り込まないようにします。動いているときは、定期的に割り込みます。

時間があらかじめ決まっていないので、カーネルがsleepしてどれくらい時間がかかったかを毎度ハードウェアをチェックする必要があります。それをクロックソースと呼んでいます。

クロックソースとして代表的なものが、TSC(Time Stamp Counter)、ACPI PM(ACPI Power Management timer)、HPET(High Precision Event Timer)などがあります。

ティックレスカーネルの適用状況

Linuxのディストリビューションがティックレスカーネルを採用した時期はまちまちです。以下にまとめてみました。

ディストリビューション バージョン 時期
Red Hat Enterprise Linux 6 2010-11-09
CentOS 6 2011-07-10
Fedora 7 x86 2007-05-31
Fedora 8 x86_64 2007-11-8
Ubuntu 7.10 2007-10-18
SUSE Linux Enterprise Server 12 2014-10-28

UbuntuとFedoraが同時期で、Red HatとCentOSが次で、SUSEの順番です。

使っている人が多いと思われるCentOSは6からのサポートです。最近になって時計の話題を見かけることが少なくなったことはこのためだと思われます。

また、Ubuntuの方が時計が正確だという報告があったのですがティックレスカーネルの採用時期が早かったためでしょう。

SUSEは本当に最近のサポートですね。

ティックレスカーネルと準仮想化クロックと組み合わせると

ティックレスカーネルになってクロックソースを指定できるようになりました。最近のディストリビューションでは準仮想化クロックを採用しているところばかりです。準仮想化クロックといってもこれまで調べたようにその中身はTSCを使っています。ゲストOSの時計が正確になったのは、CPUの周波数の変化によってTSCの周波数が変化しなくなっていることも大きいと思います。

Linuxカーネルの進化とCPUの進化によって時計の正確さが増したということでしょう。

VirtualBoxのゲストの時計の補正を停止する方法

ゲストの時計の補正を止める

VirtualBoxは、準仮想化インターフェースが”なし”のとき、ゲストOSとホストOSの時計は同期しています。でも、何かのときにゲストとホストの時計の同期を止めたいことがあると思います。

こちらを参考に紹介します。ゲストOSがWindowsの場合が載っています。

なお、TSCTiedToExecutionというのはTSCに関する別の設定ですので今回は設定しません。

VirtualBoxがホストの時計をゲストに供給するのを止める

時計の補正を止めたいゲストOSをシャットダウンします。

設定でシステム―>アクセラレーション―>準仮想化インターフェースで”なし”を選びます。

Legacyを選んでも”なし”と同じです。他を選ぶと自動的に補正されなくなります。

VirtualBoxのインストールされているフォルダに行きます。

cd "C:\Program Files\Oracle\VirtualBox"

次のコマンドでゲストOSのリストを見ることができます。

C:\Program Files\Oracle\VirtualBox> VBoxManage list vms

その名前を使って次のようにするとゲストがホストの時計を参照することを止めることができます。"Guest Name"というのは自分の設定したいゲストの名前です。

C:\Program Files\Oracle\VirtualBox> VBoxManage setextradata "Guest Name" "VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled" "1"

このコマンドで、ゲストOSのところにある設定ファイル"Guest Name".vboxに設定が書き込まれます。

この後で、ゲストOSを再起動するとゲストOSの時計ががホストに同期するのは停止されます。

GuestAdditionsの時計の同期を止める

上記だけでも時計が同期しなくなります。
ゲストOS上でも、Guest Additionsが同期しようとしていますので、念のためそれを止める方法です。

ゲストOSがUbuntu 12.04 x64の場合です。

現在動いているVBoxServiceを止めます。

sudo service vboxadd-service stop

タイマーの同期をとめたままGuest Additionsを動かします。

sudo /usr/sbin/VBoxService --disable-timesync

これでゲストとホストの時計の同期は止まるはずです。この方法だと、ゲストOSを再起動するとまた--disable-timesyncなしでサービスが動きます。

ゲストとホストの時計の同期を再開する

VBoxManageの最後の"1"をなくして実行することで設定のエントリーをなくすことができます。"0"とすることでも再開できます。

C:\Program Files\Oracle\VirtualBox> VBoxManage setextradata "Guest Name" "VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled"

ゲストの実行中でもコマンドを実行できますが、再起動しないと有効になりません。

準仮想化クロックの更新の頻度

以前、準仮想化クロック(kvm-clock)は起動してからの時間とTSCの値をメモリに書き込んでいることがわかりました。

また、その書き込むタイミングはホストOSからゲストOSに制御が移る前だということもわかりました。

ただ、書き込む頻度についてはよくわかりませんでした。

そこで、LinuxのKVMのソースコードを読んで詳しく調べてみました。

ソースコードはarch/x86/kvm/x86.cで、kvm_guest_time_update()でシステムタイムとTSCのメモリの値を更新しています。

この関数は、vcpu_enter_guest()で呼ばれます。ホストOSからゲストOSに制御を移す関数です。

ただ、KVM_REQ_CLOCK_UPDATEという名前のリクエストがあったときのみkvm_guest_time_update()は呼ばれますので、どういう条件か探してみます。

わかったところを列挙すると次のようになります。

  • システムが立ち上がったとき
  • TSCの周波数が変更されたとき
  • プロセスのCPU間での移動があったとき
  • マイグレーションのとき
  • MSR_KVM_SYSTEM_TIMEをセットしたとき
  • CPUのホットプラグでロードされたとき

現在のパソコンのシステムを考えると、最初の3つくらいがよく起こることだと思います。でも、思いのほか準仮想化クロックの値の更新がなされません。Core iシリーズ以降の最近のCPUでは、CPUの周波数が変わったときでもTSCの周波数は変わらないです。プロセスの移動もそう頻繁にはないです。システムが起動してから、あまり頻繁には準仮想化クロックは更新されていないということがわかります。

実際に、ホストOSの時刻を変更してもゲストOSの時刻が同期して変わることはありません。

このことから準仮想化クロックは頻繁には更新されず、主にTSCによって現在時刻を計算していることがわかりました。

VirtualBox 5.0の準仮想化クロックが遅れるときの対処法

準仮想化クロックの遅れの原因を追究してみます

前回、準仮想化クロックが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の周波数を決めるところにバグがあることがわかりました。

原因がつかめたようでよかったです。早くバグが修正されるといいなと思います。

VirtualBox 5.0の準仮想化クロックの精度

このところずっとVirtualBoxを使っていて、ゲストOSの時計が遅れていることに気がつきました。なんか変だということで、精度を簡単に測ってみることにしました。

クロックソースによる時計のおくれの違い

準仮想化クロックkvm-clockは、/sys/devices/system/clocksource/clocksource0/current_clocksourceでクロックソースとして指定しています。その他にも変えられるものがあるので、準仮想化ではなくなってしまいますがクロックソースを変えてみます。

$ sudo sh -c "echo tsc >/sys/devices/system/clocksource/clocksource0/current_clocksource"

で変えられます。

変えられるのは、cat /sys/devices/system/clocksource/clocksource0/available_clocksourceで確認できます。

tsc(Time Stamp Counter)というのは、CPUの刻む時計です。acpi_pm(ACPI Power Management Timer)というのは、ACPIというパソコンの規格で定められている精度の高いタイマーです。

また、VirtualBoxの準仮想化インターフェースでKVMのほかに、Legacyと”なし”にしたときも同様に測ってみます。

計測は手元のストップウォッチで5分を計測して、dateコマンドで秒単位の変化を見ました。

ホストWindows 7 x86_64 ultimate、ゲストUbuntu 12.04 x86_64です。VirtualBoxは5.0で、GuestAdditionsを入れてあります。

準仮想化インターフェースがKVMのとき

準仮想化インターフェースをKVMにしてかかった時間を計測します。

実時間 kvm-clock tsc acpi_pm
5分00秒 4分54秒 4分54秒 5分00秒

kvm-clockとtscのときは、5分経過したとき6秒も時計が遅れていました。acpi_pmにしたときは、ほぼ5分になります。

kvm-clockはtscをつかっているので、kvm-clockがずれているのはtscが原因だと思います。

準仮想化インターフェースがLegacyのとき

準仮想化インターフェースのLegacyがよくわからなかったので、計測してみました。

実時間 tsc acpi_pm
5分00秒 5分00秒 5分00秒

tsc、acpi_pmの両方とも正確に測れています。

準仮想化インターフェースがなしのとき

準仮想化インターフェースをなしにしたときも測定してみました。

実時間 tsc acpi_pm
5分00秒 5分00秒 5分00秒

tsc、acpi_pmともに時計は正確でした。

KVMでkvm-clockを使ったとき

時計の遅れがゲストOSのkvm-clockドライバーの問題なのか、VirtualBoxの準仮想化インターフェースの問題なのかはっきりさせるため、KVMを使ってみました。
LinuxのホストマシンがないのでVMware Playerを使ってKVMを仮想環境で動かしました。

ホストOSは、Windows 7 x64 Ultimate上でVMware Player 7.1.2のIntel VT-x/EPTまたはAMD-V/RVIを仮想化という機能を使ってUbuntu 14.04 x86_64を動かしました。
Ubuntu 14.04 x86_64上でQEMUの開発バージョンでKVMを使います。
ゲストOSは、Ubuntu 12.04 x86_64です。

実時間 kvm-clock
5分00秒 5分00秒

とくに大きな遅れは見られなかったです。ホストOSが複雑なので難しいのですが、kvm-clockが遅れの原因ではなさそうです。

準仮想化クロックを使っても時計が正確にならない

残念ですね。せっかく準仮想化クロックを導入しても時計が正確ではないです。VirtualBoxが1分で1秒以上遅れてしまうのは問題ですね。

マニュアルを読むとGuestAdditionsが時計を補正するとあります。どういう場合に補正されるのかが気になります。少なくとも準仮想化クロックをKVMにしたときは、補正されていないです。

準仮想化インターフェースのLegacyは、VBoxManageのヘルプを読むとVirtualBox 5.0以前に作られた仮想マシンを5.0以降で起動すると自動的に設定されるとあります。VirtualBoxのログを読むとLegacyが選ばれたときは準仮想化インターフェースとしてnone(なし)が選ばれています。そのため、Legacyとnoneは実質的な違いはないと思います。

何が原因で時計が遅れているのかを調べるため、次回ソースコードを追いかけて検討しようと思います。