Win2kとRTC

RTCのタイマーは、PICに割り込み終了の合図を送ることで動くようになりました。
ところが、Win2kは起動時にこそRTCを見に行くのですが、その後ぜんぜんアクセスしていないことが判明。x86_64 Linuxホストで、Win2kの時計が不安定な理由がRTCではないかと思ったのですが、徒労に終わった。せっかく作ったんですけどね。
その代わり、Win2kゲスト上でインターネットエクスプローラを立ち上げると、PITにアクセスしていることが判明。何をしているのかわからないけど。
それと、timeoutを0にすると、時計が2倍くらい速くなることが判明。割り込みも速くなっています。バグがうまく再現できているみたい。x86_64 Linuxホストでの時計の不安定さは何とかできそうな気がする。気のせいかも。

CPUのループ

QEMUは、cpu_execの中の大小2つのループでCPUの動作をエミュレートしています。大きいほうは、割り込みが起こったときにCPUの状態を変化させることをしています。小さい方はゲストのコードを解釈してダイナミックトランスレートして実行します。
少し気になったので、1秒あたりのループの回数を調べてみました。
大きいほうが、1秒間に2000回くらい、小さいほうが120000回くらいでした。ゲストのコードを実行しながら時々割り込みの対処をするといった感じでしょうか。

Linuxのタイマーパッチ

Linuxホストのタイマーのパッチを作りました。いろいろ試行錯誤したのですが、結局いつも/dev/rtcを使うようにトライしてみるようにするだけでした。
http://www.h7.dion.ne.jp/~qemu-win/download/qemu-20060407-linux-timer.patch
時計を正確にするには、2つのことをしてください。
(1) まず、ホストOSで、rootでmax-user-freqを1024に設定します。
[Linux host]#echo 1024 >/proc/sys/dev/rtc/max-user-freq
ホストOSを起動するごとに設定する必要があります。/etc/rc.d/rc.localに書いておくといいと思います。
(2) パッチをあてて、バイナリを作ります。patch -p0オプションで当てられます。
必須なのは、max-user-freqを設定することです。最近のカーネルならば、パッチは必要ないと思います。kernel-2.6.11-1.1369_FC4ホストではパッチが必要でしたが、kernel-2.6.15-1.1831_FC4ホストではその必要がありませんでした。
max-user-freqを設定しないと、Windows 2000でIEを立ち上げたとき、時計が速くなったり遅くなったりします。
タイマーは、RTC(Real Time Clock)が使えると1msのタイマーが使えます。Linux 2.6ゲストの時計が正確になります。そうでないと、setitimer/getitimerで設定していますが、これがLinuxゲストで時計が1/2に遅くなった原因でした。最近のカーネルでは、時計は1/4に遅くなると思います。
テストした環境は、次のとおりです。
Windows 2000 guest /FC4 kernel-2.6.11-1.1369_FC4 host
FC4-i386-rescuecd.iso guest/FC4 kernel-2.6.11-1.1369_FC4 host
Windows 2000 guest/FC4 kernel-2.6.15-1.1831_FC4 host
FC4-i386-rescuecd.iso guest/FC4 kernel-2.6.15-1.1831_FC4 host
Linux 2.4のホストではテストしていません。
x86_64のホストでは、Windows 2000ゲストの時計がとても不安定です。1ms間隔の割り込みは起こっているのですが、時計がうまく動きません。遅れることはないみたいだけど。

Linuxのタイマー

Linuxホスト上のタイマーを調べています。必要なのは、max-user-freqを1024にすることだということはわかりました。rootで実行します。
[Linux host] # echo 1024 >/proc/sys/dev/rtc/max-user-freq
これだけでも、時計が安定する場合もあります。もう少し調べてみます。

QueryPerformanceCounterとQueryPerformanceFrequency

自分のシステムでは、QueryPerformanceFrequency = 3579545 になる。
QEMUの算出するticks_per_secは、1428395429ぐらいになって、400倍くらい違った。
どうしてか調べてみたら、自分のシステムではQueryPerformanceCounterは、ACPIのPM Timer(Power Management Timer)を使っているみたいです。PMタイマーを使っていると、3579545になるんだそうです。ただ、システムによって異なり、たとえば、Win2kだとTSCの値をそのまま返すシステムもあるそうです。
QEMUは、RDTSCというアセンブラ命令で得られるTSC(Time Stamp Counter)を使っていました。TSCとPMタイマーが400倍くらい違うということらしい。
RDTSCを使うのは、いろいろ問題があるみたいで、Microsoftはゲームの時間測定ではQueryPerformanceCounter/QueryPerformanceFrequencyを使うことを推奨しています。
BIOSやマザーボードのバグで、マルチコアなどのシステムではQueryPerformanceCounterがスレッドで違った値を返すことがあるそうなので、SetThreadAffinityMaskでCPUを指定しなさいとのこと。
AMDのシステムで、32-bit WinXP SP2, 64-bit WinXP, Win2003 SP1のとき、boot.iniに/usepmtimerというオプションをつけることで、マルチコアのTSC(Time Stamp Counter)に依存しないようになるそうです。AMDの人が言っている。
Linuxのブートオプションでは、clock=pit, pmtmr, cyclone, hpet, tscといろいろ時間の測定を変更するオプションがあるんですね。pmtmrというのが、ACPI PM Timerを使うということらしい。
つっこむといろいろ出てきて、時間に関する問題は奥が深いなと思った。

