《深入浅出DPDK》读书笔记(十三):DPDK虚拟化技术篇(加速包处理的vhost优化方案)

Table of Contents

加速包处理的vhost优化方案

142.vhost的演进和原理

143.Qemu与virtio-net

144.Linux内核态vhost-net

145.用户态vhost

146.基于DPDK的用户态vhost设计

147.消息机制

148.地址转换和映射虚拟机内存

149.vhost特性协商

150.virtio-net设备管理

(1)设备创建

(2)设置

(3)服务启动

(4)设备销毁

151.vhost中的Checksum和TSO功能卸载

DPDK vhost编程实例

152.DPDK vhost编程实例

153.报文收发接口介绍

154.使用DPDK vhost lib进行编程

155.使用DPDK vhost PMD进行编程

156.小结

系列文章

相关阅读


 

加速包处理的vhost优化方案


第11章主要介绍了virtio-net网络设备的前端驱动设计,本章将介绍其对应的后端驱动vhost设计。


142.vhost的演进和原理

virtio-net的后端驱动经历过从virtio-net后端,到内核态vhost-net,再到用户态vhost-user的演进过程。其演进的过程是对性能的追求,导致其架构的变化。


143.Qemu与virtio-net

用QEMU构建嵌入式LINUX系统

Linux虚拟化KVM-Qemu分析(一)

Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化

Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)

Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)

在CentOS上进行虚拟化:QEMU、Xen、KVM、LibVirt、oVirt

ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)

OpenVZ,Xen,KVM等:虚拟化解决方案

KVM Virtio: An I/O virtualization framework for Linux(Linux虚拟IO框架)

virtio-net后端驱动的最基本要素是虚拟队列机制、消息通知机制和中断机制。虚拟队列机制连接着客户机和宿主机的数据交互。消息通知机制主要用于从客户机到宿主机的消息通知。中断机制主要用于从宿主机到客户机的中断请求和处理。

图12-1是virtio-net后端模块进行报文处理的系统架构图。其中,KVM是负责为程序提供虚拟化硬件的内核模块,而Qemu利用KVM来模拟整个系统的运行环境,包括处理器和外设等;Tap则是内核中的虚拟以太网设备。

图12-1 vhost实现之前的Qemu virtio-net

当客户机发送报文时,它会利用消息通知机制(图12-1中通路2)),通知KVM,并退出到用户空间Qemu进程,然后由Qemu开始对Tap设备进行读写(图12-1中通路1))。

在这个模型中,由于宿主机、客户机和Qemu之间的上下文频繁切换带来的多次数据拷贝和CPU特权级切换,导致virtio-net性能不如人意。可以看出,性能瓶颈主要存在于数据通道和消息通知路径这两块:

  • 1)数据通道是从Tap设备到Qemu的报文拷贝和Qemu到客户机的报文拷贝,两次报文拷贝导致报文接收和发送上的性能瓶颈。
  • 2)消息通知路径是当报文到达Tap设备时内核发出并送到Qemu的通知消息,然后Qemu利用IOCTL向KVM请求中断,KVM发送中断到客户机。这样的路径带来了不必要的性能开销。

144.Linux内核态vhost-net

为了对上述报文收发性能瓶颈进行优化,Linux内核设计了vhost-net模块,目的是通过卸载virtio-net在报文收发处理上的工作,使Qemu从virtio-net的虚拟队列工作中解放出来,减少上下文切换和数据包拷贝,进而提高报文收发的性能。除此以外,宿主机上的vhost-net模块还需要承担报文到达和发送消息通知及中断的工作。

图12-2展现了加入Linux内核vhost-net模块后virtio-net模块进行报文处理的系统架构图。报文接收仍然包括数据通路和消息通知路径两个方面:

  • 1)数据通路是从Tap设备接收数据报文,通过vhost-net模块把该报文拷贝到虚拟队列中的数据区,从而使客户机接收报文。
  • 2)消息通路是当报文从Tap设备到达vhost-net时,通过KVM模块向客户机发送中断,通知客户机接收报文。

