[[353533]] この記事はWeChatの公開アカウント「LoyenWang」から転載したもので、著者はLoyenWangです。この記事を転載する場合は、LoyenWang公式アカウントまでご連絡ください。 背景 - ソースコードを読んでみろよ! --魯迅著
- 一枚の写真は千の言葉に値する。 --ゴーリキー著
例: - KVM バージョン: 5.9.1
- QEMU バージョン: 5.0.0
- ツール: Source Insight 3.5、Visio
- この記事はブログガーデンで同期されています: https://www.cnblogs.com/LoyenWang/
1. 概要 この記事では、ARM GICv2 割り込み仮想化の全体的なフレームワークとプロセスについて説明します。数日間私を悩ませていたこの問題が解決したとき、私は少し悟りを開いたような幸せな気持ちになりました。 割り込み仮想化について説明する前に、割り込みの役割と処理フローの概要を理解しておく必要があります。 - 割り込みは、プロセッサが周辺機器からの要求を非同期的に処理するために使用するメカニズムです。
- 周辺機器はハードウェア ピンを介して割り込みコントローラに接続され、電気信号を介して割り込みコントローラに要求を送信します。
- 割り込みコントローラは、周辺機器からの割り込み要求を CPU にルーティングします。
- CPU(ARM を例に挙げます)は、モードを切り替え(IRQ/FIQ に切り替え)、コンテキストを保存し、周辺装置の割り込み番号に応じてシステム内の登録されたハンドラを検索して処理します。処理が完了するとコンテキストが復元され、以前に中断された実行フローが継続して進みます。
- 割り込みの役割は周辺機器の処理に限定されません。システム スケジューリング、SMP コアの相互作用などはすべて割り込みと切り離せません。
割り込み仮想化は、割り込み信号の生成から vCPU へのルーティングまで実行され、次の 3 つのケースが含まれます。 - 物理デバイスは割り込み信号を生成し、それが vCPU にルーティングされます。
- 仮想ペリフェラルは割り込み信号を生成し、それが vCPU にルーティングされます。
- ゲスト OS 内の CPU 間で割り込み信号 (IPI 割り込み) が生成されます。
この記事では ARM-GICv2 に焦点を当てているため、MSI や ITS などの機能については説明しません。質問から始めましょう。 2. VGIC - 割り込み仮想化について説明する前に、ARMv8 のハイパーバイザーのアーキテクチャについて説明する必要があります。これは、異なる例外レベル間の切り替えが関係するためです。
- ソースコードを読みながら論文の理論とコードを照らし合わせていくうちに、いくつかの誤解が生じ、数日間悩みました。
- Linux ARM アーキテクチャ ハイパーバイザが導入されたとき、スケジューラなどの既存の Linux メカニズムを最大限に活用するために、左図に示すシステム アーキテクチャが採用されました。
- KVM/ARM の実装では、Highvisor と Lowvisor に分割された分割モードを採用しており、ARM プロセッサのさまざまなモードの利点を最大限に活用できます。たとえば、Highvisor は Linux カーネルの既存のメカニズムを使用できますが、Lowvisor は Hyp Mode の権限を使用できます。さらに、Linux カーネル コードに大幅な変更を加える必要がないことも利点の 1 つであり、これは最初に導入されたときにコミュニティに受け入れられやすかった点です。
- Lowvisor には 3 つの主要な機能があります: 1) 異なる実行コンテキストを分離して保護します。たとえば、VM は互いに影響を及ぼしません。 2) ゲストとホストの切り替え(ワールドスイッチとも呼ばれます)を提供します。 3) ハイパーバイザーにトラップされた割り込みと例外を処理するための仮想化トラップ ハンドラーを提供します。
VHE - VHE: 仮想化ホスト拡張機能。EL2 上で実行されているホスト OS をサポートするために使用されます。ハイパーバイザーとホスト OS は両方とも EL2 上で実行されるため、コンテキスト切り替えによって発生するオーバーヘッドを削減できます。
- 現在、Cortex-A55、Cortex-A75、および Cortex-A76 は VHE をサポートしており、VHE 制御は HCR_EL2 レジスタの操作によって実現されます。
もうひとつの知識: - ホストが EL1 で実行されている場合、HVC (ハイパーバイザ コール) 命令を介して EL2 にアクティブにトラップし、ハイパーバイザが引き継ぐことができます。
- ゲスト OS は、割り込みまたは例外が発生したときに EL2 にトラップするように構成できるため、ハイパーバイザーが引き継ぐことができます。
- EL2 は eret 命令を通じて EL1 に戻ることができます。
この記事の議論は非VHEシステムに基づいています。 2.1 GIC 仮想化サポート GICv2 ハードウェアは仮想化をサポートします。ここに古い写真があります: まず、物理的な GIC の機能モジュールを見てみましょう。 - GIC は、ディストリビューター インターフェイスと CPU インターフェイスの 2 つの部分に分かれています。ディストリビューター インターフェイスと CPU インターフェイスの両方に MMIO を介してアクセスします。
- ディストリビュータは、割り込みの有効化と無効化、SMP での IPI 割り込み、CPU アフィニティ、優先処理など、GIC を構成するために使用されます。
- CPU インターフェイスは、CPU に接続し、割り込み ACK (確認応答) および EOI (割り込み終了) 信号を処理するために使用されます。たとえば、CPU が割り込み信号を受信すると、CPU インターフェイスを介して ACK で応答し、割り込みを処理した後、EOI レジスタに書き込みます。 EOI を書き込む前は、CPU は割り込みを受信しなくなります。
簡略化した図は次のとおりです。 GICv2 はハードウェア仮想化サポート、つまり仮想 GIC (VGIC) を提供するため、割り込み受信をハイパーバイザーを介してソフトウェアでシミュレートする必要がありません。 - VGIC は、各 vCPU に対して、VGIC CPU インターフェイスと対応するハイパーバイザ制御インターフェイスを導入します。
- ハイパーバイザー制御インターフェイスの LR (リスト レジスタ) レジスタに書き込むことによって仮想割り込みを生成することができ、VGIC CPU インターフェイスは仮想割り込み信号をゲストに送信します。
- VGIC CPU インターフェイスは ACK と EOI をサポートしているため、これらの操作をソフトウェアによるシミュレーションのためにハイパーバイザーにトラップする必要がなく、CPU 受信割り込みのオーバーヘッドも削減されます。
- ソフトウェア シミュレーションのために、ディストリビューターをハイパーバイザーにトラップする必要があります。たとえば、vCPU が仮想 IPI を別の vCPU に送信する必要がある場合、その機能の完了を支援するためにディストリビューターが必要になります。このプロセスはハイパーバイザーにトラップする必要があります。
2.2 仮想割り込み生成プロセス この記事の冒頭で述べた 3 つの割り込み信号ソースを次の図に示します。 - ①: 物理周辺機器が仮想割り込みプロセスを生成する:
- 周辺機器の割り込み信号 (ハイパーバイザーが仮想割り込みとして構成したもの) が GIC に到達します。
- GIC ディストリビュータは物理 IRQ を CPU に送信します。
- CPU は Hyp モードにトラップされ、その時点でゲスト OS は終了してホスト OS に戻ります。
- ホスト OS は物理割り込みに応答します。つまり、ホスト OS ドライバーは周辺機器の割り込み信号に応答します。
- ハイパーバイザーは仮想割り込みをリスト レジスタに書き込み、仮想 CPU インターフェイスは仮想 irq 信号を vCPU に送信します。
- CPU は例外を処理し、ゲスト OS は仮想 CPU インターフェイスから割り込みステータスを読み取って応答します。
- ②: 仮想ペリフェラルが仮想割り込みプロセスを生成する:
Qemu は周辺機器をシミュレートし、ハイパーバイザーをトリガーして irqfd を通じて割り込みを挿入します。 ハイパーバイザーは仮想割り込みをリスト レジスタに書き込み、仮想 CPU インターフェイスは仮想 irq 信号を vCPU に送信します。 CPU は例外を処理し、ゲスト OS は仮想 CPU インターフェイスから割り込みステータスを読み取って応答します。 ゲスト OS は仮想ディストリビューターにアクセスし、例外をトリガーしてハイパーバイザーにトラップします。 ハイパーバイザーは IO 例外に応答し、最終的に仮想割り込みをリスト レジスタに書き込みます。仮想 CPU インターフェイスは、仮想 irq 信号を vCPU に送信します。 CPU は例外を処理し、ゲスト OS は仮想 CPU インターフェイスから割り込みステータスを読み取って応答します。 上記のプロセスは、実際には仮想ペリフェラルフレームワーク(VFIO など)などの仮想ペリフェラルとやり取りする必要がありますが、この記事では割り込みの観点からのみ分析し、ペリフェラル部分は省略しています。 理論の説明が終わったので、ソースコードから理論を検証してみましょう。 3. ソフトウェア実装プロセス 3.1 VGICの初期化 - kvm_init はメインのエントリ ポイントであり、vgic_v2_probe 関数に入り、GICv2 の初期化を完了します。また、GICV2 カーネル内のドライバーと対話して、主にベース アドレス情報を含む gic_kvm_info 情報を取得し、後続の操作での構成操作を容易にします。
- 青い部分の関数呼び出しからわかるように、初期化後、ユーザー層の ioctl 操作に応答するために kvm_device_ops 操作関数セットが登録されます。
- ユーザー層は ioctl(vm_fd, KVM_CREATE_DEVICE, 0) を呼び出し、最終的に vgic_create 関数を呼び出して VGIC デバイスの作成を完了します。 kvm_device_fops 操作関数セットも作成関数に登録され、デバイス属性を設定および取得します。
- ユーザー層は、ioctl(dev_fd, KVM_SET_DEVICE_ATTR, 0)/ioctl(dev_fd, KVM_GET_DEVICE_ATTR, 0) を介して属性を設定および取得し、最後に vgic_v2_set_attr/vgic_v2_get_attr を呼び出して VGIC の設定を完了します。
3.2 物理周辺機器が割り込みを生成する いつものように、CPU 仮想化に関する前回の記事を読んでいると仮定すると、次の図が表示されます。 - 前提条件から始めましょう: HCR_EL2.IMO が 1 に設定され、すべての IRQ が Hyp モードでトラップされます。したがって、ゲスト OS が vCPU 上で実行されている場合、物理周辺機器が割り込み信号をトリガーすると、EL2 に切り替わり、el1_irq が実行されます。
- ホストでは、ユーザー状態が KVM_RUN を介して vCPU の実行を制御すると、kvm_call_hyp_ret が例外レベルの切り替えをトリガーし、Hyp モードに切り替えて __kvm_vcpu_run_nvhe を呼び出します。ここで __guest_enter がゲスト OS のコンテキストに切り替え、最後に eret を介して EL1 に戻り、ゲスト OS が正式に実行を開始します。
- 割り込みがトリガーされると、el1_irq は __guest_exit を実行します。このプロセスはコンテキスト スイッチを実行します。つまり、ホストがゲストに入るポイントにジャンプし、ホストの実行を再開します。ここで混乱する点があることに注意してください。 el1_irq と __guest_exit は両方とも EL2 で実行されますが、Host は EL1 で実行されます。例外レベルを切り替える方法が見つかりませんでした。最後に、kvm_call_hyp_ret が呼び出されると、例外ベクター テーブルで対応する実行関数が見つかり、do_el2_call が実際に呼び出されることがわかりました。この関数では、例外レベルが切り替えられ、最終的に EL1 に戻ります。
- ホストに戻ると、local_irq_enable が割り込みをオンにした後、物理的に保留中の割り込みに対してホストが適切に応答できるようになります。
では、仮想割り込みはいつ挿入されるのでしょうか?そうです、図の kvm_vgic_flush_hwstate は仮想割り込みを挿入し、__guest_enter がゲスト OS に戻ったときに応答します。 - vgic_cpu 構造体の ap_list_head リストは、アクティブ状態と保留状態の割り込みを格納するために使用されるため、ap_list_head という名前が付けられています。
- kvm_vgic_flush_hwstate 関数は、ap_list_head 内の割り込み情報を走査し、それを vgic_lr 配列に入力します。最後に、vgic_restore_state 関数は配列の内容を GIC ハードウェアに更新し、割り込み挿入を完了します。 __guest_enter が実行されると、ゲスト OS に切り替わり、仮想割り込みに応答できるようになります。
- ゲスト OS を終了した後、kvm_vgic_sync_hwstate を呼び出す必要があります。これは、kvm_vgic_flush_hwstate の逆操作に相当し、ハードウェア情報を保存し、短期的には処理されない割り込みを排除します。
3.3 仮想ペリフェラルは割り込みを生成する - irqfd は仮想割り込みを注入するメカニズムを提供し、この割り込みソースは仮想周辺機器から来ることができます。
- irqfd は eventfd メカニズムに基づいて実装されており、ユーザー状態とカーネル状態の間、およびカーネル状態間のイベント通知に使用されます。
- イベント ソースは、VFIO フレームワークなどの仮想デバイスにすることができます。私はまだこのモジュールを深く研究していないので、何も言うことができません。次回以降のシリーズで追ってみます。
ソフトウェアのプロセスは次のとおりです。 - 初期化操作には 2 つの部分が含まれます。1) ルーティング エントリの設定 ([a] vgic_init は初期化時にデフォルトのエントリを作成します。[b]: ユーザー レイヤーは KVM_SET_GSI_ROUTING を通じてこれを設定します)。 2) irqfd の設定
- 初期化が完了すると、システムはいつでもイベント トリガーに応答できるようになります。イベント ソースがトリガーされると、irqfd_inject 関数にディスパッチされます。
- irqfd_inject 関数は、仮想割り込みの挿入操作を完了します。この関数では、set 関数がコールバックされ、ルーティング エントリが初期化されるときに set 関数が設定されます。
- 実際の注入操作は vgic_irqfd_set_irq 関数で完了します。
- kvm_vcpu_kick 関数は、ゲスト OS をホスト OS に戻します。割り込みが挿入された後、ゲスト OS に戻って応答します。
3.4 vCPU IPI - ホストは VGIC ディストリビューターをシミュレートします。ゲストが VGIC ディストリビューターにアクセスしようとすると、異常な操作がトリガーされ、ゲストは Hyp モードに陥ります。
- ハイパーバイザーは例外を処理し、書き込み操作を完了し、最後に応答のためにゲスト OS に戻ります。
- 簡単に言えば、ハイパーバイザーは割り込みの管理を担当します。はい、それほど強力です。
ソフトウェアのプロセスは次のとおりです。 - 上位層が ioctl(vcpu_fd, KVM_RUN, 0) を呼び出すと、最終的に vgic_register_dist_iodev 関数が呼び出され、VGIC ディストリビューターが IO デバイスとして登録され、ゲスト OS がアクセスできるようになります。
- vgic_register_dist_iodev は 2 つの機能モジュールに分かれています: 1) struct vgic_registers_region 構造体フィールドと操作関数セットを初期化します。 2) MMIO バスデバイスとして登録します。
- 構造体 vgic_registers_region は、さまざまなレジスタ領域と対応する読み取りおよび書き込み関数を定義します。 vgic_v2_dist_registers 配列は、最終的にはクエリと呼び出しのために dispach_mmio_read/dispach_mmio_write 関数に提供されます。
- ゲスト OS がディストリビューターにアクセスすると、IO 例外がトリガーされ、ホストがプロセスに戻ります。 io_mem_abort は、バス タイプ (MMIO) に応じて対応する読み取りおよび書き込み関数、つまり図の対応する dispatch_mmio_read/dispach_mmio_write 関数を見つけて操作を実行し、最終的にレジスタ領域の読み取りと書き込みを完了します。
- 図中の赤い線は例外処理の実行フローを表しており、一目でわかります。
時間がかかり、手間がかかり、苦労したこの記事がようやく完成しました。 ARMv8 GICv2 割り込み仮想化の全体的なフレームワークとプロセスを整える必要があります。インターネット全体では、関連するトピックに関する記事はあまり多くありません。少しでもお役に立てれば幸いです。 もし役に立ったら、ぜひ読んで、シェアして、報酬を与えてください。 参照する 《arm_gic_architecture_specification》《ARM_Interrupt_Virtualization》《VM-Support-ARM》《CoreLink GIC-400 Generic Interrupt Controller》《ARM アーキテクチャにおける仮想化》https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=721eecbf4fe995ca94a9edec0c9843b1cc0eaaf3 |