BitVisorの外部割り込み処理について簡単に説明します.アーキテクチャはIntelが対象です.
基本
ゲスト実行中に割り込みでVMEXITが発生するかどうかは設定によります.VTではVM-Execution Control FieldsのPin-Based VM-Execution Controls内のexternal-interrupt exitingが1だと外部割り込み時にVMEXITします (exit reason は External Interrupt).そうでなければ,割り込みはそのままゲストに通知されることになり,RFLAGS.IF=1ならゲストの割り込みハンドラが処理します.BitmapでVMEXITする番号が指定できるMSRとは異なり,特定の割り込み番号だけVMEXITさせることはできません1.
外部割り込みでVMEXITしたときの割り込みの扱いは以下のようになります.
- VM-Exit controlsのAcknowledge interrupt on exitが1のとき
- HWが割り込みコントローラにackしてくれます.割り込み番号はVM-exit interruption-information fieldに格納されます.
- そうでないとき
- 割り込みはRFLAGS.IFでマスクされた状態です.この状態でホストが割り込みを有効化すると割り込みが入り,ホストの割り込みハンドラが起動します.
一般に外部割り込みはVMに向けたものとは限らないので,ホスト側の割り込みハンドラで処理するためにacknowledge interrupt on exitは0にすることが多いようです.
BitVisorの処理
まず,BitVisorは割り込み禁止状態で動作しています(なお,VMEXITするとRFLAGS.IFはクリアされます).またBitVisorではデフォルトでexternal-interrupt exitingが1, acknowlege interrupt on exitが0です.defconfigでno_intr_intercept = 1
にするとexternal-interrupt exitingが0になります.
External InterruptでVMEXITすると,do_external_int ()
を実行しますがここではvt_exint_assert (true)
を呼んで外部割り込みがあったとマークするだけです.実際の対応はメインループからvt_interrupt()
=> vt_inject_interrupt()
で実行します.ここで num = current->exint.ack ()
によって一時的に割り込みを有効化して割り込みを受け取り,割り込み番号を取得します.そしてvt_generate_external_int (num)
で割り込みをゲストに再挿入します.どうせ再挿入するならexternal-interrupt exitingする意味がないように見えますが,いくつかのケースでexternal-interrupt exitingが有用な場合があります.
一つは,定期的にVMEXITを起こしたい場合です.BitVisorは基本パススルーしているため,設定次第ではほとんどVMEXITしなくなります.パフォーマンス的にはそれで良いのですが,BitVisor内部でデバイスを占有して利用している場合や,lwipサーバを実行している場合は,それらの処理はBitVisorのスレッドで実行しているため,VMEXITしないとそれらの処理が一切進まなくなります.外部割り込みでVMEXITさせることにより,BitVisorのスレッドがスケジュールされやすくなります.
また,次で説明する割り込み番号変換にも利用されています.
割り込み対応処理の詳細
current->exint.ack ()
の処理の実体はcore/extint_pass.c:exint_pass_ack()
であり,以下のように処理されます.
-
core/int.c:do_externalint_enable()
=>core/int.c:callfunc_and_getint()
=>core/int_handler.s:int_callfunc()
- この関数内で,
gs:gs_inthandling=1
にして一瞬だけ割り込みを有効化 (sti; nop; cli) - すると,それまでマスクされてた割り込みが入る
- 割り込みハンドラ (
core/int_handler.s:int_handler
)でgs:gs_inthandling
の値をチェックして,その値が1であれば割り込み番号をgs:gs_inthandling
に入れてリターン- (そうでなければ(ようするにBitVisor実行中に発生した例外であれば)panic)
- 割り込みハンドラから戻ってきたら,
gs:gs_inthandling
に格納された割り込み番号をチェックして,対応したコールバック関数を実施し,その戻り値を返す.
exint_pass_intr_alloc()
やexint_pass_intr_register_callback()
で特定の割り込みに対してコールバック関数が登録できます.
virtio-netのデバイスドライバは,MSIで割り込みを受けてそれをMSI-Xで通知するということをしていますが,ここの割り込み変換にこのコールバック関数を利用しています.この処理については以前書きました.
また,(割り込み番号変換ではないようですが)NVMeやxHCIのドライバも何かコールバック関数を登録して処理しています.したがってこれらのデバイスドライバを利用する場合はdefconfigでno_intr_intercept=0
を設定する必要があります.
参考文献
- Intel SDM
- 33.2 INTERRUPT HANDLING IN VMX OPERATION
- 33.3 EXTERNAL INTERRUPT VIRTUALIZATION
-
VT-dのPosted Interruptsを利用すると,external-interrupt exitingが1でも特定の割り込みをVMX non-rootのゲストに直接挿入できるみたいです. ↩