报文发送过程与之类似,此处不再赘述。

Linux内核态vhost-net的设计是建立在Qemu能共享如下信息的基础之上:

图12-2 virtio-net与Linux内核vhost-net

  • ❑Qemu共享在客户机上的内存空间的布局:vhost-net能够得到相应的地址转换的信息,主要是指客户机物理地址(GPA)到宿主机物理地址(HPA)的转换。
  • ❑Qemu共享虚拟队列的地址:vhost-net能直接对这些虚拟队列进行读写操作,从而进行报文的收发处理。由于虚拟队列的地址是Qemu进程上虚拟空间中的地址,实际使用时需要转换成vhost-net所在进程的虚拟地址。
  • ❑Qemu共享KVM中配置的用于向客户机上的virtio-net设备发送中断的事件文件描述符(eventfd):通过这种方式,vhost-net收到报文后可以通知客户机取走接收队列中的报文。
  • ❑Qemu共享KVM中配置的用于virtio-net PCI配置空间写操作触发的事件文件描述符:该描述符在virtio-net端口的PCI配置空间有写入操作时被触发,客户机可以在有报文需要发送时利用这种方式通知vhost-net。

145.用户态vhost

Linux内核态的vhost-net模块需要在内核态完成报文拷贝和消息处理,这会给报文处理带来一定的性能损失,因此用户态的vhost应运而生。用户态vhost采用了共享内存技术,通过共享的虚拟队列来完成报文传输和控制,大大降低了vhost和virtio-net之间的数据传输成本。

DPDK vhost是用户态vhost的一种实现,其实现原理与Linux内核态vhost-net类似,它实现了用户态API,卸载了Qemu在Virtio-net上所承担的虚拟队列功能,同样基于Qemu共享内存空间布局、虚拟化队列的访问地址和事件文件描述符给用户态的vhost,使得vhost能进行报文处理以及跟客户机通信。同时,由于报文拷贝在用户态进行,因此Linux内核的负担得到减轻。

DPDK vhost同时支持Linux virtio-net驱动和DPDK virtio PMD驱动的前端,其包含简易且轻量的2层交换功能以及如下基本功能:

  • ❑virtio-net网络设备的管理,包括virtio-net网络设备的创建和virtio-net网络设备的销毁。
  • ❑虚拟队列中描述符列表、可用环表和已用环表在vhost所在进程的虚拟地址空间的映射和解除映射,以及实际报文数据缓冲区在vhost所在进程的虚拟地址空间的映射和解除映射。
  • ❑当收到报文时,触发发送到客户机的消息通知;当发送报文时,接收来自客户机的消息通知。
  • ❑virtio-net设备间(虚拟队列)以及其与物理设备间(网卡硬件队列)的报文交换。可用VMDQ机制来对数据包进行分类和排序,避免软件方式的报文交换,从而减少报文交换的成本。
  • ❑virtio-net网络后端的实现以及部分新特性的实现,如合并缓冲区实现巨帧的接收,虚拟端口上多队列机制等。

146.基于DPDK的用户态vhost设计

DPDK vhost支持vhost-cuse(用户态字符设备)和vhost-user(用户态socket服务)两种消息机制,它负责为客户机中的virtio-net创建、管理和销毁vhost设备。前者是一个过渡性技术,这里着重介绍目前通用的vhost-user方式。


147.消息机制

当使用vhost-user时,首先需要在系统中创建一个Unix domain socket server,用于处理Qemu发送给vhost的消息,其消息机制如图12-3所示。

图12-3 vhost后端和Qemu消息机制

如果有新的socket连接,说明客户机创建了新的virtio-net设备,因此vhost驱动会为之创建一个vhost设备,如果Qemu发给vhost的消息中已经包含有socket文件描述符,说明该Unix domain socket已创建,因此该描述符可以直接被vhost进程使用。