タイマー/時計のパッチとバイナリ

タイマー/時計のパッチとバイナリを作りました。
http://www.h7.dion.ne.jp/~qemu-win/download/qemu-20060330-timer.patch
http://www.h6.dion.ne.jp/~kazuw/qemu-win/qemu-20060330-timer.zip
1ms間隔の割り込みをサポートします。
Kqemu/Qvm86を使ったときでも、時間が正確です。
時計の遅れは、main_loop_waitがSleepしているとき、割り込みの時間切れになっても、タイマー割り込みが起こらないことが原因でした。イベントオブジェクトを使って解決しています。
QEMUの時間測定は、RDTSCを使っていたのですが、見ているとどうも時間が安定していないです。ticks_per_secの時間測定は、_ftimeを使っていたんですけど、精度がよくないので、QueryPerformanceCounter/QueryPerformanceFrequencyを使う方法に変えてみました。すると、びっくりするくらい時計が安定しました。
もしかすると、SMP(HTもしくはマルチコア)のシステムでも、使えるかもしれません。未確認ですけど。
これで、ベンチマークソフトが使えます。今まで、時計が不安定で使えなかったので。
これでうれしい人が多いかもね。とりあえず、できてよかった。
追記:
Windowsホストだけです。Win2k/XPゲストのタスクバーの時計をダブルクリックしてはいけません。画面がフリーズして終了できなくなってしまいます。

PITと割り込み

QEMUには、main_loop_waitと、cpu_execという2つの大きなループがある。cpu_execは、main_loop_waitから呼ばれる。
cpu_execは、CPUの動作をエミュレートしている。その中では、ゲストのコードをトランスレートしてホストのコードに変換して実行している。TranslatonBlockというのが、その実行の単位。
main_loop_waitは、周辺機器も含めたPC全体の動作をエミュレートする。CPUも機器の1つだからcpu_execが含まれる。
PITが動作するには、その両方が動作することが必要。
CPUは、env->interrupt_requestにCPU_INTERRUPT_HARDが入っていることでCPUとしての割り込みを実行する。つまり、IDTに登録された割り込みハンドラを実行する。
QEMUを、-d in_asm つきで実行したときにできる、c:tmpqemu.logというログファイルの
Servicing hardware INT=0x20
というのがそれにあたる。
トランスレートされたコードは、再利用されるので、ログファイルには、アセンブラは2度目からは記録されていない。でも、実行されている。
では、周辺機器の割り込みのエミュレートと、CPUの動作がどうつながるのか。
もう1つの疑問は、host_alarm_handlerがホストのSIGALRM、Windowsの場合はマルチメディアタイマから呼ばれて実行されるけど、これは割り込みとどう関係しているのか。
host_alarm_handlerでは、CPU_INTERRUPT_EXITが設定されて、cpu_execのループを抜けることになる。
PITのエミュレートでは、初期設定を除くと、pit_irq_timer_updateは、main_loop_waitのqemu_run_timersからしか呼ばれない。
gdbで動かしつながら、というのはこの辺までだったので、host_alarm_handlerやmain_loop_waitで情報を書き出しながらみる。
timeout=10で10ms Sleepしているときでも、host_alarm_handlerは、1msごとに呼ばれています。PITを1msに設定したとき、qemu_timer_expiredに引っかかって割り込みが入ってもいいのに入らないことに気づく。
それで、SleepをWaitForMultipleObjectsにして、イベントでmain_loop_waitが起きるようにしてみると、割り込みがうまく動くようになるのを確認できました。
やれやれ。

QEMUのPIT

とりあえず調べてみる。
PITのモードは、2。gdbで、pit_ioport_writeにブレークポイントを仕掛けると、6回止まる。最初の3回は、BIOSが初期設定をしている。後の3回はhariboteがio_out8したもの。
PITの設定が終わると、pit_irq_timerが定期的に実行されるようになります。
関係ないけど、MS-DOSは、55msのタイマ割り込みを設定しているそうですね。
pit_get_next_transition_timeという関数は、irqが次に変化する時間を計算している。
PITの設定は、その時間をs->next_transition_timeに設定し、qemu_mod_timerで、expire_timeを設定している。
時間がたつと関数が実行されるメカニズムは、時間のタイムアウトを設定しておくと、vl.cのmain_loop_waitのなかで、qemu_run_timersを呼んで、時間切れがあったら、ts->cb(ts->opaque)というコールバック関数を呼んでいること。このts->cbにpit_irq_timerが登録されていて、実行される。
もう1つ、CPUの実行中に割り込みが起こるというのは、cpu-exec.cのcpu-exec関数のループの中で、env->interrupt_requestを調べる。CPU_INTERRUPT_HARDが設定されていると、cpu_get_pic_interruptでどの割り込み番号が設定されているかを調べる。do_interrupt(intno, 0, 0, 0, 1)で次に実行するenv->eipを設定したりして、ゲストOSがIDTに登録した関数が実行されるようにする。
QEMU内部での、プログラムとしての時間切れの処理と、ゲストOS内でやってほしいことの2つあるということか。
main_loop_waitで、pit_irq_timerを呼んでいるので、ゲストOSがHLTで止まると、Sleepしてしまい、PITも止まってしまうことになるのかな。
もう1つ、host_alarm_handlerがホストの時間に従って、割り込みを入れているようなんだけど、これって、どういう役割になるんだろうか。
あまりよくわかってなくて書いているので、間違っているかも。

仮想化やクラウドについて