Translated message

A translation of this page exists in English.

Warning message

This translation is outdated. For the most up-to-date information, please refer to the English version.

仮想 vhost プロトコルと、OVS DPDK、qemu、および virtio-net でのその実装の詳細図

Solution Verified - Updated -

Environment

Red Hat OpenStack Platform 10
Open vSwitch 2.6.1

Issue

仮想 vhost プロトコルと、OVS DPDK、qemu、および virtio-net でのその実装の詳細図

Resolution

免責事項: 以下に示す外部の Web サイトへのリンクは、お客様の利便性のみを目的として提供しています。Red Hat はリンクの内容を確認しておらず、コンテンツまたは可用性について責任を負わないものとします。外部の Web サイトへのリンクを含めることは、Web サイトまたはそれらの法的主体、製品またはサービスについて Red Hat が承認したことを意味するものではありません。お客様は、外部サイトまたはコンテンツの使用 (または信頼) によって生じる損失または費用について、Red Hat が責任を負わないことに同意するものとします。

仮想 vhost プロトコルと、OVS DPDK、qemu、および virtio-net でのその実装の詳細図

概要: OVS DPDK と qemu が vhost ユーザープロトコルを介して通信する方法

vhost ユーザープロトコルは、制御パスとデータパスで設定されます。

  • すべての制御情報は Unix ソケット経由で交換されます。これには、直接メモリーアクセスのためにメモリーマッピングを交換するための情報や、データが virtio キューに入れられた場合に反対側をキック/中断するための情報が含まれます。neutron の Unix ソケットの名前は vhuxxxxxxxx-xx です。

  • 実際のデータプレーンは、直接メモリーアクセスを介して実装されます。ゲスト内の virtio-net ドライバーは、インスタンスメモリーの一部を virtio キューに割り当てます。このキューの構造は、virtio 標準で標準化されています。Qemu は、制御チャネルを介して OVS DPDK とこのメモリーセクションのアドレスを共有します。次に、DPDK 自体が同じ標準化された virtio キュー構造をこのメモリーセクションにマップし、インスタンスの hugepage メモリー内の virtio キューから直接読み書きできるようにします。この直接メモリーアクセスは、OVS DPDK と qemu の両方が hugepage メモリーを使用する必要がある理由の 1 つです。qemu が正しく設定されていても、Huge Page メモリーの設定が不足している場合には、OVS DPDK は qemu のメモリーにアクセスできず、パケットを交換できません。nova のメタデータを介してインスタンスの hugepage をリクエストするのを忘れると、ユーザーはこれに気付くでしょう。

OVS DPDK がインスタンスに向けて送信すると、これらのパケットは OVS DPDK の統計情報内にポート vhuxxxxxxxx-xx の Tx として表示されます。インスタンス内では、これらのパケットは Rx として表示されます。

インスタンスがパケットを OVS DPDK に送信すると、インスタンスではこれらのパケットが Tx として表示され、OVS DPDK の vhuxxxxxxxx-xx ポートでは Rx として表示されます。

インスタンスにはハードウェアカウンターがないことに注意してください。ethtool -s は実装されていません。すべての低レベルカウンターは、OVS 内にのみ表示され (ovs-vsctl list get interfave vhuxxxxxxxx-xx statistics)、OVS DPDK のパースペクティブをレポートします。

パケットは共有メモリー経由で直接送信できますが、パケットが virtio キューにコピーされたことを反対側に伝える手段がどちらの側にも必要です。これは、vhost ユーザーソケット vhuxxxxxxxx-xx で実装されているコントロールプレーンを介して反対側を キック することによって発生します。反対側をキックすることには代償が伴います。まず、ソケットに書き込むためにシステムコールが必要です。次に、割り込みは相手側で処理する必要があります。したがって、送信側と受信側の両方が、制御チャネル内でコストのかかる余分な時間を費やします。

コントロールプレーンを介したコストのかかる キック を回避するために、Open vSwitch と qemu の両方が特定のフラグを設定して、割り込みを受信したくないことを反対側に通知できます。ただし、一時的または継続的に virtio キューをポーリングする場合にのみ、これを行うことができます。

たとえば、ネットワークパフォーマンスの場合、これはパケット処理の最適な手段がインスタンス自体内の DPDK であることを意味します。Linux カーネルネットワーキング (NAPI) は、割り込みモードとポーリングモードの処理を組み合わせて使用しますが、依然として多数の割り込みにさらされています。OVS DPDK は、非常に高いレートでインスタンスに向けてパケットを送信します。同時に、qemu の virtio キューの RX および TX バッファーは、デフォルトで 256 エントリー、最大で 1024 エントリーに制限されています。その結果、インスタンス自体がパケットを非常に速く処理する必要があります。これは、インスタンスのインターフェイスで DPDK PMD を使用して常にポーリングすることで実現するのが理想的です。

vhost ユーザープロトコル

https://github.com/qemu/qemu/blob/master/docs/interop/vhost-user.txt

Vhost-user Protocol
===================

Copyright (c) 2014 Virtual Open Systems Sarl.

This work is licensed under the terms of the GNU GPL, version 2 or later.
See the COPYING file in the top-level directory.
===================

This protocol is aiming to complement the ioctl interface used to control the
vhost implementation in the Linux kernel. It implements the control plane needed
to establish virtqueue sharing with a user space process on the same host. It
uses communication over a Unix domain socket to share file descriptors in the
ancillary data of the message.

The protocol defines 2 sides of the communication, master and slave. Master is
the application that shares its virtqueues, in our case QEMU. Slave is the
consumer of the virtqueues.

In the current implementation QEMU is the Master, and the Slave is intended to
be a software Ethernet switch running in user space, such as Snabbswitch.

Master and slave can be either a client (i.e. connecting) or server (listening)
in the socket communication.

vhost ユーザーには 2 つの側面があります。

  • Master - qemu

  • Slave - Open vSwitch またはその他のソフトウェアスイッチ

vhost ユーザーは 2 つのモードで実行できます。

  • vhostuser-client - qemu がサーバー、ソフトウェアスイッチがクライアント

  • vhostuser - ソフトウェアスイッチがサーバー、qemu がクライアント

vhost user は vhost アーキテクチャーに基づいており、すべての機能をユーザー空間に実装しています。

qemu インスタンスが起動すると、すべてのインスタンスメモリーが共有 hugepage として割り当てられます。OS の virtio 準仮想化ドライバーは、virtio リングバッファーを保持するために、この hugepage メモリーの一部を予約します。これにより、OVS DPDK はインスタンスの virtio リングから直接読み書きできるようになります。OVS DPDK と qemu の両方が、この予約済みメモリーセクションを介してパケットを直接交換できます。