最后,当socket连接关闭时,vhost会销毁相应的设备。

常用消息如下:

  • VHOST_GET_FEATURES:返回vhost所能支持的virtio-net功能子集。
  • VHOST_SET_FEATURES:检查功能掩码,设置vhost和前端virtio-net所共同支持的特性,任何特性只有二者同时支持的情况下才真正有效。
  • VHOST_SET_OWNER:将设备设置为当前进程所有。
  • VHOST_RESET_OWNER:当前进程释放对该设备的所有权。
  • VHOST_SET_MEM_TABLE:设置内存空间布局信息,用于在报文收发时进行数据缓冲区地址转换。
  • VHOST_SET_LOG_BASE/VHOST_SET_LOG_FD:该消息可用于客户机在线迁移。
  • VHOST_SET_VRING_NUM:vhost记录每个虚拟队列(包括接收队列和发送队列)的大小信息。
  • VHOST_SET_VRING_ADDR:这个消息在Qemu地址空间里发送Virtqueue结构的虚拟地址。vhost将该地址转换成vhost的虚拟地址空间。它使用VHOST_SET_VRING_NUM的消息确定描述符队列、AVAIL队列、USED队列的大小(通常每个队列分配一页的大小)。
  • VHOST_SET_BASE:这个消息传递初始索引值,vhost根据该索引值找到可用的描述符。vhost同时记录该索引值并设置成当前位置。
  • VHOST_GET_BASE:这个消息将返回vhost当前的索引值,即vhost目前期望找到可用的描述符的地方。
  • VHOST_SET_VRING_KICK:这个消息传递eventfd文件描述符。当客户端有新的数据包需要发送时,通过该文件描述符通知vhost接收新的数据包并发送到目的地。vhost使用eventfd代理模块把这个eventfd文件描述符从Qemu上下文映射到它自己的进程上下文中。
  • VHOST_SET_VRING_CALL:这个消息同样传递eventfd文件描述符,使vhost能够在完成对新的数据包接收时,通过中断的方式来通知客户机,准备接收新的数据包。vhost使用eventfd代理模块把这个eventfd文件描述符从Qemu上下文映射到它自己的进程上下文中。
  • VHOST_USER_GET_VRING_BASE:这个消息将虚拟队列的当前可用索引值发送给Qemu。

148.地址转换和映射虚拟机内存

Qemu支持一个参数选项(mem-path),用于传送目录/文件系统,Qemu在该文件系统中分配所需的内存空间。因此,必须保证宿主机上有足够的大页空间,同时总是需要指定内存预分配(mem-prealloc)。

为了vhost能访问虚拟队列和数据包缓冲区,所有的虚拟队列中的描述符表、可用环表和已用环表的地址,其所在的的页面必须被映射到vhost的进程空间中。

vhost收到Qemu发送的VHOST_SET_MEM_TABLE消息后,使用消息中的内存分布表(文件描述符、地址偏移、块大小等信息),将Qemu的物理内存映射到自己的虚拟内存空间。

这里有如下几个概念需要描述。

Guest的物理地址(GPA):客户机的物理地址,如虚拟队列中的报文缓冲区的地址,可以被认为是一个基于上述系统函数MMAP返回起始地址的偏移量。

Qemu地址空间虚拟地址(QVA):当Qemu发送VHOST_SET_VRING_ADDR消息时,它传递虚拟队列在Qemu虚拟地址空间中的位置。

vhost地址空间虚拟地址(VVA):要查找虚拟队列和存储报文的缓存在vhost进程的虚拟地址空间地址,必须将Qemu虚拟地址和Guest物理地址转换成vhost地址空间的虚拟地址。

在DPDK的实现中,使用virtio_memory数据结构存储Qemu内存文件的区域信息和映射关系。其中,区域信息使用virtio_memory_regions数据结构进行存储。

