Table of Contents
138.基于DPDK用户空间的Virtio网络设备驱动设计以及性能优化
半虚拟化Virtio
131.Virtio是一种半虚拟化的设备抽象接口规范,最先由Rusty Russell开发,他当时的目的是支持自己的虚拟化解决方案lguest。后来Virtio在Qemu和KVM中得到了更广泛的使用,也支持大多数客户操作系统,例如Windows和Linux等。在客户机操作系统中实现的前端驱动程序一般直接叫Virtio,在宿主机实现的后端驱动程序目前常用的叫vhost。与宿主机纯软件模拟I/O(如e1000、rtl8139)设备相比,virtio可以获得很好的I/O性能。但其缺点是必须要客户机安装特定的virtio驱动使其知道是运行在虚拟化环境中。本章下面介绍的就是Virtio的基本原理和前端驱动,vhost将在下一章介绍。
132.Virtio使用场景
现代数据中心中大量采用虚拟化技术,设备的虚拟化是其中重要的一环。由于设备种类繁多,不同厂家的产品对各种特性的支持也各不一样。一般来说,数据中心使用一款设备,首先要安装该设备的驱动程序,然后根据该设备的特性对数据中心应用做一定的定制开发,运维阶段的流程和问题处理也可能会和设备的特性紧密相关。Virtio作为一种标准化的设备接口,主流的操作系统和应用都逐渐加入了对Virtio设备的直接支持,这给数据中心的运维带来了很多方便。
Virtio同I/O透传技术相比,目前在网络吞吐率、时延以及抖动上尚不具有优势,相关的优化工作正在进行当中。I/O透传的一个典型问题是从物理网卡接收到的数据包将直接到达客户机的接收队列,或者从客户机发送队列发出的包将直接到达其他客户机(比如同一个PF的VF)的接收队列或者直接从物理网卡发出,绕过了宿主机的参与;但在很多应用场景下,有需求要求网络包必须先经过宿主机的处理(如防火墙、负载均衡等),再传递给客户机。另外,I/O透传技术不能从硬件上支持虚拟机的动态迁移以及缺乏足够灵活的流分类规则。
图11-1是数据中心使用Virtio设备的一种典型场景。宿主机使用虚拟交换机连通物理网卡和虚拟机。虚拟交换机内部有一个DPDK Vhost,实现了Virtio的后端网络设备驱动程序逻辑。虚拟机里有DPDK的Virtio前端网络设备驱动。前端和后端通过Virtio的虚拟队列交换数据。这样虚拟机里的网络数据便可以发送到虚拟交换机中,然后经过转发逻辑,可以经由物理网卡进入外部网络。
图11-1 Virtio典型场景
133.Virtio规范和原理
Virtio规范主要有两个版本,0.95和1.0,其规定的实现接口有PCI、MMIO(内存映射)和Channel IO方式,而Channel IO方式是1.0规范中新加的。详细内容请参考[Ref11-1]和[Ref11-2]。PCI是现代计算机系统中普遍使用的一种总线接口,最新的规范是PCI-e,在第6章有详细介绍。在一些系统(例如嵌入式系统中),可能没有PCI接口,Virtio可使用内存映射方式。IBM S/390的虚拟系统既不支持PCI接口也不支持内存映射方式,只能使用特有的Channel IO方式。DPDK目前只支持Virtio PCI接口方式。
1.0规范也兼容以前的0.95规范,并把以前规范所定义的称为传统(Legacy)模式,而1.0中新的规范称为现代(modern)模式。现在使用的最广泛的还是PCI的传统模式。在Linux Kernel 4.0以后,PCI现代模式也得到了比较好的支持。现代和传统模式的PCI设备参数和使用方式都有比较大的差别,但在Linux Kernel 4.0中Virtio驱动侧是实现在同一个驱动程序中。驱动程序会根据Qemu模拟的PCI设备是传统还是现代模式而自动加载相应的驱动逻辑。详见[Ref11-3]。
virtio在PCI(传输层)的结构之上还定义了Virtqueue(虚拟队列)接口,它在概念上将前端驱动程序连接到后端驱动程序。驱动程序可以使用1个或多个队列,具体数量取决于需求。例如,Virtio网络驱动程序使用两个虚拟队列(一个用于接收,另一个用于发送),而Virtio块驱动程序则使用一个虚拟队列。
Virtio用PCI接口实现时,宿主机会使用后端驱动程序模拟一个PCI的设备,并将这个设备添加在虚拟机配置中。下面就主要以广泛使用的传统模式的PCI设备为例详细解释Virtio的整体架构和原理,部分地方也对照介绍现代模式。为叙述简便起见,本章下面如果没有特别指定的话,驱动就代表Virtio的前端驱动,而设备就代表Virtio后端驱动(如Vhost)所模拟的PCI的设备。
11.2.1 设备的配置
1. 设备的初始化
设备的初始化共有以下五个步骤。初始化成功后,设备就可以使用了。
- 1)手工重启设备状态,或者是设备上电时的自动重启后,系统发现设备。
- 2)客户机操作系统设置设备的状态为Acknowlege,表示当前已经识别到设备。
- 3)客户机操作系统设置设备的状态为Driver,表明客户操作系统已经找到合适的驱动程序。
- 4)设备驱动的安装和配置:进行特性列表的协商,初始化虚拟队列,可选的MSI-X的安装,设备专属的配置等。
- 5)设置设备状态为Driver_OK,或者如果中途出现错误,则为Failed。
2. 设备的发现
1.0规范中定义了18种Virtio设备,如表11-1所示。其中的Virtio Device ID表示的是Virtio规范中的设备编号,在每一种具体的接口架构底层实现中,例如PCI方式,可能会有各自特有的设备类型号,例如PCI设备编号。Virtio设备编号不一定等于PCI设备编号等具体实现的设备编号,但会有一定的对应关系。
表11-1 Virtio Device ID
PCI方式的Virtio设备和普通的PCI设备一样,使用标准PCI配置空间和I/O区域。Virtio设备的PCI厂商编号(Vendor ID)为0x1AF4, PCI设备编号(Device ID)范围为0x1000~0x107F。其中,0x1000~0x103F用于传统模式设备,0x1040~0x107F用于现代模式设备。例如,PCI设备编号0x1000代表的是传统模式Virtio网卡,而0x1041代表的是现代模式Virtio网卡,对应的都是Virtio Device ID等于1的网卡设备。
3. 传统模式virtio的配置空间
传统模式使用PCI设备的BAR0来对PCI设备进行配置,配置参数如表11-2所示。
表11-2 传统模式Virtio设备配置空间:通用配置
如果传统设备配置了MSI-X(Message Signaled Interrupt-Extended)中断,则在上述Bits后添加了两个域,如表11-3所示。
表11-3 传统模式Virtio设备配置空间:MSI-X附加配置
紧接着,这些通常的Virtio参数可能会有指定设备(例如网卡)专属的配置参数,见表11-4所示。
表11-4 传统模式Virtio设备的配置空间:设备专属配置
4. 现代模式Virtio的配置空间
和传统设备固定使用BAR0不同,现代设备通过标准的PCI配置空间中的能力列表(capability list),可以指定配置信息的存储位置(使用哪个BAR,从BAR空间开始的偏移地址等)。1.0规范中定义了4种配置信息:通用配置(Common conf iguration)、提醒(Notif ications)、中断服务状态(ISR Status)、设备专属配置(Device-specif ic conf iguration)。
图11-2所列的是现代设备配置空间的通用配置,与表11-2传统设备对比,可以看出新的配置在特性的协商、队列大小的设置等都有了增强。而且传统设备表11-3的MSI-X附加配置也包含在其中。
图11-2 现代模式Virtio设备配置空间:通用配置
下面具体解释一下几个关键的参数:
(1)设备状态
当Virtio驱动初始化一个Virtio设备时,可以通过设备状态来反映进度。下面是传统设备中定义的5种状态:
- 0:驱动写入0表示重启该设备。
- 1:Acknowledge,表明客户操作系统发现了一个有效的Virtio设备。
- 2:Driver,表明客户操作系统找到了合适的驱动程序(例如,Virtio网卡驱动)。
- 4:Driver_OK,表示驱动安装成功,设备可以使用。
- 128:FAILED,在安装驱动过程中出错。
现代设备又添加了两种:
- 8:FEATURES_OK,表示驱动程序和设备特性协商成功。
- 64:DEVICE_NEEDS_RESET,表示设备遇到错误,需要重启。
(2)特性列表
设备和驱动都有单独的特性列表,现代设备特性列表有64字位,传统设备只支持32字位。通过特性列表,设备和驱动都能提供自己支持的特性集合。设备在初始化过程中,驱动程序读取设备的特性列表,然后挑选其中自己能够支持的作为驱动的特性列表。这就完成了驱动和设备之间的特性协商。
特性列表的字位安排如下:
- 0~23:具体设备的特性列表,每一种设备有自己的特性定义。例如,网卡定义了24个特性,如VIRTIO_NET_F_CSUM使用字位0,表示是否支持发送端校验和卸载,而VIRTIO_NET_F_GUEST_CSUM使用字位1,表示是否支持接收端校验和卸载等。
- 24~32:保留位,用于队列和特性协商机制的扩展。例如VIRTIO_F_RING_INDIRECT_DESC使用字位28,表示驱动是否支持间接的描述表。
- 33~:保留位,用于将来扩充(只有现代设备支持)。
(3)中断配置
现代设备和传统设备都支持两种中断源(设备中断和队列中断)和两种中断方式(INTx和MSI-X)。每个设备中设备中断源只有一个,队列中断源则可以每个队列一个。但具体有多少个中断还取决于中断方式。INTx方式下,一个设备只支持一个中断,所以设备中断源和队列中断源必须共享这一个中断。MSI-X支持多个中断,每个单独中断也称为中断向量。假设有n个队列,则设备可以有n个队列中断,加上一个设备中断,总共有n+1个中断。这n+1个中断还可以灵活配置,其中任意一个中断源都可以配置使用其中任意一个中断向量。
INTx现在使用的已经比较少了,新的系统一般都支持更为强大的MSI-X方式。下面就介绍MSI-X的相关设置。
传统设备中,设备启用MSI-X中断后,就可以使用表11-3所示的MSI-X附加配置的两个寄存器把设备和队列中断源映射到对应的MSI-X中断向量(对应Conf iguration Vector和Queue Vector)。这两个寄存器都是16字位,可读写的。通过写入有效的中断向量值(有效值范围:0x0~0x7FF)来映射中断,设备或队列有了中断后,便会通过这个中断向量通知驱动。写入VIRTIO_MSI_NO_VECTOR(0xFFFF)则会关闭中断,取消映射。
读取这两个寄存器则返回映射到指定中断源上的中断向量。如果是没有映射,则返回VIRTIO_MSI_NO_VECTOR。
现代设备中,这两个寄存器直接包含在通用配置里,用法和传统设备类似。
映射一个中断源到中断向量上需要分配资源,可能会失败,此时读取寄存器的值,返回VIRTIO_MSI_NO_VECTOR。当映射成功后,驱动必须读取这些寄存器的值来确认映射成功。如果映射失败的话,可以尝试映射较少的中断向量或者关闭MSI-X中断。
(4)设备的专属配置
此配置空间包含了特定设备(例如网卡)专属的一些配置信息,可由驱动读写。
以网卡设备为例,传统设备定义了MAC地址和状态信息,现代设备增加了最大队列数信息。这种专属的配置空间和特征位的使用扩展了设备的特性功能。
134.虚拟队列的配置
虚拟队列(Virtqueue)是连接客户机操作系统中Virtio设备前端驱动和宿主机后端驱动的实际数据链路,示意图如图11-3所示。
虚拟队列主要由描述符列表(descriptor table)、可用环表(available ring)和已用环表(used ring)组成。描述符列表指向的是实际要传输的数据。两个环表指向的是描述符列表,分别用来标记前端和后端驱动对描述符列表中描述符的处理进度。
在传统网卡设备中,对描述符的处理进度,一般用两个指针就可以标记:前端指针指向网卡驱动在描述符列表的处理位置,后端指针指向网卡设备处理的位置。例如,刚开始时,前端指针和后端指针都为0;网卡驱动请求网卡设备发送n个网络包,将相关的网络包数据缓冲区地址填充到前端指针指向的描述符0开始的n个描述符中,然后更新前端指针为n;网卡设备看见前端指针更新,就知道有新的包要发送,于是处理当前后端指针指向的描述符0,处理完后更新后端指针,然后循环处理描述符1到n-1,后端指针等于前端指针n,网卡设备于是知道所有的包已经处理完毕,等待下次任务。这是一个传统的生产者和消费者模式,前端指针一直在前面生产,而后端指针在后面消费。
图11-3 Virtio虚拟队列示意图
网卡设备中双指针方案有一个缺点:描述符只能顺序执行,前一个描述符处理完之前,后一个描述符就只能等待。Virtio设备中的虚拟队列则不存在这个限制,队列的生产者(前端驱动)将生产出来的描述符放在可用环表中,而消费者(后端驱动)消费之后将消费过的描述符放在已用环表中。前端驱动可以根据已用环表来回收描述符以供下次使用。这样即使中间有描述符被后端驱动所占用,也不会影响被占用描述符之后其他描述符的回收和循环使用。
1. 初始化虚拟队列
虚拟队列的初始化一般紧接着设备的初始化,大部分使用到的寄存器也是和设备的寄存器在同一个配置空间。驱动这边的具体的过程如下:
- 1)选择虚拟队列的索引,写入队列选择寄存器(Queue Select)。
- 2)读取队列容量寄存器(Queue Size),获得虚拟队列的大小,如果是0的话,则表示这个队列不可用。(传统设备中,队列容量只能由设备指定,而现代设备中,如果驱动可以选择写入一个小一些的值到队列容量寄存器来减少内存的使用。)
- 3)分配队列要用到的内存,并把处理后的物理地址写入队列地址寄存器(Queue Address)。
- 4)如果MSI-X中断机制启用,选择一个向量用于虚拟队列请求的中断,把对应向量的MSI-X序号写入队列中断向量寄存器(Queue Vector),然后再次读取该域以确认返回正确值。
2. 描述符列表
描述符列表中每一个描述符代表的是客户虚拟机这侧的一个数据缓冲区,供客户机和宿主机之间传递数据。如果客户机和宿主机之间一次要传递的数据超过一个描述符的容量,多个描述符还可以形成描述符链以共同承载这个大的数据。
每个描述符,如图11-3所示,具体包括以下4个属性:
- Address:数据缓冲区的客户机物理地址。
- Len:数据缓冲区的长度。
- Next:描述符链中下一个描述符的地址。
- Flags:标志位,表示当前描述符的一些属性,包括Next是否有效(如无效,则当前描述符是整个描述符链的结尾),和当前描述符对设备来说是否可写等。
3. 可用环表
可用环表是一个指向描述符的环型表,是由驱动提供(写入),给设备使用(读取)的。设备取得可用环表中的描述符后,描述符所对应的数据缓冲区既可能是可写的,也可能是可读的。可写的是驱动提供给设备写入设备传送给驱动的数据的,而可读的则是用于发送驱动的数据到设备之中。
可用环表的表项,如图11-3所示,具体包括以下3个属性:
- ring:存储描述符指针(id)的数组。
- index:驱动写入下一个可用描述符的位置。
- Flags:标志位,表示可用环表的一些属性,包括是否需要设备在使用了可用环表中的表项后发送中断给驱动。
4. 已用环表
已用环表也是一个指向描述符的环型表,和可用环表相反,它是由设备提供(写入),给驱动使用(读取)的。设备使用完由可用环表中取得的描述符后,再将此描述符插入到已用环表,并通知驱动收回。
已用环表的表项,如图11-3所示,具体包括以下3个属性:
- ring:存储已用元素的数组,每个已用元素包括描述符指针(id)和数据长度(len)。
- index:设备写入下一个已用元素的位置。
- Flags:标志位,表示已用环表的一些属性,包括是否需要驱动在回收了已用环表中的表项后发送提醒给设备。
135.设备的使用
设备使用主要包括两部分过程:驱动通过描述符列表和可用环表提供数据缓冲区给设备用,和设备使用描述符后再通过已用环表还给驱动。例如,Virtio网络设备有两个虚拟队列:发送队列和接收队列。驱动添加要发送的包到发送队列(对设备而言是只读的),然后在设备发送完之后,驱动再释放这些包。接收包的时候,设备将包写入接收队列中,驱动则在已用环表中接收处理这些包。
1. 驱动向设备提供数据缓冲区
客户机操作系统通过驱动提供数据缓冲区给设备使用,具体包括以下步骤:
- 1)把数据缓冲区的地址、长度等信息赋值到空闲的描述符中。
- 2)把该描述符指针添加到该虚拟队列的可用环表的头部。
- 3)更新该可用环表中的头部指针。
- 4)写入该虚拟队列编号到Queue Notify寄存器以通知设备。
2. 设备使用和归还数据缓冲区
设备使用数据缓冲区后(基于不同种类的设备可能是读取或者写入,或是部分读取或者部分写入),将用过的缓冲区描述符填充已用环表,并通过中断通知驱动。具体的过程如下:
- 1)把使用过的数据缓冲区描述符的头指针添加到该虚拟队列的已用环表的头部。
- 2)更新该已用环表中的头部指针。
- 3)根据是否开启MSI-X中断,用不同的中断方式通知驱动。
136.Virtio网络设备驱动设计
Virtio网络设备是Virtio规范中到现在为止定义的最复杂的一种设备。Linux内核和DPDK都有相应的驱动,Linux内核版本功能比较全面,DPDK则更注重性能。
137.Virtio网络设备Linux内核驱动设计
Virtio网络设备Linux内核驱动主要包括三个层次:底层PCI-e设备层,中间Virtio虚拟队列层,上层网络设备层。下面以Linux内核版本v4.1.0为例,具体介绍这三层的组成和互相调用关系。
1. 底层PCI-e设备层
底层PCI-e设备层负责检测PCI-e设备,并初始化设备对应的驱动程序。图11-4所示的是模块组成示意图,原文件是C语言实现的,为了描述方便,按照面向对象的方式对相关变量和函数进行了重新组织。
virtio_driver和virtio_device是Virtio驱动和设备的抽象类,里面封装了所有Virtio设备都需要的一些公共属性和方法,例如向内核注册等。
virtio_pci_device代表的是一个抽象的Virtio的PCI-e设备。virtio_pci_probe是向Linux内核系统注册的回调函数,内核系统发现Virtio类型的设备就会调用这个函数来进行进一步处理。setup_vq、del_vq和conf ig_vector则是相应的功能接口,由具体的实现(virtio_pci_modern_device或virtio_pci_legacy_device)来提供设置虚拟队列、删除虚拟队列和配置中断向量的具体功能。
图11-4 Virtio设备Linux内核底层PCI-e设备层
virtio_pci_device有两个具体的实现,分别是实现现代协议的virtio_pci_modern_device,和实现传统协议的virtio_pci_legacy_device。这两个实现有各自的探测函数virtio_pci_legacy_probe和virtio_pci_modern_probe。如果其中一个探测成功,则会生成一个相应版本的virtio_pci_device。其中的setup_vq负责创建中间Virtio虚拟队列层的vring_virtqueue。
2. 中间Virtio虚拟队列层
中间Virtio虚拟队列层实现了Virtio协议中的虚拟队列,模块示意图如图11-5所示。顶层vring_virtqueue结构代表了Virtio虚拟队列,其中的vring主要是相关的数据结构,virtqueue则连接了设备和实现了对队列操作。vring的数据机构中,vring_desc实现了协议中的描述符列表,vring_avail实现了可用环表,vring_used实现了已用环表。virtqueue中主要有virtqueue_add用来添加描述符到引用环表给设备使用,而virtqueue_get_buf用来从已用环表中获得设备使用过的描述符。
图11-5 Virtio设备Linux内核中间Virtio虚拟队列层
3. 上层网络设备层
上层网络设备层实现了两个抽象类:Virtio设备(virtio_net_driver::virtio_driver)和网络设备(dev::net_device),示意模块组成如图11-6所示。virtio_net_driver是抽象Virtio设备针对于网络设备的具体实现,利用底层PCI-e设备层和中间Virtio虚拟队列层实现了网络设备的收发包和其他的控制功能。dev是Linux抽象网络设备的具体实现,主要通过virtnet_netdev实现Linux net_device_ops接口,和virtnet_ethtool_ops实现Linux ethtool_ops接口,从而Linux系统能够像对待普通网卡一样操作这个Virtio网络设备。
图11-6 Virtio设备Linux内核上层网络设备层
138.基于DPDK用户空间的Virtio网络设备驱动设计以及性能优化
基于DPDK用户空间的Virtio网络设备驱动和Linux内核驱动实现同样的Virtio PCI-e协议。以DPDK版本v2.1为例,DPDK驱动暂时只实现了传统设备的支持,会在后续的版本中支持现代设备。详细内容请参考[Ref11-4]。
其主要实现是在目录drivers/net/virtio/下,也是包括三个层次:底层PCI-e设备层,中间Virtio虚拟队列层,上层网络设备层。底层PCI-e设备层的实现更多的是在DPDK公共构件中实现,virtio_pci.c和virtio_pci.h主要是包括一些读取PCI-e中的配置等工具函数。中间Virtio虚拟队列层实现在virtqueue.c, virtqueue.h和virtio_ring.h中,vring及vring_desc等结构定义和Linux内核驱动也都基本相同。
上层的网络设备层实现的是rte_eth_dev的各种接口,主要在virtio_ethdev.c和virtio_rxtx.c文件中。virtio_rxtx负责数据报的接受和发送,而virtio_ethdev则负责设备的设置。
DPDK用户空间驱动和Linux内核驱动相比,主要不同点在于DPDK只暂时实现了Virtio网卡设备,所以整个构架和优化上面可以暂时只考虑网卡设备的应用场景。例如,Linux内核驱动实现了一个公共的基础Virtio的功能构件,一些特性协商和虚拟队列的设置等都在基础构件中实现,这些基础构件可以在所有的Virtio设备(例如Virtio网络设备和Virtio块设备等)中共享。而在DPDK中,为了效率考虑,一些基础功能就合并在上层的网络设备层中实现了。下面会具体介绍DPDK针对单帧和巨型帧处理的性能优化。
在其他的大部分地方,DPDK用户空间驱动的基本流程和功能与Linux内核驱动是一致的、兼容的,这里就不再展开讨论。
总体而言,DPDK用户空间驱动充分利用了DPDK在构架上的优势(SIMD指令,大页机制,轮询机制,避免用户和内存之间的切换等)和只需要针对网卡优化的特性,虽然实现的是和内核驱动一样的Virtio协议,但整体性能上有较大的提升。
139.关于单帧mbuf的网络包收发优化
如果一个数据包能够放入一个mbuf的结构体中,叫做单帧mbuf。在通常的网络包收发流程中,对于每一个包,前端驱动需要从空闲的vring描述符列表中分配一个描述符,填充guest buffer的相关信息,并更新可用环表表项以及avail idx,然后后端驱动读取并操作更新后的可用环表。在QEMU/KVM的Virtio实现中,vring描述符的个数一般设置成256个。对于接收过程,可以利用mbuf前面的HEADROOM作为virtio net header的空间,所以每个包只需要一个描述符。对于发送过程,除了需要一个描述符指向mbuf的数据区域,还需要使用一个额外的描述符指向额外分配的virtio net header的区域,所以每个包需要两个描述符。
这里有一个典型的性能问题,由于前端驱动和后端驱动一般运行在不同的CPU核上,前端驱动的更新和后端驱动的读取会触发不同核之间可用环表表项的Cache迁移,这是一个比较费时的操作。为了解决这个问题,DPDK提供了一种优化的设计,如图11-7所示,固定了可用环表表项与描述符表项的映射,即可用环表所有表项head_idx指向固定的vring描述符表位置(对于接收过程,可用环表0->描述符表0, 1->1, …, 255->255的固定映射;对于发送过程,0->128, 1->129, … 127->255, 128->128, 129->129, … 255->255的固定映射,描述符表0~127指向mbuf的数据区域,描述符表128~255指向virtio net header的空间),对可用环表的更新只需要更新环表自身的指针。固定的可用环表除了能够避免不同核之间的CACHE迁移,也节省了vring描述符的分配和释放操作,并为使用SIMD指令进行进一步加速提供了便利。
图11-7 固定可用环表的优化
需要特别强调的是,这种优化只针对单帧mbuf(非链式mbuf)的接收与发送。
140.Indirect特性在网络包发送中的支持
Indirect特性指的是Virtio前端和后端通过协商后,都支持VIRTIO_F_RING_INDIRECT_DESC标示,表示驱动支持间接描述符表。如前面介绍,发送的包至少需要两个描述符。通过支持indirect特性,任何一个要发送的包,无论单帧还是巨型帧(巨型帧的介绍见6.6.1节)都只需要一个描述符,该描述符指向一块驱动程序额外分配的间接描述符表的内存区域。间接描述符表中的每一个描述符分别指向virtio net header和每个mbuf的数据区域(单帧只有一个mbuf;巨型帧有多个mbuf)。如图11-8所示,Virtio队列描述符表中每一个描述符都指向一块间接的描述符表的内存区域。该区域的描述符(DPDK目前分配8个)通过next域连接成链。第一个描述符用于指向virtio net header,其余7个描述符可以指向一个巨型帧的最多7个数据区域。
vring desc table
L: len F: flag N: next
图11-8 Indirect描述符表
这种优化针对包的发送,让所有发送的包都只用一个描述符,经过测试发现可以提高性能。
141.小结
本章首先简单介绍了半虚拟化Virtio的典型使用场景,然后详细讨论了Virtio技术,包括设备层面、虚拟队列层面的配置,和设备的使用步骤等。在11.3节则介绍了Virtio网络设备的两种不同的前端驱动设计,包括Linux内核和DPDK用户空间驱动,及DPDK采用的优化技术。在下一章中我们将会详细讨论Virtio设备的后端驱动技术——Vhost。
系列文章
《《深入浅出DPDK》读书笔记(三):NUMA - Non Uniform Memory Architecture 非统一内存架构》
《《深入浅出DPDK》读书笔记(四):并行计算-SIMD是Single-Instruction Multiple-Data(单指令多数据)》
《《深入浅出DPDK》读书笔记(六):报文转发(run to completion、pipeline、精确匹配算法、最长前缀匹配LPM)》
《《深入浅出DPDK》读书笔记(七):PCIe与包处理I/O》
《《深入浅出DPDK》读书笔记(八):网卡性能优化(异步中断模式、轮询模式、混和中断轮询模式)》
《《深入浅出DPDK》读书笔记(十):硬件加速与功能卸载(VLAN、IEEE1588、IP TCP/UDP/SCTP checksum、Tunnel)》
《《深入浅出DPDK》读书笔记(十一):DPDK虚拟化技术篇(I/O虚拟化、CPU虚拟化、内存虚拟化、VT-d、I/O透传)》
《《深入浅出DPDK》读书笔记(十二):DPDK虚拟化技术篇(半虚拟化Virtio)》
相关阅读
《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框架)》