ユーザー空間アプリケーションは、事前に割り当てられた共有ゲスト RAM のファイル記述子を受け取ります。ゲストのメモリー空間にある関連する vring に直接アクセスします (http://www.virtualopensystems.com/en/solutions/guides/snabbswitch-qemu/)。

たとえば、次の VM、モード vhostuser を見てください。

qemu      528828  0.1  0.0 2920084 34188 ?       Sl   Mar28   1:45 /usr/libexec/qemu-kvm -name guest=instance-00000028,debug-threads=on -S -object secret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-58-instance-00000028/master-key.aes -machine pc-i440fx-rhel7.4.0,accel=kvm,usb=off,dump-guest-core=off -cpu Skylake-Client,ss=on,hypervisor=on,tsc_adjust=on,pdpe1gb=on,mpx=off,xsavec=off,xgetbv1=off -m 2048 -realtime mlock=off -smp 8,sockets=4,cores=1,threads=2 -object memory-backend-file,id=ram-node0,prealloc=yes,mem-path=/dev/hugepages/libvirt/qemu/58-instance-00000028,share=yes,size=1073741824,host-nodes=0,policy=bind -numa node,nodeid=0,cpus=0-3,memdev=ram-node0 -object memory-backend-file,id=ram-node1,prealloc=yes,mem-path=/dev/hugepages/libvirt/qemu/58-instance-00000028,share=yes,size=1073741824,host-nodes=1,policy=bind -numa node,nodeid=1,cpus=4-7,memdev=ram-node1 -uuid 48888226-7b6b-415c-bcf7-b278ba0bca62 -smbios type=1,manufacturer=Red Hat,product=OpenStack Compute,version=14.1.0-3.el7ost,serial=3d5e138a-8193-41e4-ac95-de9bfc1a3ef1,uuid=48888226-7b6b-415c-bcf7-b278ba0bca62,family=Virtual Machine -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-58-instance-00000028/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=delay -no-hpet -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/var/lib/nova/instances/48888226-7b6b-415c-bcf7-b278ba0bca62/disk,format=qcow2,if=none,id=drive-virtio-disk0,cache=none -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -chardev socket,id=charnet0,path=/var/run/openvswitch/vhuc26fd3c6-4b -netdev vhost-user,chardev=charnet0,queues=8,id=hostnet0 -device virtio-net-pci,mq=on,vectors=18,netdev=hostnet0,id=net0,mac=fa:16:3e:52:30:73,bus=pci.0,addr=0x3 -add-fd set=0,fd=33 -chardev file,id=charserial0,path=/dev/fdset/0,append=on -device isa-serial,chardev=charserial0,id=serial0 -chardev pty,id=charserial1 -device isa-serial,chardev=charserial1,id=serial1 -device usb-tablet,id=input0,bus=usb.0,port=1 -vnc 172.16.2.10:1 -k en-us -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x5 -msg timestamp=on

Qemu は、Huge Page プールからメモリーを割り当て、それを共有メモリーにする (share=yes) ように指示されます。

-object memory-backend-file,id=ram-node0,prealloc=yes,mem-path=/dev/hugepages/libvirt/qemu/58-instance-00000028,share=yes,size=1073741824,host-nodes=0,policy=bind -numa node,nodeid=0,cpus=0-3,memdev=ram-node0 -object memory-backend-file,id=ram-node1,prealloc=yes,mem-path=/dev/hugepages/libvirt/qemu/58-instance-00000028,share=yes,size=1073741824,host-nodes=1,policy=bind

ただし、単にパケットを相手のバッファーにコピーするだけでは十分ではありません。さらに、vhost ユーザーは、vswitch と qemu 間の通信に Unix ドメインソケット (vhu[a-f0-9-]) を使用します。これは、初期化中と共有メモリー内の virtio リングにパケットがコピーされたときに反対側を キック するために使用しています。したがって、相互作用は、設定と通知用の制御パス (vhu ソケット) と、実際のペイロードを移動するためのデータパス (直接メモリーアクセス) で設定されます。

For the described Virtio mechanism to work, we need a setup interface to initialize the shared memory regions and exchange the event file descriptors. A Unix domain socket implements an API which allows us to do that. This straightforward socket interface can be used to initialize the userspace Virtio transport (vhost-user), in particular:

* Vrings are determined at initialization and are placed in shared memory between the two processed.

* For Virtio events (Vring kicks) we shall use eventfds that map to Vring events. This allows us compatibility with the QEMU/KVM implementation described in the next chapter, since KVM allows us to match events coming from virtio_pci in the guest with eventfds (ioeventfd and irqfd).

Sharing file descriptors between two processes differs than sharing them between a process and the kernel. One needs to use sendmsg over a Unix domain socket with SCM_RIGHTS set.

(http://www.virtualopensystems.com/en/solutions/guides/snabbswitch-qemu/)

vhostuser モードでは、OVS は vhu ソケットを作成し、qemu はそれに接続します。vhostuser クライアントモードでは、qemu は vhu ソケットを作成し、OVS はそれに接続します。

上記の vhostuser モードの例では、qemu は vhost-user タイプの netdev/var/run/openvswitch/vhuc26fd3c6-4b に接続するように指示されています。

-chardev socket,id=charnet0,path=/var/run/openvswitch/vhuc26fd3c6-4b -netdev vhost-user,chardev=charnet0,queues=8,id=hostnet0 -device virtio-net-pci,mq=on,vectors=18,netdev=hostnet0,id=net0,mac=fa:16:3e:52:30:73,bus=pci.0,addr=0x3

lsof で、ソケットが OVS によって作成されたことが分かります。

[root@overcloud-compute-0 ~]# lsof -nn | grep vhuc26fd3c6-4b | awk '{print $1}' | uniq
ovs-vswit
vfio-sync
eal-intr-
lcore-sla
dpdk_watc
vhost_thr
ct_clean3
urcu4
handler12
handler13
handler14
handler15
revalidat
pmd189
pmd182
pmd187
pmd184
pmd185
pmd186
pmd183
pmd188

パケットが参加者の 1 人によって共有メモリー内の virtio リングにコピーされると、反対側のいずれかは、

  • 現在 (Linux カーネルの NAPI など) または常に (DPDK の PMD など) キューをポーリングしており、その場合、それ以上の通知なしに新しいパケットを取得します。

  • キューをポーリングせず、パケットの到着を通知する必要があります。

2 番目のケースでは、vhu ソケットを介して別の制御パスを介してインスタンスを キック できます。制御パスは、eventfd オブジェクトを交換することにより、ユーザー空間に割り込みを実装します。ソケットへの書き込みにはシステムコールが必要であり、PMD がカーネル空間で時間を費やすことに注意してください。仮想マシンは、VRING_AVAIL_F_NO_INTERRUPT フラグを設定することにより、制御パスをオフに切り替えることができます。そうしないと、仮想マシンが新しいパケットを virtio リングに入れるたびに、Open vSwitch が仮想マシンを キック (中断) します。

詳細については、次のブログ投稿を参照してください: http://blog.vmsplice.net/2011/09/qemu-internals-vhost-architecture.html

Vhost as a userspace interface

One surprising aspect of the vhost architecture is that it is not tied to KVM in any way. Vhost is a userspace interface and has no dependency on the KVM kernel module. This means other userspace code, like libpcap, could in theory use vhost devices if they find them convenient high-performance I/O interfaces.

When a guest kicks the host because it has placed buffers onto a virtqueue, there needs to be a way to signal the vhost worker thread that there is work to do. Since vhost does not depend on the KVM kernel module they cannot communicate directly. Instead vhost instances are set up with an eventfd file descriptor which the vhost worker thread watches for activity. The KVM kernel module has a feature known as ioeventfd for taking an eventfd and hooking it up to a particular guest I/O exit. QEMU userspace registers an ioeventfd for the VIRTIO_PCI_QUEUE_NOTIFY hardware register access which kicks the virtqueue. This is how the vhost worker thread gets notified by the KVM kernel module when the guest kicks the virtqueue.

On the return trip from the vhost worker thread to interrupting the guest a similar approach is used. Vhost takes a "call" file descriptor which it will write to in order to kick the guest. The KVM kernel module has a feature called irqfd which allows an eventfd to trigger guest interrupts. QEMU userspace registers an irqfd for the virtio PCI device interrupt and hands it to the vhost instance. This is how the vhost worker thread can interrupt the guest.

In the end the vhost instance only knows about the guest memory mapping, a kick eventfd, and a call eventfd.
Where to find out more
Here are the main points to begin exploring the code:

    drivers/vhost/vhost.c - common vhost driver code
    drivers/vhost/net.c - vhost-net driver
    virt/kvm/eventfd.c - ioeventfd and irqfd

The QEMU userspace code shows how to initialize the vhost instance:

    hw/vhost.c - common vhost initialization code
    hw/vhost_net.c - vhost-net initialization

データパス - 直接メモリーアクセス

virtq のメモリーのマッピング方法

virtio 標準は、virtq がどのように見えるべきかを正確に定義しています。

2.4 Virtqueues

The mechanism for bulk data transport on virtio devices is pretentiously called a virtqueue. Each device can have zero or more virtqueues. Each queue has a 16-bit queue size parameter, which sets the number of entries and implies the total size of the queue.

Each virtqueue consists of three parts:

    Descriptor Table
    Available Ring
    Used Ring

http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html

標準では、記述子テーブルの構造、使用可能なリング、および使用されるリングが正確に定義されています。たとえば、利用可能なリングの場合:

2.4.6 The Virtqueue Available Ring
struct virtq_avail {
#define VIRTQ_AVAIL_F_NO_INTERRUPT      1
        le16 flags;
        le16 idx;
        le16 ring[ /* Queue Size */ ];
        le16 used_event; /* Only if VIRTIO_F_EVENT_IDX */
};

The driver uses the available ring to offer buffers to the device: each ring entry refers to the head of a descriptor chain. It is only written by the driver and read by the device.

idx field indicates where the driver would put the next descriptor entry in the ring (modulo the queue size). This starts at 0, and increases. Note: The legacy [Virtio PCI Draft] referred to this structure as vring_avail, and the constant as VRING_AVAIL_F_NO_INTERRUPT, but the layout and value were identical.

http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html

直接メモリーアクセスを可能にするために、DPDK は上記の規格を実装しています。

dpdk-stable-16.11.4/drivers/net/virtio/virtio_ring.h

 48 /* The Host uses this in used->flags to advise the Guest: don't kick me
 49  * when you add a buffer.  It's unreliable, so it's simply an
 50  * optimization.  Guest will still kick if it's out of buffers. */
 51 #define VRING_USED_F_NO_NOTIFY  1
 52 /* The Guest uses this in avail->flags to advise the Host: don't
 53  * interrupt me when you consume a buffer.  It's unreliable, so it's
 54  * simply an optimization.  */
 55 #define VRING_AVAIL_F_NO_INTERRUPT  1
 56
 57 /* VirtIO ring descriptors: 16 bytes.
 58  * These can chain together via "next". */
 59 struct vring_desc {
 60         uint64_t addr;  /*  Address (guest-physical). */
 61         uint32_t len;   /* Length. */
 62         uint16_t flags; /* The flags as indicated above. */
 63         uint16_t next;  /* We chain unused descriptors via this. */
 64 };
 65
 66 struct vring_avail {
 67         uint16_t flags;
 68         uint16_t idx;
 69         uint16_t ring[0];
 70 };
 71
 72 /* id is a 16bit index. uint32_t is used here for ids for padding reasons. */
 73 struct vring_used_elem {
 74         /* Index of start of used descriptor chain. */
 75         uint32_t id;
 76         /* Total length of the descriptor chain which was written to. */
 77         uint32_t len;
 78 };
 79
 80 struct vring_used {
 81         uint16_t flags;
 82         volatile uint16_t idx;
 83         struct vring_used_elem ring[0];
 84 };
 85
 86 struct vring {
 87         unsigned int num;
 88         struct vring_desc  *desc;
 89         struct vring_avail *avail;
 90         struct vring_used  *used;
 91 };

dpdk-stable-16.11.4/lib/librte_vhost/vhost.h

 81 struct vhost_virtqueue {
 82         struct vring_desc       *desc;
 83         struct vring_avail      *avail;
 84         struct vring_used       *used;
 85         uint32_t                size;
 86
 87         uint16_t                last_avail_idx;
 88         uint16_t                last_used_idx;
 89 #define VIRTIO_INVALID_EVENTFD          (-1)
 90 #define VIRTIO_UNINITIALIZED_EVENTFD    (-2)
 91
 92         /* Backend value to determine if device should started/stopped */
 93         int                     backend;
 94         /* Used to notify the guest (trigger interrupt) */
 95         int                     callfd;
 96         /* Currently unused as polling mode is enabled */
 97         int                     kickfd;
 98         int                     enabled;
 99
100         /* Physical address of used ring, for logging */
101         uint64_t                log_guest_addr;
102
103         uint16_t                nr_zmbuf;
104         uint16_t                zmbuf_size;
105         uint16_t                last_zmbuf_idx;
106         struct zcopy_mbuf       *zmbufs;
107         struct zcopy_mbuf_list  zmbuf_list;
108
109         struct vring_used_elem  *shadow_used_ring;
110         uint16_t                shadow_used_idx;
111 } __rte_cache_aligned;

メモリーマッピングが完了すると、DPDK はゲストの共有メモリー内の virtio-net と同じ構造に直接作用し、操作できます。

制御パス - Unix ソケット

vhost ユーザーソケットを介した qemu および DPDK メッセージ交換

DPDK と qemu は、標準化された vhost-user プロトコルを介して通信します。

メッセージのタイプは次のとおりです。
dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.h

 54 typedef enum VhostUserRequest {
 55         VHOST_USER_NONE = 0,
 56         VHOST_USER_GET_FEATURES = 1,
 57         VHOST_USER_SET_FEATURES = 2,
 58         VHOST_USER_SET_OWNER = 3,
 59         VHOST_USER_RESET_OWNER = 4,
 60         VHOST_USER_SET_MEM_TABLE = 5,
 61         VHOST_USER_SET_LOG_BASE = 6,
 62         VHOST_USER_SET_LOG_FD = 7,
 63         VHOST_USER_SET_VRING_NUM = 8,
 64         VHOST_USER_SET_VRING_ADDR = 9,
 65         VHOST_USER_SET_VRING_BASE = 10,
 66         VHOST_USER_GET_VRING_BASE = 11,
 67         VHOST_USER_SET_VRING_KICK = 12,
 68         VHOST_USER_SET_VRING_CALL = 13,
 69         VHOST_USER_SET_VRING_ERR = 14,
 70         VHOST_USER_GET_PROTOCOL_FEATURES = 15,
 71         VHOST_USER_SET_PROTOCOL_FEATURES = 16,
 72         VHOST_USER_GET_QUEUE_NUM = 17,
 73         VHOST_USER_SET_VRING_ENABLE = 18,
 74         VHOST_USER_SEND_RARP = 19,
 75         VHOST_USER_MAX
 76 } VhostUserRequest;

メッセージタイプの詳細については、https://github.com/qemu/qemu/blob/master/docs/interop/vhost-user.txt の qemu のソースコードを参照してください。

DPDK は受信メッセージを以下のように処理します。
dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.c

 920 int
 921 vhost_user_msg_handler(int vid, int fd)
 922 {

これは以下を使用します。
dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.c:

 872 /* return bytes# of read on success or negative val on failure. */
 873 static int
 874 read_vhost_message(int sockfd, struct VhostUserMsg *msg)
 875 {

DPDK は送信メッセージを次のように書き込みます。
dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.c

 902 static int
 903 send_vhost_message(int sockfd, struct VhostUserMsg *msg)
 904 {

qemu には、同等の受信方法があります。
qemu-2.9.0/contrib/libvhost-user/libvhost-user.c

746 static bool
747 vu_process_message(VuDev *dev, VhostUserMsg *vmsg)

また、明らかに qemu には、同等の送信方法もあります。
qemu-2.9.0/hw/virtio/vhost-user.c

198 /* most non-init callers ignore the error */
199 static int vhost_user_write(struct vhost_dev *dev, VhostUserMsg *msg,
200                             int *fds, int fd_num)
201 {

DPDK が Unix ソケットを登録し、それをメッセージ交換に使用する方法

neutron は Open vSwitch に vhuxxxxxxxx-xx という名前のポートを作成するように指示します。OVS 内では、この名前は netdev->name として netdev 構造に保存されます。

vhost ユーザーポートを作成するとき、Open vSwitch は DPDK に新しい vhost-user ソケットを登録するように指示します。ソケットのパスは、vhost_sock_dirnetdev->name を連結した dev->vhost_id として設定されます。

OVS は、RTE_VHOST_USER_CLIENT フラグを渡すことにより、vhost ユーザークライアントモードでソケットの作成を要求できます。

OVS の netdev_dpdk_vhost_construct メソッドは DPDK の rte_vhost_driver_register メソッドを呼び出し、次に vhost_user_create_server または vhost_user_create_client を実行します。デフォルトでは、vhost ユーザーサーバーモードが使用され、RTE_VHOST_USER_CLIENT が設定されている場合は、vhost ユーザークライアントモードが使用されます。

関連するメソッドの概要:

OVS
                       netdev_dpdk_vhost_construct
                        (struct netdev *netdev)
                                   |
                                   |
DPDK                               V
                      rte_vhost_driver_register
                        (const char *path, uint64_t flags)
                                   |
             -----------------------------------------------                     
             |                                             |
             V                                             |
vhost_user_create_server                                   |
  (struct vhost_user_socket *vsocket)                      |
             |                                             |
             V                                             V
vhost_user_server_new_connection                     vhost_user_create_client                     vhost_user_client_reconnect
(int fd, void *dat, int *remove __rte_unused)          (struct vhost_user_socket *vsocket)          (void *arg __rte_unused)
             |                                             |                                                  |
             V                                             V                                                  V
             --------------------------------------------------------------------------------------------------
                                                           |
                                                           V
                                               vhost_user_add_connection
                                                 (int fd, struct vhost_user_socket *vsocket)
                                                           |
                                                           V
                                               vhost_user_read_cb
                                                (int connfd, void *dat, int *remove)
                                                           |
                                                           V
                                                vhost_user_msg_handler

netdev_dpdk_vhost_construct is in openvswitch-2.6.1/lib/netdev-dpdk.c:

 886 static int
 887 netdev_dpdk_vhost_construct(struct netdev *netdev)
 888 {
 889     struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
 890     const char *name = netdev->name;
 891     int err;
 892
 893     /* 'name' is appended to 'vhost_sock_dir' and used to create a socket in
 894      * the file system. '/' or '\' would traverse directories, so they're not
 895      * acceptable in 'name'. */
 896     if (strchr(name, '/') || strchr(name, '\\')) {
 897         VLOG_ERR("\"%s\" is not a valid name for a vhost-user port. "
 898                  "A valid name must not include '/' or '\\'",
 899                  name);
 900         return EINVAL;
 901     }
 902
 903     if (rte_eal_init_ret) {
 904         return rte_eal_init_ret;
 905     }
 906
 907     ovs_mutex_lock(&dpdk_mutex);
 908     /* Take the name of the vhost-user port and append it to the location where
 909      * the socket is to be created, then register the socket.
 910      */
 911     snprintf(dev->vhost_id, sizeof dev->vhost_id, "%s/%s",
 912              vhost_sock_dir, name);
 913
 914     dev->vhost_driver_flags &= ~RTE_VHOST_USER_CLIENT;
 915     err = rte_vhost_driver_register(dev->vhost_id, dev->vhost_driver_flags);
 916     if (err) {
 917         VLOG_ERR("vhost-user socket device setup failure for socket %s\n",
 918                  dev->vhost_id);
 919     } else {
 920         fatal_signal_add_file_to_unlink(dev->vhost_id);
 921         VLOG_INFO("Socket %s created for vhost-user port %s\n",
 922                   dev->vhost_id, name);
 923     }
 924     err = netdev_dpdk_init(netdev, -1, DPDK_DEV_VHOST);
 925
 926     ovs_mutex_unlock(&dpdk_mutex);
 927     return err;
 928 }

netdev_dpdk_vhost_constructrte_vhost_driver_register を呼び出します。次のコードはすべて dpdk-stable-16.11.4/lib/librte_vhost/socket.c にあります。

494 /*
495  * Register a new vhost-user socket; here we could act as server
496  * (the default case), or client (when RTE_VHOST_USER_CLIENT) flag
497  * is set.
498  */
499 int
500 rte_vhost_driver_register(const char *path, uint64_t flags)
501 {
(...)
525         if ((flags & RTE_VHOST_USER_CLIENT) != 0) {
526                 vsocket->reconnect = !(flags & RTE_VHOST_USER_NO_RECONNECT);
527                 if (vsocket->reconnect && reconn_tid == 0) {
528                         if (vhost_user_reconnect_init() < 0) {
529                                 free(vsocket->path);
530                                 free(vsocket);
531                                 goto out;
532                         }
533                 }
534                 ret = vhost_user_create_client(vsocket);
535         } else {
536                 vsocket->is_server = true;
537                 ret = vhost_user_create_server(vsocket);
538         }

vhost_user_create_servervhost_user_server_new_connection を呼び出します。

304 static int
305 vhost_user_create_server(struct vhost_user_socket *vsocket)
306 {
307         int fd;
308         int ret;
309         struct sockaddr_un un;
310         const char *path = vsocket->path;
311
312         fd = create_unix_socket(path, &un, vsocket->is_server);

そして、次の 3 つのメソッドのいずれかが vhost_user_add_connection を呼び出します。

239 /* call back when there is new vhost-user connection from client  */
240 static void
241 vhost_user_server_new_connection(int fd, void *dat, int *remove __rte_unused)
242 {
(...)
386 static void *
387 vhost_user_client_reconnect(void *arg __rte_unused)
388 {
(...)
447 static int
448 vhost_user_create_client(struct vhost_user_socket *vsocket)
449 {
(...)
190 static void
191 vhost_user_add_connection(int fd, struct vhost_user_socket *vsocket)
192 {

vhost_user_add_connectionvhost_user_read_cb を実行し、vhost_user_msg_handler が受信メッセージの処理に使われます。

253 static void
254 vhost_user_read_cb(int connfd, void *dat, int *remove)
255 {
256         struct vhost_user_connection *conn = dat;
257         struct vhost_user_socket *vsocket = conn->vsocket;
258         int ret;
259
260         ret = vhost_user_msg_handler(conn->vid, connfd);
261         if (ret < 0) {
262                 close(connfd);
263                 *remove = 1;
264                 vhost_destroy_device(conn->vid);
265
266                 pthread_mutex_lock(&vsocket->conn_mutex);
267                 TAILQ_REMOVE(&vsocket->conn_list, conn, next);
268                 pthread_mutex_unlock(&vsocket->conn_mutex);
269
270                 free(conn);
271
272                 if (vsocket->reconnect)
273                         vhost_user_create_client(vsocket);
274         }
275 }

dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.c

  920 int
 921 vhost_user_msg_handler(int vid, int fd)
 922 {
 923         struct virtio_net *dev;
 924         struct VhostUserMsg msg;
 925         int ret;
 926 
 927         dev = get_device(vid);
 928         if (dev == NULL)
 929                 return -1;
 930 
 931         ret = read_vhost_message(fd, &msg);
 932         if (ret <= 0 || msg.request >= VHOST_USER_MAX) {
 933                 if (ret < 0)
 934                         RTE_LOG(ERR, VHOST_CONFIG,
 935                                 "vhost read message failed\n");
 936                 else if (ret == 0)
 937                         RTE_LOG(INFO, VHOST_CONFIG,
 938                                 "vhost peer closed\n");
 939                 else
 940                         RTE_LOG(ERR, VHOST_CONFIG,
 941                                 "vhost read incorrect message\n");
 942 
 943                 return -1;
 944         }
 945 
 946         RTE_LOG(INFO, VHOST_CONFIG, "read message %s\n",
 947                 vhost_message_str[msg.request]);
 948         switch (msg.request) {
 949         case VHOST_USER_GET_FEATURES:
 950                 msg.payload.u64 = vhost_user_get_features();
 951                 msg.size = sizeof(msg.payload.u64);
 952                 send_vhost_message(fd, &msg);
 953                 break;
 954         case VHOST_USER_SET_FEATURES:
 955                 vhost_user_set_features(dev, msg.payload.u64);
 956                 break;
 957 
 958         case VHOST_USER_GET_PROTOCOL_FEATURES:
 959                 msg.payload.u64 = VHOST_USER_PROTOCOL_FEATURES;
 960                 msg.size = sizeof(msg.payload.u64);
 961                 send_vhost_message(fd, &msg);
 962                 break;
 963         case VHOST_USER_SET_PROTOCOL_FEATURES:
 964                 vhost_user_set_protocol_features(dev, msg.payload.u64);
 965                 break;
 966 
 967         case VHOST_USER_SET_OWNER:
 968                 vhost_user_set_owner();
 969                 break;
 970         case VHOST_USER_RESET_OWNER:
 971                 vhost_user_reset_owner(dev);
 972                 break;
 973 
 974         case VHOST_USER_SET_MEM_TABLE:
 975                 vhost_user_set_mem_table(dev, &msg);
 976                 break;
 977 
 978         case VHOST_USER_SET_LOG_BASE:
 979                 vhost_user_set_log_base(dev, &msg);
 980 
 981                 /* it needs a reply */
 982                 msg.size = sizeof(msg.payload.u64);
 983                 send_vhost_message(fd, &msg);
 984                 break;
 985         case VHOST_USER_SET_LOG_FD:
 986                 close(msg.fds[0]);
 987                 RTE_LOG(INFO, VHOST_CONFIG, "not implemented.\n");
 988                 break;
 989 
 990         case VHOST_USER_SET_VRING_NUM:
 991                 vhost_user_set_vring_num(dev, &msg.payload.state);
 992                 break;
 993         case VHOST_USER_SET_VRING_ADDR:
 994                 vhost_user_set_vring_addr(dev, &msg.payload.addr);
 995                 break;
 996         case VHOST_USER_SET_VRING_BASE:
 997                 vhost_user_set_vring_base(dev, &msg.payload.state);
 998                 break;
 999 
1000         case VHOST_USER_GET_VRING_BASE:
1001                 ret = vhost_user_get_vring_base(dev, &msg.payload.state);
1002                 msg.size = sizeof(msg.payload.state);
1003                 send_vhost_message(fd, &msg);
1004                 break;
1005 
1006         case VHOST_USER_SET_VRING_KICK:
1007                 vhost_user_set_vring_kick(dev, &msg);
1008                 break;
1009         case VHOST_USER_SET_VRING_CALL:
1010                 vhost_user_set_vring_call(dev, &msg);
1011                 break;
1012 
1013         case VHOST_USER_SET_VRING_ERR:
1014                 if (!(msg.payload.u64 & VHOST_USER_VRING_NOFD_MASK))
1015                         close(msg.fds[0]);
1016                 RTE_LOG(INFO, VHOST_CONFIG, "not implemented\n");
1017                 break;
1018 
1019         case VHOST_USER_GET_QUEUE_NUM:
1020                 msg.payload.u64 = VHOST_MAX_QUEUE_PAIRS;
1021                 msg.size = sizeof(msg.payload.u64);
1022                 send_vhost_message(fd, &msg);
1023                 break;
1024 
1025         case VHOST_USER_SET_VRING_ENABLE:
1026                 vhost_user_set_vring_enable(dev, &msg.payload.state);
1027                 break;
1028         case VHOST_USER_SEND_RARP:
1029                 vhost_user_send_rarp(dev, &msg);
1030                 break;
1031 
1032         default:
1033                 break;
1034 
1035         }
1036 
1037         return 0;
1038 }

virtio が virtio キューのメモリーアドレスを DPDK に通信する方法

DPDK は vhost_user_set_vring_addr と呼ばれるメソッドを使用して、virtio の desc、used、avail リングアドレスを独自のアドレス空間に変換します。

dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.c

 324 /*
 325  * The virtio device sends us the desc, used and avail ring addresses.
 326  * This function then converts these to our address space.
 327  */
 328 static int
 329 vhost_user_set_vring_addr(struct virtio_net *dev, struct vhost_vring_addr *addr)
 330 {
 331         struct vhost_virtqueue *vq;
 332
 333         if (dev->mem == NULL)
 334                 return -1;
 335
 336         /* addr->index refers to the queue index. The txq 1, rxq is 0. */
 337         vq = dev->virtqueue[addr->index];
 338
 339         /* The addresses are converted from QEMU virtual to Vhost virtual. */
 340         vq->desc = (struct vring_desc *)(uintptr_t)qva_to_vva(dev,
 341                         addr->desc_user_addr);
 342         if (vq->desc == 0) {
 343                 RTE_LOG(ERR, VHOST_CONFIG,
 344                         "(%d) failed to find desc ring address.\n",
 345                         dev->vid);
 346                 return -1;
 347         }
 348
 349         dev = numa_realloc(dev, addr->index);
 350         vq = dev->virtqueue[addr->index];
 351
 352         vq->avail = (struct vring_avail *)(uintptr_t)qva_to_vva(dev,
 353                         addr->avail_user_addr);
 354         if (vq->avail == 0) {
 355                 RTE_LOG(ERR, VHOST_CONFIG,
 356                         "(%d) failed to find avail ring address.\n",
 357                         dev->vid);
 358                 return -1;
 359         }
 360
 361         vq->used = (struct vring_used *)(uintptr_t)qva_to_vva(dev,
 362                         addr->used_user_addr);
 363         if (vq->used == 0) {
 364                 RTE_LOG(ERR, VHOST_CONFIG,
 365                         "(%d) failed to find used ring address.\n",
 366                         dev->vid);
 367                 return -1;
 368         }
 369
 370         if (vq->last_used_idx != vq->used->idx) {
 371                 RTE_LOG(WARNING, VHOST_CONFIG,
 372                         "last_used_idx (%u) and vq->used->idx (%u) mismatches; "
 373                         "some packets maybe resent for Tx and dropped for Rx\n",
 374                         vq->last_used_idx, vq->used->idx);
 375                 vq->last_used_idx  = vq->used->idx;
 376                 vq->last_avail_idx = vq->used->idx;
 377         }
 378
 379         vq->log_guest_addr = addr->log_guest_addr;
 380
 381         LOG_DEBUG(VHOST_CONFIG, "(%d) mapped address desc: %p\n",
 382                         dev->vid, vq->desc);
 383         LOG_DEBUG(VHOST_CONFIG, "(%d) mapped address avail: %p\n",
 384                         dev->vid, vq->avail);
 385         LOG_DEBUG(VHOST_CONFIG, "(%d) mapped address used: %p\n",
 386                         dev->vid, vq->used);
 387         LOG_DEBUG(VHOST_CONFIG, "(%d) log_guest_addr: %" PRIx64 "\n",
 388                         dev->vid, vq->log_guest_addr);
 389
 390         return 0;
 391 }

タイプ VHOST_USER_SET_VRING_ADDR のメッセージが vhu ソケット経由で到着すると、メモリーが設定されます。
dpdk-stable-16.11.4/lib/librte_vhost/vhost_user.c

 920 int
 921 vhost_user_msg_handler(int vid, int fd)
 922 {
 923         struct virtio_net *dev;
 924         struct VhostUserMsg msg;
 925         int ret;
 926
 927         dev = get_device(vid);
 928         if (dev == NULL)
 929                 return -1;
 930
 931         ret = read_vhost_message(fd, &msg);
 932         if (ret <= 0 || msg.request >= VHOST_USER_MAX) {
 933                 if (ret < 0)
 934                         RTE_LOG(ERR, VHOST_CONFIG,
 935                                 "vhost read message failed\n");
 936                 else if (ret == 0)
 937                         RTE_LOG(INFO, VHOST_CONFIG,
 938                                 "vhost peer closed\n");
 939                 else
 940                         RTE_LOG(ERR, VHOST_CONFIG,
 941                                 "vhost read incorrect message\n");
 942
 943                 return -1;
 944         }
 945
 946         RTE_LOG(INFO, VHOST_CONFIG, "read message %s\n",
 947                 vhost_message_str[msg.request]);
 948         switch (msg.request) {
 949         case VHOST_USER_GET_FEATURES:
 950                 msg.payload.u64 = vhost_user_get_features();
 951                 msg.size = sizeof(msg.payload.u64);
 952                 send_vhost_message(fd, &msg);
 953                 break;
(...)
 993         case VHOST_USER_SET_VRING_ADDR:
 994                 vhost_user_set_vring_addr(dev, &msg.payload.addr);
 995                 break;

実際のところ、qemu には DPDK の同じメソッドと同等のものがあります。
qemu-2.9.0/contrib/libvhost-user/libvhost-user.c

746 static bool
 747 vu_process_message(VuDev *dev, VhostUserMsg *vmsg)
 748 {
 749     int do_reply = 0;
 750
 751     /* Print out generic part of the request. */
 752     DPRINT("================ Vhost user message ================\n");
 753     DPRINT("Request: %s (%d)\n", vu_request_to_string(vmsg->request),
 754            vmsg->request);
 755     DPRINT("Flags:   0x%x\n", vmsg->flags);
 756     DPRINT("Size:    %d\n", vmsg->size);
 757
 758     if (vmsg->fd_num) {
 759         int i;
 760         DPRINT("Fds:");
 761         for (i = 0; i < vmsg->fd_num; i++) {
 762             DPRINT(" %d", vmsg->fds[i]);
 763         }
 764         DPRINT("\n");
 765     }
 766
 767     if (dev->iface->process_msg &&
 768         dev->iface->process_msg(dev, vmsg, &do_reply)) {
 769         return do_reply;
 770     }
 771
 772     switch (vmsg->request) {
 773     case VHOST_USER_GET_FEATURES:
 774         return vu_get_features_exec(dev, vmsg);
(...)
 793     case VHOST_USER_SET_VRING_ADDR:
 794         return vu_set_vring_addr_exec(dev, vmsg);
(...)

明らかに、ソケットを介してそのアドレスを DPDK に通信する方法もあります。
qemu-2.9.0/hw/virtio/vhost-user.c:

329 static int vhost_user_set_vring_addr(struct vhost_dev *dev,
330                                      struct vhost_vring_addr *addr)
331 {
332     VhostUserMsg msg = {
333         .request = VHOST_USER_SET_VRING_ADDR,
334         .flags = VHOST_USER_VERSION,
335         .payload.addr = *addr,
336         .size = sizeof(msg.payload.addr),
337     };
338
339     if (vhost_user_write(dev, &msg, NULL, 0) < 0) {
340         return -1;
341     }
342
343     return 0;
344 }

vhost ユーザーを使用した OVS DPDK はどのようにパケットをインスタンスに送信し、いつ Tx ドロップが発生しますか?

OVS DPDK からインスタンスへの Tx を処理するコードは、lib/netdev-dpdk.c__netdev_dpdk_vhost_send() 関数にあります。

OVS は送信を試み、それ以上スペースがないにも拘らず、進捗があった場合には、VHOST_ENQ_RETRY_NUM で指定した回数だけ再試行します (デフォルトでは 8)。 最初の試行で進行がなかった (リングにプッシュされたパケットがない) 場合、または VHOST_ENQ_RETRY_NUM の回数を超えた場合、バッチ内の残りのすべてのパケット (最大 32 パケットである cnt まで) をドロップします。

1520     do {
1523
1524         tx_pkts = rte_vhost_enqueue_burst(netdev_dpdk_get_vid(dev),
1525                                           vhost_qid, cur_pkts, cnt);
1526         if (OVS_LIKELY(tx_pkts)) {
1527             /* Packets have been sent.*/
1528             cnt -= tx_pkts;
1529             /* Prepare for possible retry.*/
1530             cur_pkts = &cur_pkts[tx_pkts];
1531         } else {
1532             /* No packets sent - do not retry.*/
1533             break;
1534         }
1535     } while (cnt && (retries++ <= VHOST_ENQ_RETRY_NUM));
1536
1545     for (i = 0; i < total_pkts - dropped; i++) {
1546         dp_packet_delete(pkts[i]);
1547     }

インスタンス Rx 割り込み処理

OVS DPDK が新しいデータを virtio のリングに入れる場合、2 つのシナリオが考えられます。

  • インスタンスはそのキューをポーリングしていないため、新しいパケットが到着したことを認識させる必要があります

  • インスタンスは現在ポーリングしているため、新しいデータがリングにあることを伝える必要はありません

Linux カーネルネットワークを使用するインスタンス内で、Linux ネットワークスタックは、割り込みモードとポーリングモードを組み合わせた NAPI を使用します。ゲスト OS は割り込みモードで起動するため、最初の割り込みが発生するまで何もしません。これが発生すると、CPU は IRQ にすばやく ACK を返し、ksoftirqd スレッドがコールバックを実行するようにスケジュールします。また、それ以上の IRQ を無効にします。

ksoftirqd が実行されると、できるだけ多くのパケットを netdev_budget までポーリングしようとします。キューにさらにパケットがある場合、ksoftirqd はそれ自体を再スケジュールし、使用可能なパケットがなくなるまで操作を繰り返します。ポーリング中であり、それ以上の割り込みは必要ないことに注意してください。 利用可能なパケットがなくなると、ksoftirqd はポーリングを停止し、IRQ を再度有効にして、次のパケット/割り込みを待ちます。

インスタンスがポーリングしている場合、CPU キャッシュはホットで、余分な待ち時間や遅延が少なく、ホストと仮想マシンで適切なプロセスが実行されているため、待ち時間がさらに短縮されます。さらに、ホストが IRQ をゲストに送信するには、高価な Unix ソケット (syscall) に書き込む必要があり、遅延やコストがさらに増加します。

NFV アプリケーションの一部としてインスタンス内で DPDK を実行する利点は、PMD がトラフィックを処理する方法にあります。PMD は常にポーリングしているため、常に割り込みをオフに切り替えることができ、OVS DPDK は仮想マシンをキックする必要がなくなります。これにより、OVS DPDK がソケットに書き込む必要がなくなり、カーネルへの書き込みシステムコールを実行する必要がなくなります。これは双方に有利な状況です。OVS DPDK の PMD はユーザー空間に残り、インスタンスは制御プランと割り込み処理を介してキックされる余分なオーバーヘッドをスキップするため、パケットをより高速に処理できます。

フラグ VRING_AVAIL_F_NO_INTERRUPT が設定されていない場合、ゲストは割り込みを受け取ることができます。この割り込みは、callfd および OS の eventfd オブジェクトを介してゲストに送信されます。

したがって、ゲスト OS は割り込みを有効または無効にすることができます。ゲストが virtio インターフェイスで割り込みを無効にすると、virtio-net はこれを変換し、DPDK と qemu の両方で定義されているフラグ VRING_AVAIL_F_NO_INTERRUPT を使用します。

[root@overcloud-compute-0 SOURCES]# grep VRING_AVAIL_F_NO_INTERRUPT -R | grep def
dpdk-stable-16.11.5/drivers/net/virtio/virtio_ring.h:#define VRING_AVAIL_F_NO_INTERRUPT  1
dpdk-stable-17.11/drivers/net/virtio/virtio_ring.h:#define VRING_AVAIL_F_NO_INTERRUPT  1
[root@overcloud-compute-0 SOURCES]#
[root@overcloud-compute-0 qemu]# grep AVAIL_F_NO_INTERRUPT  -R -i | grep def
qemu-2.9.0/roms/SLOF/lib/libvirtio/virtio.h:#define VRING_AVAIL_F_NO_INTERRUPT    1
qemu-2.9.0/roms/ipxe/src/include/ipxe/virtio-ring.h:#define VRING_AVAIL_F_NO_INTERRUPT 1
qemu-2.9.0/roms/seabios/src/hw/virtio-ring.h:#define VRING_AVAIL_F_NO_INTERRUPT 1
qemu-2.9.0/include/standard-headers/linux/virtio_ring.h:#define VRING_AVAIL_F_NO_INTERRUPT    1

vq->avail->flagsVRING_AVAIL_F_NO_INTERRUPT のビットが設定されると、DPDK にインスタンスをキックし ない ように指示します。dpdk-stable-16.11.4/lib/librte_vhost/virtio_net.c から:

(...)
        /* Kick the guest if necessary. */
        if (!(vq->avail->flags & VRING_AVAIL_F_NO_INTERRUPT)
                        && (vq->callfd >= 0))
                eventfd_write(vq->callfd, (eventfd_t)1);
        return count;
(...)

前に説明したように、これにより、PMD がシステムコールを実行する必要がなくなります。

詳細はコード (インスタンスに対する OVS DPDK Tx) を確認

lib/netdev-dpdk.c のコードで、さらに調査を行うことができます。

1487 static void
1488 __netdev_dpdk_vhost_send(struct netdev *netdev, int qid,
1489                          struct dp_packet **pkts, int cnt)
1490 {
1491     struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
1492     struct rte_mbuf **cur_pkts = (struct rte_mbuf **) pkts;
1493     unsigned int total_pkts = cnt;
1494     unsigned int dropped = 0;
1495     int i, retries = 0;
1496
1497     qid = dev->tx_q[qid % netdev->n_txq].map;
1498
1499     if (OVS_UNLIKELY(!is_vhost_running(dev) || qid < 0
1500                      || !(dev->flags & NETDEV_UP))) {
1501         rte_spinlock_lock(&dev->stats_lock);
1502         dev->stats.tx_dropped+= cnt;
1503         rte_spinlock_unlock(&dev->stats_lock);
1504         goto out;
1505     }
1506
1507     rte_spinlock_lock(&dev->tx_q[qid].tx_lock);
1508
1509     cnt = netdev_dpdk_filter_packet_len(dev, cur_pkts, cnt);
1510     /* Check has QoS has been configured for the netdev */
1511     cnt = netdev_dpdk_qos_run__(dev, cur_pkts, cnt);
1512     dropped = total_pkts - cnt;
1513
1514     do {
1515         int vhost_qid = qid * VIRTIO_QNUM + VIRTIO_RXQ;
1516         unsigned int tx_pkts;
1517
1518         tx_pkts = rte_vhost_enqueue_burst(netdev_dpdk_get_vid(dev),
1519                                           vhost_qid, cur_pkts, cnt);
1520         if (OVS_LIKELY(tx_pkts)) {
1521             /* Packets have been sent.*/
1522             cnt -= tx_pkts;
1523             /* Prepare for possible retry.*/
1524             cur_pkts = &cur_pkts[tx_pkts];
1525         } else {
1526             /* No packets sent - do not retry.*/
1527             break;
1528         }
1529     } while (cnt && (retries++ <= VHOST_ENQ_RETRY_NUM));
1530
1531     rte_spinlock_unlock(&dev->tx_q[qid].tx_lock);
1532
1533     rte_spinlock_lock(&dev->stats_lock);
1534     netdev_dpdk_vhost_update_tx_counters(&dev->stats, pkts, total_pkts,
1535                                          cnt + dropped);
1536     rte_spinlock_unlock(&dev->stats_lock);
1537
1538 out:
1539     for (i = 0; i < total_pkts - dropped; i++) {
1540         dp_packet_delete(pkts[i]);
1541     }
1542 }

作業はここで実行されます。

    do {
        int vhost_qid = qid * VIRTIO_QNUM + VIRTIO_RXQ;
        unsigned int tx_pkts;

        tx_pkts = rte_vhost_enqueue_burst(netdev_dpdk_get_vid(dev),
                                          vhost_qid, cur_pkts, cnt);
        if (OVS_LIKELY(tx_pkts)) {
            /* Packets have been sent.*/
            cnt -= tx_pkts;
            /* Prepare for possible retry.*/
            cur_pkts = &cur_pkts[tx_pkts];
        } else {
            /* No packets sent - do not retry.*/
            break;
        }
    } while (cnt && (retries++ <= VHOST_ENQ_RETRY_NUM));

メソッド rte_vhost_enqueue_burst は、DPDK の vhost ライブラリーから直接取得されます。

[root@overcloud-compute-0 src]# grep rte_vhost_enqueue_burst dpdk-stable-16.11.4/ -R
dpdk-stable-16.11.4/doc/guides/prog_guide/vhost_lib.rst:* ``rte_vhost_enqueue_burst(vid, queue_id, pkts, count)``
dpdk-stable-16.11.4/doc/guides/rel_notes/release_16_07.rst:* The function ``rte_vhost_enqueue_burst`` no longer supports concurrent enqueuing
dpdk-stable-16.11.4/drivers/net/vhost/rte_eth_vhost.c:    nb_tx = rte_vhost_enqueue_burst(r->vid,
dpdk-stable-16.11.4/examples/tep_termination/vxlan_setup.c:    ret = rte_vhost_enqueue_burst(vid, VIRTIO_RXQ, pkts_valid, count);
dpdk-stable-16.11.4/examples/vhost/main.c:    ret = rte_vhost_enqueue_burst(dst_vdev->vid, VIRTIO_RXQ, &m, 1);
dpdk-stable-16.11.4/examples/vhost/main.c:    enqueue_count = rte_vhost_enqueue_burst(vdev->vid, VIRTIO_RXQ,
dpdk-stable-16.11.4/lib/librte_vhost/rte_vhost_version.map:    rte_vhost_enqueue_burst;
dpdk-stable-16.11.4/lib/librte_vhost/rte_virtio_net.h:uint16_t rte_vhost_enqueue_burst(int vid, uint16_t queue_id,
dpdk-stable-16.11.4/lib/librte_vhost/virtio_net.c:rte_vhost_enqueue_burst(int vid, uint16_t queue_id,

dpdk-stable-16.11.4/lib/librte_vhost/rte_virtio_net.h

/**
 * This function adds buffers to the virtio devices RX virtqueue. Buffers can
 * be received from the physical port or from another virtual device. A packet
 * count is returned to indicate the number of packets that were succesfully
 * added to the RX queue.
 * @param vid
 *  virtio-net device ID
 * @param queue_id
 *  virtio queue index in mq case
 * @param pkts
 *  array to contain packets to be enqueued
 * @param count
 *  packets num to be enqueued
 * @return
 *  num of packets enqueued
 */
uint16_t rte_vhost_enqueue_burst(int vid, uint16_t queue_id,
        struct rte_mbuf **pkts, uint16_t count);

dpdk-stable-16.11.4/lib/librte_vhost/virtio_net.c

uint16_t
rte_vhost_enqueue_burst(int vid, uint16_t queue_id,
        struct rte_mbuf **pkts, uint16_t count)
{
        struct virtio_net *dev = get_device(vid);

        if (!dev)
                return 0;

        if (dev->features & (1 << VIRTIO_NET_F_MRG_RXBUF))
                return virtio_dev_merge_rx(dev, queue_id, pkts, count);
        else
                return virtio_dev_rx(dev, queue_id, pkts, count);
}

これらのメソッドは両方とも、インスタンスにパケットを送信し、必要に応じて割り込み (書き込み) を介してインスタンスに通知します。

dpdk-stable-16.11.4/lib/librte_vhost/virtio_net.c

/**
 * This function adds buffers to the virtio devices RX virtqueue. Buffers can
 * be received from the physical port or from another virtio device. A packet
 * count is returned to indicate the number of packets that are succesfully
 * added to the RX queue. This function works when the mbuf is scattered, but
 * it doesn't support the mergeable feature.
 */
static inline uint32_t __attribute__((always_inline))
virtio_dev_rx(struct virtio_net *dev, uint16_t queue_id,
              struct rte_mbuf **pkts, uint32_t count)
(...)
        /* Kick the guest if necessary. */
        if (!(vq->avail->flags & VRING_AVAIL_F_NO_INTERRUPT)
                        && (vq->callfd >= 0))
                eventfd_write(vq->callfd, (eventfd_t)1);
        return count;
(...)
static inline uint32_t __attribute__((always_inline))
virtio_dev_merge_rx(struct virtio_net *dev, uint16_t queue_id,
        struct rte_mbuf **pkts, uint32_t count)
(...)

上記のメソッド virtio_dev_rx から:

 319         avail_idx = *((volatile uint16_t *)&vq->avail->idx);
 320         start_idx = vq->last_used_idx;
 321         free_entries = avail_idx - start_idx;
 322         count = RTE_MIN(count, free_entries);
 323         count = RTE_MIN(count, (uint32_t)MAX_PKT_BURST);
 324         if (count == 0)
 325                 return 0;

したがって、送信されるパケットの数は MIN([空きエントリーの数]、[MAX_PKT_BURST]、[呼び出しメソッドから渡され、送信されるパケットの数]) に設定します。

別のまれなケースでは、カウントがより低い値に設定され、for ループが中断されます。

 344         for (i = 0; i < count; i++) {
 345                 uint16_t desc_idx = desc_indexes[i];
 346                 int err;
 347
 348                 if (vq->desc[desc_idx].flags & VRING_DESC_F_INDIRECT) {
 349                         descs = (struct vring_desc *)(uintptr_t)gpa_to_vva(dev,
 350                                         vq->desc[desc_idx].addr);
 351                         if (unlikely(!descs)) {
 352                                 count = i;
 353                                 break;
 354                         }

最終的に、使用されるインデックスは count だけ増加します。

 378         *(volatile uint16_t *)&vq->used->idx += count;
 379         vq->last_used_idx += count;
 380         vhost_log_used_vring(dev, vq,
 381                 offsetof(struct vring_used, idx),
 382                 sizeof(vq->used->idx));

データは、次の方法でインスタンスのメモリーにコピーされます。

 48 #include "vhost.h"
(...)
363      err = copy_mbuf_to_desc(dev, descs, pkts[i], desc_idx, sz);

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.

Comments