/** 
* Information relating to memory regions including offsets 
* to addresses in QEMUs memory file. 
*/ 
struct virtio_memory_regions { 
  /**< Base guest physical address of region. */ 
  uint64_t     guest_phys_address; 
  /**< End guest physical address of region. */ 
  uint64_t     guest_phys_address_end; 
  /**< Size of region. */ 
  uint64_t     memory_size; 
  /**< Base userspace address of region. */ 
  uint64_t     userspace_address; 
  /**< Offset of region for address translation. */ 
  uint64_t     address_offset; 
}; 
/** 
* Memory structure includes region and mapping information. 
*/ 
struct virtio_memory { 
  /**< Base QEMU userspace address of the memory file. */ 
  uint64_t     base_address; 
  /**< Mapped address of memory file base in our applications memory space. */ 
  uint64_t     mapped_address; 
  /**< Total size of memory file. */ 
  uint64_t     mapped_size; 
  /**< Number of memory regions. */ 
  uint32_t     nregions; 
  /**< Memory region information. */ 
  struct virtio_memory_regions        regions[0]; 
}; 

通过这两个数据结构,DPDK就可以通过地址偏移计算出客户机物理地址或Qemu虚拟地址在vhost地址空间的虚拟地址。

struct virtio_memory_regions *region; 
vhost_va = region->address_offset + guest_pa; 
vhost_va = qemu_va + region->guest_phys_address + 
            region->address_offset - 
            region->userspace_address; 

149.vhost特性协商

在设备初始化时,客户机virtio-net前端驱动询问vhost后端所支持的特性。当其收到回复后,将代表vhost特性的字段与自身所支持特性的字段进行与运算,确定二者共同支持的特性,并将最终可用的特性集合发送给vhost。

如下是DPDK vhost支持的特性集合:

  • VIRTIO_NET_F_HOST_TSO4:宿主机支持TSO V4。
  • VIRTIO_NET_F_HOST_TSO6:宿主机支持TSO V6。
  • VIRTIO_NET_F_CSUM:宿主机支持校验和。
  • VIRTIO_NET_F_MRG_RXBUF:宿主机可合并收包缓冲区。
  • VHOST_SUPPORTS_MQ:支持虚拟多队列。
  • VIRTIO_NET_F_CTRL_VQ:支持控制通道。
  • VIRTIO_NET_F_CTRL_RX:支持接收模式控制通道。
  • VHOST_USER_F_PROTOCOL_FEATURES:支持特性协商。
  • VHOST_F_LOG_ALL:用于vhost动态迁移。

150.virtio-net设备管理

一个virtio-net设备的生命周期包含设备创建、配置、服务启动和设备销毁四个阶段。

(1)设备创建

vhost-user通过建立socket连接来创建。

当创建一个virtio-net设备时,需要:

  • ❑分配一个新的virtio-net设备结构,并添加到virtio-net设备链表中。
  • ❑分配一个为virtio-net设备服务的处理核并添加virtio-net设备到数据面的链表中。
  • ❑在vhost上分配一个为virtio-net设备服务的RX / TX队列。

(2)设置

利用VHOST_SET_VRING_*消息通知vhost虚拟队列的大小、基本索引和位置,vhost将虚拟队列映射到它自己的虚拟地址空间。

(3)服务启动

vhost-user利用VHOST_USER_SET_VRING_KICK消息来启动虚拟队列服务。之后,vhost便可以轮询其接收队列,并将数据放在virtio-net设备接收队列上。同时,也可轮询发送虚拟队列,查看是否有待发送的数据包,若有,则将其复制到发送队列中。

(4)设备销毁

vhost-user利用VHOST_USER_GET_VRING_BASE消息来通知停止提供对接收和发送虚拟队列的服务。收到消息后,vhost会立即停止轮询传输虚拟队列,还将停止轮询网卡接收队列。同时,分配给virtio-net设备的处理核和物理网卡上的RX / TX队列也将被释放。


