[[397740]]この記事はWeChatの公開アカウント「LoyenWang」から転載したもので、著者はLoyenWangです。この記事を転載する場合は、LoyenWang公式アカウントまでご連絡ください。 背景- ソースコードを読んでみろよ! --魯迅著
- 一枚の写真は千の言葉に値する。 --ゴーリキー著
例: - KVM バージョン: 5.9.1
- QEMU バージョン: 5.0.0
- ツール: Source Insight 3.5、Visio
- この記事はブログガーデンで同期されています: https://www.cnblogs.com/LoyenWang/
1. 概要まず、問題の概要を見てみましょう。以前の virtio シリーズの記事では、ネットワーク仮想化のフレームワークを次の図に示しました。 - Qemu での virtio-net デバイス データ パケットの送受信は、ユーザー モードで tap デバイスにアクセスすることによって完了します。
- 送受信プロセスには、ゲスト OS、KVM、Qemu の virtio-net デバイス、ホストのネットワーク プロトコル スタックなどの相互作用が関係します。パスが長く、多くのスイッチが関係するため、パフォーマンスが低下します。
- vhost-net の導入により、vitio-net バックエンド デバイスのデータ処理モジュールがカーネルに組み込まれ、全体的な効率が向上します。
vhost-net のフレームワーク図は次のとおりです。 - 図からわかるように、ゲストのネットワーク データのやり取りは vhost-net カーネル モジュールによって直接処理され、Qemu プロセスでの処理のためにカーネル状態からユーザー状態に切り替える必要はありません。
- 前回の記事では、virtio デバイスとドライバーを分析しました。データ転送は virtio プロトコルに従うため、vhost-net に virtqueue 関連のメカニズムを実装する必要があります。
この記事では、vhost-net の原理を分析し、重要な点のみに焦点を当てて要点を述べます。 2. データ構造vhost-net カーネル モジュールの階層構造は次のとおりです。 - struct vhost_net: Vhost-Net デバイスを記述するために使用されます。いくつかの重要なフィールドが含まれています: 1) struct vhost_dev、一般的な vhost デバイス。これは、struct device 構造と同様に、他の特定のデバイスの構造に埋め込むことができます。 2) struct vhost_net_virtqueue は、実際には struct vhost_virtqueue をカプセル化し、ネットワーク パケットのデータ転送に使用されます。 3) struct vhost_poll は、データ パケットの受信時および送信時にタスクをスケジュールするためのソケット ポーリングに使用されます。
- struct vhost_dev: struct vhost_net、struct vhost_scsi などの vhost メカニズムに基づく他のデバイス構造に埋め込むことができる一般的な vhost デバイスを記述します。主要なフィールドは次のとおりです。1) データ転送に対応する、割り当てられた struct vhost_virtqueue を指す vqs ポインター。 2) work_list、vhost_worker カーネル スレッドで実行する必要があるタスクを配置するために使用されるタスク リスト。 3) ワーカー。タスク リスト内のタスクを実行するために作成されたカーネル スレッドを指すために使用されます。
- struct vhost_virtqueue: デバイスに対応する virtqueue を記述するために使用されます。この部分については、virtqueue メカニズムの以前の分析を参照してください。本質的には、Qemu の virtqueue 処理メカニズムはカーネルに組み込まれています。キー フィールドは次のとおりです: 1) struct vhost_poll、eventfd に対応するファイルをポーリングするために使用されます。処理要求が満たされない場合は、eventfd に対応する待機キューに追加されます。起動されると、構造体内の struct vhost_work (実行関数は、送信を例にとると handle_tx_kick に初期化されます) がカーネル スレッドに配置されて実行されます。
構造の中核はデータと通知のメカニズムを中心に展開され、データは vhost_virtqueue に反映され、通知は主に vhost_poll を通じて実装されます。具体的な内容については後述します。 3. プロセス分析3.1 初期化 vhost-net は、misc デバイスとして登録されたカーネル モジュールです。 Qemu はシステム コール インターフェイスを通じてカーネルと対話します。 Qemu での初期化は次のとおりです。 - Qemu でのタップ デバイスの初期化は net_init_tap で完了します。ここで、net_init_tap_one はカーネルの vhost-net と対話するために vhost-net デバイス ファイルを開きます。
- vhost_set_backend_type: vhost のバックエンド タイプと vhost 操作関数のセットを設定します。現在、2 つの vhost バックエンドがあります。1 つはカーネル モードで実装された virtio バックエンドで、もう 1 つはユーザー モードで実装された virtio バックエンドです。
- kernel_ops: いくつかのコールバック関数の実装である vhost カーネル操作関数セットは、最終的には vhost_kernel_call-->ioctl-->vhost-net.ko パスを通じて構成されます。
ioctl システム コールとドライバーの相互作用は、簡単に 3 つのカテゴリに分けることができます。以下にいくつかの重要な設定を示します。 vhost ネット設定 - VHOST_SET_OWNER: 最下層は、上記のデータ構造の vhost_worker に対応する、呼び出し元のカーネル スレッドを作成します。同時に、呼び出し元スレッドのメモリ空間データ構造も vhost_dev 構造に保存されます。
- VHOST_NET_SET_BACKEND: vhost-net がタップ デバイスと直接通信できるように、Qemu からカーネル状態に渡されるタップ デバイスに対応する fd などの vhost-net のバックエンド デバイスを設定します。
vhost-dev 設定 ゲスト OS の仮想アドレスからホストの最終的な物理アドレスへのマッピング関係を上の図に示します。ゲスト OS でデータを送信する場合は、ゲスト OS に関する物理アドレス レイアウト情報を Qemu に渡すだけで済みます。さらに、VHOST_SET_OWNER 中に渡されたメモリ空間情報と組み合わせることで、マッピング関係に従ってホスト上の物理アドレスに対応するゲスト OS 内のデータを見つけ、最終的な転送を完了できます。 - VHOST_SET_MEM_TABLE: Qemu 内の仮想マシンの物理アドレスレイアウト情報をカーネルに渡します。この問題を明確に説明するために、メモリ仮想化の以前の図を見てみましょう。
vhost vring 設定 - VHOST_SET_VRING_KICK: vhost-net モジュールのフロントエンドの virtio ドライバーが通知を送信し、最終的に handle_kick 関数の実行をトリガーするときにトリガーされる eventfd と通知メカニズムを設定します。
- VHOST_SET_VRING_CALL: vhost-net バックエンドから仮想マシン virtio フロントエンドへの割り込み通知を設定します。前の記事の irqfd メカニズムを参照してください。
- また、vring デバイスには、vring のサイズ、アドレス情報なども含まれます。
上記の設定のプロセス パスは次のようになります (クリティカル パスのみが描画されています)。 - ゲスト OS の virtio-net ドライバーが初期化を完了すると、vp_set_status を介してステータスを設定し、バックエンド ドライバーに準備が完了したことを通知します。これにより、例外処理のために VM が終了して KVM に入り、最終的に Qemu にルーティングされます。
- Qemu の vcpu スレッド監視が異常です。 KVM_EXIT_MMIO が検出されると、virtio_pci_common_write 関数など、IO 領域に登録されている読み取りおよび書き込み関数がコールバックされ、最終的に vhost_net_start 関数がレベルごとに呼び出されます。
- vhost_net_start では、最終的に kernel_ops 関数セットを使用して、基盤となるレイヤーをセットアップし、対話します。
初期化が完了したら、データの送受信を見てみましょう。全体のプロセスを明確に表現するために、全体の図をいくつかのステップに分割して説明します。 3.2 データ転送1) 送信前のブロック図は以下のとおりです。 - ゲスト OS の virtio-net ドライバーは、送信用と受信用の 2 つの virtqueue を維持します。
- 図のデータグラムは送信する必要のあるデータを表します。
- KVM モジュールは通知メカニズムとして ioeventfd と irqfd を提供します。
- タスクを処理するために、vhost-net モジュール内に vhost_worker カーネル スレッドが作成されます。
2) - データ パケットの準備が整うと、kick fd がトリガーされ、vhost_worker カーネル スレッドが起動して handle_tx_kick が呼び出され、データが送信されます。
- Tap/Tun が送信準備ができていない場合、vhost_worker はソケットをポーリングし、Tap/Tun が起動するまで待機します。起動すると、handle_tx_net を呼び出して送信できます。
- 最後の handle_tx は特定の送信を完了します。
3) - vhost_get_vq_desc 関数は、vritqueue で使用可能なバッファーを検索し、アクセスを向上させるためにその情報を iov に保存します。
- sock->ops->sendmsg() 関数は実際には tun_sendmsg 関数を呼び出し、その中で skb 構造体が割り当てられ、iov[] 内の情報が渡されます。最後に、図に示すようにデータがコピーされて送信され、NIC を介して送信されます。
4) - データが送信された後、irqfd メカニズムを通じて vcpu に通知されます。
3.3 データ受信データの受信はデータの送信の逆のプロセスであり、プロセスは同じです。 1) 初期化部分は送信プロセスと一致しています。 Tap/Tun ドライバーは NIC からデータ パケットを受信し、それを vhost-net に送信する準備をします。 2) - vhost-net の vhost_worker スレッドも、送信側と同様に 2 つの fd をポーリングします。
- kick fd で信号がトリガーされると、最終的に handle_rx_kick 関数が呼び出されます。 Tap/Tun に対応するソケットで信号がトリガーされると、handle_rx_net 関数が呼び出されます。
- 最後に、handle_rx を使用して実際の受信を完了します。
3) - 受信プロセス中、vhost_get_vq_desc は virtqueue 内の使用可能なバッファを取得し、その情報を iov[] に格納します。
- sock->ops->recvmsg() 関数は実際には tun_recvmsg 関数を指しており、ここでデータ転送が最終的に完了します。
4) データが受信されると、irqfd メカニズムを介して vcpu に渡され、ゲスト OS で処理されます。 vhost-net の全体的な内容は非常に大きく、上から下までの詳細は非常に扱いにくいです。短い記事ですべてを網羅するのは難しいので、概要だけ紹介します。 とりあえずここでやめておきましょう。 参照する virtio-networking と vhost-net の紹介 Virtio ネットワークと vhost-net の詳細 Vhost-net デバイス IOTLB |