かなり久々のブログ更新となりますが、本日は カーネル/VM Advent Calendar にてまたまた私の当番!ってことで、今回は FreeBSD 8.1-RELEASE に Linux KVM 用のタイムカウンタを実装してみたというネタでお話しようかと思います。
昨年のネタ: FreeBSD VIMAGEを使ったTCP/IPのルーティング デモンストレーション
■ Linux KVM のタイマ機能
まずバックグラウンドについて説明しておきましょう。 FreeBSD のはじめとする殆どの OS では、以下の計算式でシステム時間(年月日・時分秒)を把握しています。
システム時間 = システム起動時の時間 + システム起動後の経過時間
このうち「システム起動時の時間」はRTC(リアルタイムクロック)の値で初期化し、「システム起動後の経過時間」はOSが定期的にカウンタをインクリメントしています。しかし、仮想化環境ではこの仕組みをそのまま適用すると時刻ズレの原因となりやすいことは、この記事を読むような方であればご存じのことでしょう。
そこで、タイマの仮想化です。 Linux KVM でもタイマの準仮想化機能があります。
KVM PVclock
http://www.linux-kvm.org/page/KVMClock
これは、ゲスト OS が「おいら時間情報がほしいから、メモリのここに定期的に書いておいてちょ」と仮想マシンモニタにお願いすると、適当な間隔で仮想マシンモニタが指定されたメモリ上に時刻情報を書いておいてくれる、という仕組みです。
ただ、 KVM のサイトを見るとこの機能は Linux guests only. と書いてあります。
どうして
Linux でしか使えないの?
それは、ゲスト OS が KVM PV Clock に対応する必要があるからです。
しかし KVM の PV Clock は決して難しいものではありません。もともと Xen にインスパイアされて殆ど同じ汎用的なデータ構造を持っているので、すでに BSDL で Xen 対応のコードがある FreeBSD であれば私のヘナチョコがちょろちょろっとコピペして済むレベルです。対応するかしないかは仮想マシンモニタ開発側&ゲスト OS 開発側のモチベーション次第なだけなのです!よっしゃ、私は最近 FreeBSD 使っていないけど、やってみるかー。
ってことでとりあえず動くモノを作ってみましたとさ。
■ FreeBSD 用 timecounter: kvmclock 失敗編
時は 2011 年 10 月。今回のモノは FreeBSD の timecounter として実装しました。 Linux で言う clocksource ですね。システム起動時には以下のイメージで初期化されます。
Timecounter "kvmclock" frequency 1800000536 Hz quality 900
このタイムカウンタは、 Linux KVM が定期的に TSC のカウンタをメモリに書いてくれるので、それを使って時を刻んでいます。
struct pvclock_vcpu_time_info {
u32 version;
u32 pad0;
u64 tsc_timestamp;
u64 system_time;
u32 tsc_to_system_mul;
s8 tsc_shift;
u8 pad[3];
} __attribute__((__packed__)); /* 32 bytes */
とりあえず動いたのですが、しかし実際には問題が起きて使い物になりませんでした。理由は桁あふれです。1.8GHzでカウントアップされていくタイマだと、あっという間に32ビットのカウンタは一週してゼロに戻ります。この結果、アイドル時間が多いと時計が遅れるという最高にイケていないタイムカウンタになりました。なお while true; do date > /dev/null ; done とかしていると非常に精度が高いカウンタであったことは申し添えておきます!
■ FreeBSD用timecounter: kvmclock その2
時は2011年12月29日。年末年始はスノーボードに行くので今作らなかったら Advent Calendar のネタがありません。ってことで、とりあえずバージョンアップしました。
Timecounter "kvmclock" frequency 1000000 Hz quality 900
このタイマはゲストOSに対して仮想的にマイクロ秒精度のタイマ機能を提供します。
前回は Linux KVM がシャドウしてくれた TSC の値を使って時を刻んでいましたが、今回は Linux KVM がシャドウしてくれたシステム時間をベースに時を刻みます。
struct pvclock_wall_clock {
u32 version;
u32 sec;
u32 nsec;
} __attribute__((__packed__));
このデータ自体はナノ秒オーダーのものですが、 t = sec * 1000 + nsec / 1000 としてマイクロ秒のオーダーに変換し、これの情報を OS 側に食わせます。この形であればカーネルが気づかないうちに桁あふれなんてことがそうそう起きません。
■ 評価
早速作ったタイムカウンタで仮想マシンを動かしてみました。素人ハックの結果としては割と良好かと思います。
これは while true; do date; sleep 1; done の結果をゲスト(上)とホスト(下)で実行しっぱなしにした結果です。
5時8分0秒が飛んでいるので不安定っぽく見えますが、 sleep 1 の待ち時間がフラついているという問題はあるもののホスト OS から 1 秒以上時間がずれることは無くなりました。 sleep の精度が低い問題はありつつも gettimeofday は安定しているようです。
また、vCPUがアイドルの場合も、負荷をかけた場合でも、システムの時刻がホストとゲストの間で大きくズレることはないようです。
12 月 29 日から 1 月 4 日までの 5 日間放置しても、システム時間のずれは生じなかった
■ もっと頑張らないといけないこと
とりあえず自覚している課題は以下の部分です。
- 実はまだ KVM のプローブをしていない。 MSR を一発たたけばいいので、いつでもできます。
- vCPU != 1 の場合を想定していないし、評価していない。vCPU ごとにカウンタを進める必要がありますので、今のコードのままではまずい。このままでも動くかもしれないけど。
- sleep 1 が不安定 ゚+.(・ω・)゚+.゚ ヘボグラマなので細かい修正はエキスパートにお願いすればいいかなっと。
現時点のやるきのないコードの一部(試行錯誤の跡付き)
■ おわりに
というわけで、「タイマ作りました、動いてます」というとっても退屈な記事ですが、、こんな事してみましたよ、ってことで。私のようなヘボでもいじくれる所があるというのは有り難いものです。
Advent Calendar 参加者の皆さんの記事、楽しく読ませていただきました。今後ともよろしくお願いします。
0 件のコメント:
コメントを投稿