151.vhost中的Checksum和TSO功能卸载

为了降低高速网络系统对CPU的消耗,现代网卡大多都支持多种功能卸载技术,如第9章所述。其中,较为重要的两种功能是Checksum(校验和)的计算和TSO(TCP分片卸载)。

Checksum(校验和)被广泛应用于网络协议中,用于检验消息在传递过程中是否发生错误。如果网卡支持Checksum功能的卸载,则Checksum的计算可以在网卡中完成,而不需要消耗CPU资源。

TSO(TCP Segmentation Off load, TCP分片卸载)技术利用网卡的处理能力,将上层传来的TCP大数据包分解成若干个小的TCP数据包,完成添加IP包头、复制TCP协议头并针对每一个小包计算校验和等工作。因此,如果网卡不支持TSO,则TCP软件协议层在向IP层发送数据包时会考虑MSS(Maximum Segment Size,最大分片大小),将较大的数据分成多个包进行发送,从而带来更多CPU负载。

在DPDK vhost的实现中,为了避免给虚拟机带来额外的CPU负载,同样可以对Checksum卸载和TSO进行支持。

由于数据包通过virtio从客户机到宿主机是用内存拷贝的方式完成的,期间并没有通过物理网络,因此不存在产生传输错误的风险,也不需要考虑MSS如何对大包进行分片。因此,vhost中的Checksum卸载和TSO的实现只需要在特性协商时告诉虚拟机这些特性已经被支持。之后,在虚拟机virtio-net发送数据包时,在包头中标注该数据包的Checksum和TCP分片的工作需要在vhost端完成。最后,当vhost收到该数据包时,修改包头,标注这些工作已经完成。


DPDK vhost编程实例

152.DPDK vhost编程实例

DPDK的vhost有两种封装形式:vhost lib和vhost PMD。vhost lib实现了用户态的vhost驱动供vhost应用程序调用,而vhost PMD则对vhost lib进行了封装,将其抽象成一个虚拟端口,可以使用标准端口的接口来进行管理和报文收发。

vhost lib和vhost PMD在性能上并无本质区别,不过vhost lib可以提供更多的函数功能来供使用,而vhost PMD受制于抽象层次,不能直接对非标准端口功能的函数进行封装。为了使用vhost lib的所有功能,保证其使用灵活性和功能完备性,vhost PMD提供了以下两种方式。

  • 1)添加了回调函数:如果使用老版本vhost lib的程序需要在新建或销毁设备时进行额外的操作,可使用新增的回调函数来完成。
  • 2)添加了帮助函数:帮助函数可以将端口号转换成virtio-net设备指针,这样便可以通过这个指针来调用vhost lib中的其他函数。

153.报文收发接口介绍

在使用vhost lib进行编程时,使用如下函数进行报文收发:

/*  This  function  get  guest  buffers  from  the  virtio  device  TX  virtqueue  for 
processing. */ 
uint16_t  rte_vhost_dequeue_burst(struct  virtio_net  *dev,  uint16_t  queue_id, 
                    struct rte_mempool *mbuf_pool, struct rte_mbuf **pkts, uint16_t count); 
/* This function adds buffers to the virtio devices RX virtqueue. */ 

uint16_t  rte_vhost_enqueue_burst(struct  virtio_net  *dev,  uint16_t  queue_id, 
                    struct rte_mbuf **pkts, uint16_t count); 

而vhost PMD可以使用如下接口函数:

static  inline  uint16_t  rte_eth_rx_burst(uint8_t  port_id,  uint16_t  queue_id, 
                                struct rte_mbuf **rx_pkts, const uint16_t nb_pkts); 
static  inline  uint16_t  rte_eth_tx_burst(uint8_t  port_id,  uint16_t  queue_id, 
                                struct rte_mbuf **tx_pkts, uint16_t nb_pkts); 

该接口会通过端口号查找设备指针,并最终调用设备所提供的收发函数:

struct rte_eth_dev *dev = &rte_eth_devices[port_id]; 
(*dev->rx_pkt_burst)(dev->data->rx_queues[queue_id], rx_pkts, nb_pkts); 
(*dev->tx_pkt_burst)(dev->data->tx_queues[queue_id], tx_pkts, nb_pkts); 

vhost PMD设备所注册的收发函数如下:

static uint16_t eth_vhost_rx(void *q, struct rte_mbuf **bufs, uint16_t nb_bufs); 
static uint16_t eth_vhost_tx(void *q, struct rte_mbuf **bufs, uint16_t nb_bufs); 

它们分别对rte_vhost_dequeue_burst和rte_vhost_enqueue_burst进行了封装。

本章将介绍两个编程实例,它们分别使用vhost lib和vhost PMD进行报文转发。


154.使用DPDK vhost lib进行编程

在DPDK所包含的示例程序中,vhost-switch是基于vhost lib的一个用户态以太网交换机的实现,可以完成在virtio-net设备和物理网卡之间的报文交换。

实例中还使用了虚拟设备队列(VMDQ)技术来减少交换过程中的软件开销,该技术在网卡上实现了报文处理分类的任务,大大减轻了处理器的负担。

该实例包含配置平面和数据平面。在运行时,vhost-switch需要至少两个处理器核心:一个用于配置平面,另一个用于数据平面。为了提高性能,可以为数据平面配置多个处理核。

配置平面主要包含下面这些服务。

  • ❑virtio-net设备管理:virtio-net设备创建和销毁以及处理核的关联。
  • ❑vhost API实现:虚拟主机API的实现。
  • ❑物理网卡的配置:为一个virtio-net设备配置MAC/ VLAN(VMDQ)滤波器到绑定的物理网卡上。

数据平面的每个处理核对绑定在其上的所有vhost设备进行轮询操作,轮询该设备所对应的VMDQ接收队列。如有任何数据包,则接收并将其放到该vhost设备的接收虚拟队列上。同时,处理核也将轮询相应virtio-net设备的虚拟发送队列,如有数据需要发送,则把待发送数据包放到物理网卡的VMDQ传输队列中。

在完成vhost驱动的注册后,即可通过调用vhost lib中的rte_vhost_dequeue_burst和rte_vhost_enqueue_burst进行报文的接收和发送。

其核心交换代码如下(基于DPDK 2.1.0):

while (dev_ll ! = NULL) { 
    /* 查找得到Virtio-net设备 */ 
    vdev = dev_ll->vdev; 
    dev = vdev->dev; 
    /* 检查设备有效性 */ 
    if (unlikely(vdev->remove)) { 
    dev_ll = dev_ll->next; 
    unlink_vmdq(vdev); 
    vdev->ready = DEVICE_SAFE_REMOVE; 
    continue; 
    } 
    if (likely(vdev->ready == DEVICE_RX)) { 
        /* 从接收端口接收数据包 */ 
        rx_count = rte_eth_rx_burst(ports[0], 
        vdev->vmdq_rx_q, pkts_burst, MAX_PKT_BURST); 
        if (rx_count) { 
            /* 若虚拟队列条目不够,为避免丢包,等待后尝试重发 */ 
            if (enable_retry && unlikely(rx_count > rte_vring_available_entries(dev, VIRTIO_RXQ))) { 
                for (retry = 0; retry < burst_rx_retry_num; retry++) { 
                    rte_delay_us(burst_rx_delay_time); 
                    if (rx_count <= rte_vring_available_entries(dev, VIRTIO_RXQ)) 
                        break; 
                } 
            } 
            /* 调用vhost lib中的enqueue函数,将报文发送到客户机 */ 
            ret_count = rte_vhost_enqueue_burst(dev, VIRTIO_RXQ, pkts_burst, rx_count); 
            if (enable_stats) { 
                rte_atomic64_add( &dev_statistics[dev_ll->vdev->dev->device_fh].rx_total_atomic, rx_count); 
                rte_atomic64_add( &dev_statistics[dev_ll->vdev->dev->device_fh].rx_atomic, ret_count); 
            } 
            /* 释放缓存区 */ 
            while (likely(rx_count)) { 
                rx_count--; 
                rte_pktmbuf_free(pkts_burst[rx_count]); 
            } 
        } 
    } 
    if (likely(! vdev->remove)) { 
        /* 调用vhost lib中的dequeue函数,从客户机接收报文 */ 
        tx_count = rte_vhost_dequeue_burst(dev, VIRTIO_TXQ, mbuf_pool, pkts_burst,  MAX_PKT_BURST); 
        /* 如果首次收到该MAC则进行MAC学习,并设置VMDQ */ 
        if (unlikely(vdev->ready == DEVICE_MAC_LEARNING) && tx_count) { 
            if (vdev->remove —— (link_vmdq(vdev, pkts_burst[0]) == -1)) { 
            while (tx_count) 
                rte_pktmbuf_free(pkts_burst[--tx_count]); 
            } 
        } 
        /* 将报文转发到对应的端口 */ 
        while (tx_count) 
        virtio_tx_route(vdev, pkts_burst[--tx_count], (uint16_t)dev->device_fh); 
    } 
    /* 开始处理下一个设备 */ 
    dev_ll = dev_ll->next; 
} 

155.使用DPDK vhost PMD进行编程

如果使用vhost PMD进行报文收发,由于使用了标准端口的接口,因此函数的调用过程相对简单。

首先,需要注册vhost PMD驱动,其数据结构如下:

static struct rte_driver pmd_vhost_drv = { 
    .name = "eth_vhost", 
    .type = PMD_VDEV, 
    .init = rte_pmd_vhost_devinit, 
    .uninit = rte_pmd_vhost_devuninit, 
}; 

rte_pmd_vhost_devinit()调用eth_dev_vhost_create()来注册网络设备并完成所需数据结构的分配。其中,网络设备的数据结构rte_eth_dev定义如下:

struct rte_eth_dev { 
    eth_rx_burst_t rx_pkt_burst; /**< Pointer to PMD receive function. */ 
    eth_tx_burst_t tx_pkt_burst; /**< Pointer to PMD transmit function. */ 
    struct rte_eth_dev_data *data;   /**< Pointer to device data */ 
    const struct eth_driver *driver; /**< Driver for this device */ 
    const struct eth_dev_ops *dev_ops; /**< Functions exported by PMD */ 
    struct rte_pci_device *pci_dev; /**< PCI info. supplied by probing */ 
    /** User application callbacks for NIC interrupts */ 
    struct rte_eth_dev_cb_list link_intr_cbs; 
    /** 
      * User-supplied functions called from rx_burst to post-process 
      * received packets before passing them to the user 
      */ 
    struct rte_eth_rxtx_callback *post_rx_burst_cbs[RTE_MAX_QUEUES_PER_PORT]; 
    /** 
      * User-supplied functions called from tx_burst to pre-process 
      * received packets before passing them to the driver for transmission. 
      */ 
    struct rte_eth_rxtx_callback *pre_tx_burst_cbs[RTE_MAX_QUEUES_PER_PORT]; 
    uint8_t attached; /**< Flag indicating the port is attached */ 

    enum rte_eth_dev_type dev_type; /**< Flag indicating the device type */ 
}; 

rx_pkt_burst和tx_pkt_burst即指向该设备用于接收和发送报文的函数,在vhost PMD设备中注册如下:

eth_dev->rx_pkt_burst = eth_vhost_rx; 
eth_dev->tx_pkt_burst = eth_vhost_tx; 

完成设备的注册后,操作vhost PMD的端口与操作任何物理端口并无区别。如下代码即可完成一个简单的转发过程。

struct fwd_stream { 
    portid_t    rx_port;    /* 接收报文的端口 */ 
    queueid_t  rx_queue;  /* 接收报文的队列 */ 
    portid_t    tx_port;    /* 发送报文的端口 */ 
    queueid_t  tx_queue;  /* 发送报文的队列 */ 
}; 
struct fwd_stream *fs; 
/* 从接收端口接收报文 */ 
nb_rx = rte_eth_rx_burst(fs->rx_port, fs->rx_queue, pkts_burst, 
            nb_pkt_per_burst); 
if (unlikely(nb_rx == 0)) 
    return; 
/* 从发送端口发送报文 */ 
nb_tx = rte_eth_tx_burst(fs->tx_port, fs->tx_queue, pkts_burst, nb_rx); 
/* 若发送失败,则释放缓存区 */ 
if (unlikely(nb_tx < nb_rx)) { 
    do { 
        rte_pktmbuf_free(pkts_burst[nb_tx]); 
    } while (++nb_tx < nb_rx); 
} 

最终rte_eth_rx_burst和rte_eth_tx_burst通过设备的指针调用设备的rx_pkt_burst和tx_pkt_burst。


156.小结

virtio半虚拟化的性能优化不能仅仅只优化前端virtio或后端vhost,还需要两者同时优化,才能更好地提升性能。本章先介绍后端vhost演进之路,分析了各自架构的优缺点。然后重点介绍了DPDK在用户态vhost的设计思路以及优化点。最后,对如何使用DPDK进行vhost编程给出了简要示例。


系列文章

《深入浅出DPDK》读书笔记(一):基础部分知识点

《深入浅出DPDK》读书笔记(二):网卡的读写数据操作

《深入浅出DPDK》读书笔记(三):NUMA - Non Uniform Memory Architecture 非统一内存架构

《深入浅出DPDK》读书笔记(四):并行计算-SIMD是Single-Instruction Multiple-Data(单指令多数据)

《深入浅出DPDK》读书笔记(五):同步互斥机制

《深入浅出DPDK》读书笔记(六):报文转发(run to completion、pipeline、精确匹配算法、最长前缀匹配LPM)

《深入浅出DPDK》读书笔记(七):PCIe与包处理I/O

《深入浅出DPDK》读书笔记(八):网卡性能优化(异步中断模式、轮询模式、混和中断轮询模式)

《深入浅出DPDK》读书笔记(九):流分类与多队列

《深入浅出DPDK》读书笔记(十):硬件加速与功能卸载(VLAN、IEEE1588、IP TCP/UDP/SCTP checksum、Tunnel)

《深入浅出DPDK》读书笔记(十一):DPDK虚拟化技术篇(I/O虚拟化、CPU虚拟化、内存虚拟化、VT-d、I/O透传)

《深入浅出DPDK》读书笔记(十二):DPDK虚拟化技术篇(半虚拟化Virtio)

《深入浅出DPDK》读书笔记(十三):DPDK虚拟化技术篇(加速包处理的vhost优化方案)

 


相关阅读

DPDK PMD( Poll Mode Driver)轮询模式驱动程序

DPDK笔记 RSS(receive side scaling)网卡分流机制

网卡多队列:RPS、RFS、RSS、Flow Director(DPDK支持)

Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO

ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)

KVM Virtio: An I/O virtualization framework for Linux(Linux虚拟IO框架)

用QEMU构建嵌入式LINUX系统

Linux虚拟化KVM-Qemu分析(一)

Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化

Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)

Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)

在CentOS上进行虚拟化:QEMU、Xen、KVM、LibVirt、oVirt

ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)

OpenVZ,Xen,KVM等:虚拟化解决方案

KVM Virtio: An I/O virtualization framework for Linux(Linux虚拟IO框架)

已标记关键词 清除标记
相关推荐
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页