MEMPOOL库
内存池是固定大小对象的分配器。在DPDK中,它由名称标识,并使用环形结构来存储空闲对象。它提供一些其他可选服务,例如每个核心的对象缓存和一个对齐辅助工具,以确保对象填充以将它们均匀分布在所有DRAM或DDR3通道上。
这个库被Mbuf库和环境抽象层(用于记录历史)使用。
6.1 Cookies
在调试模式下(启用了CONFIG_RTE_LIBRTE_MEMPOOL_DEBUG),在分配的块的开头和结尾添加了cookies。然后,分配的对象包含覆写保护字段,以帮助调试缓冲区溢出。
6.2 统计信息
在调试模式下(启用了CONFIG_RTE_LIBRTE_MEMPOOL_DEBUG),有关从/放入池中的统计信息存储在内存池结构中。统计信息是每个核心的,以避免对统计计数器的并发访问。
6.3 内存对齐约束
根据硬件内存配置,通过在对象之间添加特定的填充可以大大提高性能。目标是确保每个对象的开始位于内存中的不同通道和等级,以便所有通道均匀加载。
在进行L3转发或流分类时,对于数据包缓冲区尤其如此。只访问了前64字节,因此通过将对象的起始地址分布在不同通道之间,可以提高性能。
任何DIMM上的rank数量是可以访问DIMM的DRAM的独立集合数,以DIMM的完整数据位宽为准。rank不能同时访问,因为它们共享相同的数据路径。DRAM芯片在DIMM上的物理布局本身不一定与rank的数量有关。
运行应用程序时,EAL命令行选项提供了添加内存通道数和rank的能力。
注意:命令行必须始终指定处理器的内存通道数。
不同DIMM架构的对齐示例如图6.1和图6.2所示。
在这种情况下,假设一个数据包是由16个64字节的块组成,但事实并非如此。
英特尔® 5520芯片组有三个通道,因此在大多数情况下,对象之间不需要填充(除非对象的大小是n x 3 x 64字节块)。
在创建新的内存池时,用户可以指定是否使用此功能。
6.4 本地缓存
从CPU使用角度来看,多个核心访问内存池的自由缓冲区环形队列的成本可能很高,因为每次访问都需要执行比较并设置(CAS)操作。为了避免对内存池环形队列产生过多的访问请求,内存池分配器可以维护每个核心的缓存,并通过缓存向内存池环形队列进行批量请求,从而在实际内存池结构上减少许多较少的锁。这样,每个核心都可以完全访问其自己的(带锁)空闲对象缓存,只有当缓存填满时,核心才需要将一些空闲对象重新放回到池的环形队列中,或者在缓存为空时获取更多的对象。
虽然这可能意味着一些缓存中的缓冲区可能会在某些核心的缓存中处于空闲状态,但核心无需锁定即可访问其自己缓存中的特定内存池,这提供了性能上的增益。
缓存由每个核心的指针组成的小型表及其长度(用作堆栈)组成。可以在创建池时启用或禁用此缓存。
缓存的最大大小是静态的,并在编译时定义(CONFIG_RTE_MEMPOOL_CACHE_MAX_SIZE)。
图6.3显示了缓存的运行情况。
6.5 使用案例
所有需要高性能水平的分配都应该使用基于池的内存分配器。以下是一些例子:
- Mbuf库
- 环境抽象层,用于日志服务
- 任何需要在数据平面分配固定大小对象并将被系统持续利用的应用程序。
MBUF库
mbuf库提供了分配和释放缓冲区(mbufs)的能力,这些缓冲区可能被DPDK应用程序用于存储消息缓冲区。消息缓冲区存储在一个mempool中,使用了Mempool库。
一个rte_mbuf结构可以携带网络数据包缓冲区或通用控制缓冲区(由CTRL_MBUF_FLAG指示)。这可以扩展到其他类型。rte_mbuf头结构尽可能地保持小,当前只使用了两个缓存行,其中最常用的字段位于两个缓存行中的第一个上。
7.1 数据包缓冲区的设计
用于存储数据包数据(包括协议头)的两种方法被考虑:
- 在单个内存缓冲区结构中嵌入元数据,后跟用于数据包数据的固定大小区域。
- 为元数据结构和数据包数据使用单独的内存缓冲区。
第一种方法的优势在于只需要一个操作来分配/释放数据包的整个内存表示。另一方面,第二种方法更加灵活,允许完全将元数据结构的分配与数据包数据缓冲区的分配分开。
DPDK选择了第一种方法。元数据包含诸如消息类型、长度、数据开始偏移和用于额外mbuf结构的指针等控制信息,从而允许缓冲区链接。
用于携带网络数据包的消息缓冲区可以处理缓冲区链接,在需要多个缓冲区来容纳完整数据包的情况下使用。这适用于由许多mbufs通过它们的next字段链接在一起的巨型帧。
对于新分配的mbuf,数据在消息缓冲区中开始的区域是RTE_PKTMBUF_HEADROOM字节在缓冲区开始后的位置,这是缓存对齐的。
消息缓冲区可以用于在系统中的不同实体之间携带控制信息、数据包、事件等。消息缓冲区也可以使用它们的缓冲区指针指向其他消息缓冲区数据部分或其他结构。
图7.1和图7.2展示了其中一些场景。
缓冲区管理器实现了一组相当标准的缓冲区访问函数,用于操作网络数据包。
7.2 存储在内存池中的缓冲区
缓冲区管理器使用Mempool库来分配缓冲区。因此,它确保数据包头在进行L3处理时最佳地交错跨越通道和rank。一个mbuf包含一个字段,指示其起源自哪个池。在调用rte_ctrlmbuf_free(m)或rte_pktmbuf_free(m)时,mbuf将返回到其原始池。
7.3 构造函数
API提供了数据包和控制mbuf的构造函数。rte_pktmbuf_init()和rte_ctrlmbuf_init()函数初始化mbuf结构中一些在创建后用户不修改的字段(mbuf类型、起源池、缓冲区起始地址等)。在池创建时,此函数作为回调函数传递给rte_mempool_create()函数。
7.4 分配和释放mbuf
分配新的mbuf需要用户指定mbuf应该从哪个内存池中获取。对于任何新分配的mbuf,它包含一个段,长度为0。数据的偏移量被初始化以在缓冲区中具有一些字节的头部空间(RTE_PKTMBUF_HEADROOM)。
释放mbuf意味着将其返回到其原始内存池。当mbuf存储在池中(作为空闲mbuf)时,mbuf的内容不会被修改。由构造函数初始化的字段在mbuf分配时不需要重新初始化。
释放包含多个段的数据包mbuf时,所有这些段都会被释放并返回到其原始内存池。
7.5 操纵mbuf
该库提供了一些用于操纵数据包mbuf中数据的函数。例如:
- 获取数据长度
- 获取数据开始的指针
- 在数据之前添加数据
- 在数据之后添加数据
- 删除缓冲区开头的数据(rte_pktmbuf_adj())
- 删除缓冲区结尾的数据(rte_pktmbuf_trim())
请参考DPDK API参考手册了解详情。
7.6 元信息
网络驱动程序检索并存储一些信息在mbuf中,以使处理更加容易。例如,VLAN、RSS哈希结果(请参阅Poll Mode Driver)和指示硬件计算校验和的标志。
一个mbuf还包含输入端口(它来自哪里)以及链中段mbuf的数量。
对于链接的缓冲区,链的第一个mbuf仅存储此元信息。
例如,在IEEE1588数据包时间戳机制、VLAN标记和IP校验和计算的RX侧,这是情况。
在TX侧,如果支持的话,应用程序也可以将某些处理委托给硬件。例如,PKT_TX_IP_CKSUM标志允许卸载IPv4校验和的计算。
以下示例说明了如何在一个经过VXLAN封装的TCP数据包上配置不同的TX卸载。
out_eth/out_ip/out_udp/vxlan/in_eth/in_ip/in_tcp/payload
案例 1:
计算 out_ip
的校验和:
mb->l2_len = len(out_eth)
mb->l3_len = len(out_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM
set out_ip checksum to 0 in the packet
案例 2:
计算 out_ip
和 out_udp
的校验和:
mb->l2_len = len(out_eth)
mb->l3_len = len(out_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM | PKT_TX_UDP_CKSUM
- 在数据包中将 `out_ip` 的校验和设置为 0
- 使用 `rte_ipv4_phdr_cksum()` 函数计算 `out_udp` 的伪首部校验和
This is supported on hardware advertising DEV_TX_OFFLOAD_IPV4_CKSUM
and DEV_TX_OFFLOAD_UDP_CKSUM.
案例 3:
计算 in_ip
的校验和:
mb->l2_len = len(out_eth + out_ip + out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM
set in_ip checksum to 0 in the packet
这与案例 1 类似,但 l2_len 不同。它受硬件支持,该硬件广告了 DEV_TX_OFFLOAD_IPV4_CKSUM。请注意,此操作仅在外部 L4 校验和为 0 时才能生效。
案例 4:
计算 in_ip
和 in_tcp
的校验和:
mb->l2_len = len(out_eth + out_ip + out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CSUM | PKT_TX_TCP_CKSUM
set in_ip checksum to 0 in the packet
set in_tcp checksum to pseudo header using rte_ipv4_phdr_cksum()
这与案例 2 类似,但 l2_len 不同。它受到硬件支持,该硬件宣传支持 DEV_TX_OFFLOAD_IPV4_CKSUM 和 DEV_TX_OFFLOAD_TCP_CKSUM。请注意,此操作仅在外部 L4 校验和为 0 时才能生效。
案例 5:
• 段内部 TCP:
mb->l2_len = len(out_eth + out_ip + out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->l4_len = len(in_tcp)
mb->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CKSUM | PKT_TX_TCP_CKSUM | PKT_TX_TCP_SEG;
将数据包中的 in_ip 校验和设置为 0
使用 rte_ipv4_phdr_cksum() 计算不包括 IP 负载长度的伪首部校验和 in_tcp
此功能由支持 DEV_TX_OFFLOAD_TCP_TSO 的硬件广告支持。请注意,仅当外部 L4 校验和为 0 时才能生效。
案例 6:
• 计算 out_ip、in_ip、in_tcp 的校验和:
mb->outer_l2_len = len(out_eth)
mb->outer_l3_len = len(out_ip)
mb->l2_len = len(out_udp + vxlan + in_eth)
mb->l3_len = len(in_ip)
mb->ol_flags |= PKT_TX_OUTER_IPV4 | PKT_TX_OUTER_IP_CKSUM | PKT_TX_IP_CKSUM | PKT_TX_TCP_CKSUM;
在数据包中将 out_ip 的校验和设置为 0
在数据包中将 in_ip 的校验和设置为 0
使用 rte_ipv4_phdr_cksum() 计算 in_tcp 的伪首部校验和
此功能由支持 DEV_TX_OFFLOAD_IPV4_CKSUM、DEV_TX_OFFLOAD_UDP_CKSUM 和 DEV_TX_OFFLOAD_OUTER_IPV4_CKSUM 的硬件广告支持。
有关标志及其确切含义的列表,请参阅 mbuf API 文档
(rte_mbuf.h)。另请参考 testpmd 源代码(特别是 csumonly.c 文件)了解详情。
7.7 直接和间接缓冲区
直接缓冲区是完全独立和自包含的缓冲区。间接缓冲区的行为类似于直接缓冲区,但缓冲区指针和其中的数据偏移指向另一个直接缓冲区中的数据。在需要复制或分片数据包的情况下很有用,因为间接缓冲区提供了在多个缓冲区中重用相同数据包数据的方式。
当使用 rte_pktmbuf_attach() 函数将一个缓冲区“附加”到直接缓冲区时,该缓冲区变为间接缓冲区。每个缓冲区都有一个引用计数字段,当间接缓冲区附加到直接缓冲区时,直接缓冲区的引用计数会递增。同样地,当间接缓冲区被分离时,直接缓冲区的引用计数会递减。如果结果引用计数等于 0,则直接缓冲区会被释放,因为它不再被使用。
在处理间接缓冲区时需要记住几件事情。首先,不可能将间接缓冲区附加到另一个间接缓冲区上。其次,要使缓冲区变成间接缓冲区,其引用计数必须等于 1,也就是说,它不能被另一个间接缓冲区引用。最后,不可能在未先分离的情况下重新将间接缓冲区附加到直接缓冲区上。
虽然可以直接使用推荐的 rte_pktmbuf_attach() 和 rte_pktmbuf_detach() 函数调用附加/分离操作,但建议使用更高级的 rte_pktmbuf_clone() 函数,该函数会正确初始化间接缓冲区,并能克隆具有多个片段的缓冲区。
由于间接缓冲区实际上不应保存任何数据,因此用于间接缓冲区的内存池应配置为表示减少的内存消耗。间接缓冲区的内存池初始化示例(以及间接缓冲区的用例示例)可在多个示例应用程序中找到,例如 IPv4 多播示例应用程序。
7.8 调试
在调试模式下(启用 CONFIG_RTE_MBUF_DEBUG),mbuf 库的函数在执行任何操作之前进行完整性检查(例如,缓冲区损坏、错误类型等)。
7.9 使用案例
所有网络应用程序都应使用 mbuf 来传输网络数据包。
8. DPDK中的Poll Mode Drivers
DPDK包括1千兆、10千兆和40千兆以及Para虚拟化virtio Poll Mode Drivers(PMD)。
Poll Mode Driver(PMD)由API组成,通过在用户空间运行的BSD驱动程序提供,用于配置设备及其各自的队列。此外,PMD直接访问RX和TX描述符,无需任何中断(除了链路状态更改中断),以便在用户应用程序中快速接收、处理和传递数据包。本节描述了PMD的要求、其全局设计原则,并提出了以太网PMD的高级架构和通用外部API。
8.1 要求和假设
用于数据包处理应用程序的DPDK环境允许两种模型,即run-to-completion(执行完毕)和pipeline(管道):
- 在run-to-completion模型中,通过API轮询特定端口的RX描述符环以获取数据包。然后在同一核心上处理数据包,并通过API将其放置在端口的TX描述符环中进行传输。
- 在pipeline模型中,一个核心通过API轮询一个或多个端口的RX描述符环。数据包被接收并通过环传递到另一个核心。另一个核心继续处理数据包,然后可以通过API将其放置在端口的TX描述符环中进行传输。
在同步的run-to-completion模型中,分配给DPDK的每个逻辑核心执行数据包处理循环,包括以下步骤:
- 通过PMD接收API检索输入数据包
- 逐个处理接收到的每个数据包,直到其转发完成
- 通过PMD传输API发送待处理的输出数据包
而在异步的pipeline模型中,一些逻辑核心可能专门用于检索接收到的数据包,其他逻辑核心用于处理先前接收到的数据包。接收到的数据包在逻辑核心之间通过环进行交换。用于数据包检索的循环包括以下步骤:
- 通过PMD接收API检索输入数据包
- 通过数据包队列提供接收到的数据包给处理核心
用于数据包处理的循环包括以下步骤:
- 从数据包队列中检索接收到的数据包
- 处理接收到的数据包,直到其转发
为了避免不必要的中断处理开销,执行环境不能使用任何异步通知机制。在必要且合适的情况下,应尽可能通过环引入异步通信。在多核环境中避免锁竞争是一个关键问题。为了解决这个问题,PMD被设计成尽可能与每个核心的私有资源一起工作。例如,每个核心、每个端口维护一个单独的传输队列。同样,端口的每个接收队列都分配给并由单个逻辑核心(lcore)轮询。
为了遵守非一致内存访问(NUMA),内存管理被设计为在本地内存中为每个逻辑核心分配私有缓冲池,以最小化远程内存访问。数据包缓冲池的配置应考虑底层的物理内存架构,包括DIMMS、通道和rank。应用程序必须确保在内存池创建时提供适当的参数。参见Mempool Library。
8.2 设计原则
以太网* PMD(Poll Mode Drivers)的API和架构设计遵循以下准则:
PMDs必须有助于在上层应用级别执行全局面向政策的决策。相反,NIC PMD函数不应妨碍上层全局政策所期望的好处,甚至更糟糕地阻止这些政策的应用。
例如,PMD的接收和发送函数都有要轮询的数据包/描述符的最大数量。这允许执行run-to-completion(执行完毕)处理堆栈通过不同的全局循环策略静态地固定或动态地调整其整体行为,比如:
- 逐个以零碎方式接收、立即处理并传输数据包。
- 尽可能接收尽量多的数据包,然后处理所有接收到的数据包,立即传输它们。
- 接收给定数量的数据包,处理接收到的数据包并累积它们,最后将所有累积的数据包发送进行传输。
为了达到最佳性能,必须考虑整体软件设计选择和纯软件优化技术,并与可用的基于低级硬件的优化功能(CPU缓存属性、总线速度、NIC PCI带宽等)进行平衡。数据包传输的情况就是在优化面向突发的网络数据包处理引擎时,软件/硬件权衡问题的一个例子。在最初的情况下,PMD可以只导出一个rte_eth_tx_one函数以在给定队列上逐个传输数据包。在此基础上,可以轻松构建一个rte_eth_tx_burst函数,该函数通过循环调用rte_eth_tx_one函数一次传输多个数据包。然而,为了最小化每个数据包的驱动程序级传输成本,PMD有效地实现了rte_eth_tx_burst函数,通过以下优化实现:
- 将调用rte_eth_tx_one函数的未摊销成本在多个数据包之间共享。
- 使rte_eth_tx_burst函数能够利用面向突发的硬件特性(在缓存中预取数据,使用NIC头/尾寄存器),以最小化每个数据包的CPU周期数,例如通过避免对环形传输描述符的不必要读内存访问,或通过系统地使用完全符合缓存行边界和大小的指针数组。
- 应用面向突发的软件优化技术,消除否则不可避免的操作,例如环形索引回卷管理。
通过API还引入了面向突发的功能,用于PMD密集使用的服务。这尤其适用于用于填充NIC环的缓冲区分配器,提供一次分配/释放多个缓冲区的函数。例如,一个mbuf_multiple_alloc函数返回一个指向rte_mbuf缓冲区的指针数组,当PMD在补充接收环的多个描述符时,加快了PMD的接收轮询函数的执行速度。
8.3 逻辑核心、内存和NIC队列之间的关系
DPDK支持NUMA,当处理器的逻辑核心和接口利用其本地内存时,可以获得更好的性能。因此,与本地PCIe*接口相关联的mbuf分配应从在本地内存中创建的内存池中进行。如果可能,缓冲区应保留在本地处理器上,以获得最佳性能,并且RX和TX缓冲区描述符应填充使用从本地内存中分配的内存池分配的mbuf。
在运行到完成模型中,如果数据包或数据操作在本地内存中而不是远程处理器的内存中,性能会更好。如果管道模型中使用的所有逻辑核心都位于同一处理器上,也适用于此情况。
多个逻辑核心不应共享接口的接收或发送队列,因为这将需要全局锁定并且会阻碍性能。
8.4 设备识别和配置
8.4.1 设备识别
每个NIC端口都由其(总线/桥、设备、功能)PCI标识符唯一标识,由DPDK初始化时执行的PCI探测/枚举功能分配。基于它们的PCI标识符,NIC端口分配了另外两个标识符:
- 用于在PMD API中指定NIC端口的端口索引。
- 用于在控制台消息中指定端口的端口名称,用于管理或调试目的。为了方便使用,端口名称包含端口索引。
8.4.2 设备配置
每个NIC端口的配置包括以下操作:
- 分配PCI资源
- 将硬件重置(发出全局重置)到已知的默认状态
- 设置PHY和链接
- 初始化统计计数器
PMD API还必须导出函数来启动/停止端口的全组播功能,并提供设置/取消设置端口混杂模式的功能。
某些硬件卸载功能必须通过特定的配置参数在端口初始化时进行单独配置。例如,这适用于接收侧扩展(RSS)和数据中心桥接(DCB)功能。
8.4.3 在线配置
所有可以“动态启动”或“动态停止”(即在不停止设备的情况下)的设备功能不需要PMD API导出专用功能来实现此目的。
需要的只是设备PCI寄存器的映射地址,以在驱动程序之外的特定函数中实现这些功能的配置。
为此,PMD API导出一个函数,提供与设备关联的所有信息,可以用于在驱动程序之外设置给定设备的功能。这包括PCI供应商标识符、PCI设备标识符、PCI设备寄存器的映射地址和驱动程序名称。
这种方法的主要优点是它完全自由选择用于配置、启动和停止此类功能的API。
例如,参考testpmd应用程序中对Intel® 82576 Gigabit Ethernet Controller和Intel® 82599 10 Gigabit Ethernet Controller控制器的IEEE1588功能的配置。
其他功能,如端口的L3/L4 5-Tuple数据包过滤功能,可以通过相同方式进行配置。以太网*流控制(暂停帧)可以在各个端口上配置。有关详细信息,请参阅testpmd源代码。另外,只要设置正确,NIC可以为单个数据包启用L4(UDP/TCP/ SCTP)校验和卸载。有关详情,请参阅硬件卸载。
8.4.4 发送和接收队列的配置
每个发送队列独立配置以下信息:
- 发送环的描述符数量
- 用于在NUMA架构中识别适当DMA内存区域的套接字标识符,用于分配发送环
- 发送队列的Prefetch、Host和Write-Back阈值寄存器的值
- 最小传输数据包释放阈值(tx_free_thresh)。当用于传输数据包的描述符数量超过此阈值时,应检查网络适配器是否已回写描述符。在TX队列配置期间可以传递0值,表示应使用默认值。tx_free_thresh的默认值为32。这确保PMD在至少有32个描述符由该队列的NIC处理之前不会搜索已完成的描述符。
- 最小RS位阈值。设置报告状态(RS)位在传输描述符中使用之前所用的最小传输描述符数量。请注意,此参数可能仅对英特尔10 GbE网络适配器有效。如果自上次RS位设置以来使用的描述符数量(到第一个用于传输数据包的描述符)超过传输RS位阈值(tx_rs_thresh),则在用于传输数据包的第一个描述符之前,将在最后一个用于传输数据包的描述符上设置RS位。简而言之,此参数控制网络适配器写回主机内存的传输描述符。在TX队列配置期间可以传递0值,表示应使用默认值。tx_rs_thresh的默认值为32。这确保在网络适配器写回最近使用的描述符之前至少使用32个描述符。这可节省由TX描述符写回产生的上行PCIe*带宽。重要的是要注意,当tx_rs_thresh大于1时,TX写回阈值(TX wthresh)应设置为0。有关更多详情,请参阅英特尔® 82599 10 Gigabit Ethernet Controller 数据表。
以下是对tx_free_thresh和tx_rs_thresh的约束:
- tx_rs_thresh必须大于0。
- tx_rs_thresh必须小于环的大小减2。
- tx_rs_thresh必须小于或等于tx_free_thresh。
- tx_free_thresh必须大于0。
- tx_free_thresh必须小于环的大小减3。
- 为了获得最佳性能,当tx_rs_thresh大于1时,TX wthresh应设置为0。
发送环中的一个描述符用作哨兵,以避免硬件竞态条件,因此有最大阈值约束。
8.4.5 硬件卸载
根据rte_eth_dev_info_get()广告的驱动程序功能,PMD可能支持硬件卸载功能,如校验和、TCP分段或VLAN插入。
这些卸载功能的支持意味着将专用状态位和值字段添加到rte_mbuf数据结构中,以及这些状态位和值字段在每个PMD导出的接收/发送功能中的适当处理。有关标志及其精确含义的列表在mbuf API文档和Mbuf Library的“元信息”部分有描述。
8.5 Poll Mode Driver API
8.5.1 概述
默认情况下,PMD导出的所有函数都是无锁函数,假定不会并行在不同逻辑核心上调用以处理相同的目标对象。例如,不能同时在两个逻辑核心上调用PMD接收函数以轮询相同端口的相同RX队列。当然,不同逻辑核心可以并行地在不同的RX队列上调用此函数。强制执行此规则是上层应用程序的责任。
如果需要,可以通过基于PMD API的相应无锁函数构建的专用内联锁感知功能,明确保护多个逻辑核心对共享队列的并行访问。
8.5.2 通用数据包表示
数据包由rte_mbuf结构表示,这是一个包含所有必要管理信息的通用元数据结构。这包括与卸载硬件功能对应的字段和状态位,例如IP头的校验和计算或VLAN标签。
rte_mbuf数据结构包含特定字段,以通用方式表示网络控制器提供的卸载功能。对于输入数据包,rte_mbuf结构的大多数字段由PMD接收函数使用接收描述符中包含的信息填充。相反,对于输出数据包,rte_mbuf结构的大多数字段由PMD发送函数用于初始化传输描述符。
mbuf结构在Mbuf Library章节中有详细描述。
8.5.3 以太网设备API
以太网PMD导出的以太网设备API在DPDK API参考中有描述。
DPDK IVSHMEM典型用例
该库通过QEMU的IVSHMEM机制,实现了虚拟机之间快速的零拷贝数据共享(主机到客户机或客户机之间)。
使用该库时,通过为QEMU提供命令行,将多个巨页映射到单个IVSHMEM设备。为了让客户机知道任何给定IVSHMEM设备内部的内容,并区分DPDK和非DPDK IVSHMEM设备,还会将一个元数据文件映射到IVSHMEM段内。客户端应用程序无需执行任何操作来将IVSHMEM设备映射到内存中;它们会被DPDK环境抽象层(EAL)自动识别。
典型的DPDK IVSHMEM使用案例如下。
IVSHMEM库
这种机制同样适用于多个虚拟机,提供主机到虚拟机或虚拟机之间的通信。元数据文件的最大数量是32个(默认值),每个元数据文件可以包含不同(甚至相同)的巨页。唯一的约束是每个虚拟机必须能够访问与其他实体共享的内存(无论是主机还是另一个虚拟机)。例如,如果用户想要在两个虚拟机之间共享相同的内存区域(memzone),每个虚拟机必须在其元数据文件中具有该内存区域。
9.1 IVHSHMEM库 API概述
以下是使用IVSHMEM库API的简单指南:
- 调用
rte_ivshmem_metadata_create()
创建一个新的元数据文件。元数据名称用于区分多个元数据文件。 - 使用以下API调用向每个元数据文件填充DPDK数据结构:
rte_ivhshmem_metadata_add_memzone()
将rte_memzone
添加到元数据文件rte_ivshmem_metadata_add_ring()
将rte_ring
添加到元数据文件rte_ivshmem_metadata_add_mempool()
将rte_mempool
添加到元数据文件
- 最后,调用
rte_ivshmem_metadata_cmdline_generate()
为QEMU生成命令行。可以向单个虚拟机提供多个元数据文件(因此也有多个命令行)。
注意: 只有完全驻留在DPDK巨页内存中的数据结构才能正常工作。由 malloc()
、mmap()
或以其他方式使用非DPDK内存创建的数据结构会导致未定义的行为甚至段错误。
9.2 IVSHMEM环境配置
成功运行IVSHMEM应用程序需要以下步骤:
-
从源代码编译特殊版本的QEMU。
源代码可以在QEMU网站找到(目前支持版本为1.4.x,但已知版本1.5.x也可以工作),但是需要对源代码进行修补,以支持使用常规文件作为IVSHMEM内存后端。该补丁未包含在DPDK软件包中,但可以在Intel®DPDK-vswitch项目网页上找到(可以单独获取或在DPDK vSwitch软件包中获取)。 -
在DPDK构建配置中启用IVSHMEM库。
在默认配置中,未编译IVSHMEM库。要编译IVSHMEM库,必须使用提供的IVSHMEM目标之一(例如,x86_64-ivshmem-linuxapp-gcc),或者在构建配置中将 CONFIG_RTE_LIBRTE_IVSHMEM 设置为“y”。 -
在虚拟机上设置巨页内存。
客户机应用程序作为常规的DPDK(主要)进程运行,因此需要在虚拟机内设置自己的巨页内存。该过程与DPDK入门指南中描述的过程相同。
9.3 编写IVSHMEM应用程序的最佳实践
在考虑使用IVSHMEM共享内存时,需要仔细评估安全性问题。IVSHMEM不适用于不受信任的客户机,因为IVSHMEM本质上是主机进程内存的窗口。这也对多个虚拟机场景有影响。虽然IVSHMEM库尝试尽可能少地共享内存,但很可能为一个虚拟机指定的数据也存在于为另一个虚拟机指定的IVSMHMEM设备中。因此,任何共享内存的损坏都会影响主机和共享特定内存的所有虚拟机。
IVSHMEM应用程序本质上类似于多进程应用程序,因此实施数据和线程安全的访问序列化是非常重要的。DPDK环形结构已经是线程安全的,但是用户可能需要的任何自定义数据结构也必须是线程安全的。
类似于常规的DPDK多进程应用程序,不建议在不同进程中使用函数指针,因为不同进程中的函数可能具有不同的内存地址。
最好避免在分配它的不同机器上释放rte_mbuf结构,也就是说,如果mbuf是在主机上分配的,那么应该在主机上释放它。因此,任何数据包的发送和接收也应该发生在同一台机器上(无论是虚拟还是物理)。如果未能这样做,可能会导致内存池缓存中的数据损坏。
尽管IVSHMEM机制是零拷贝且性能良好的,但最好还是批量处理并遵循性能优化中描述的其他程序。
9.4 运行IVSHMEM应用程序的最佳实践
出于性能考虑,最好将主机进程和QEMU进程固定在不同的核心上,以避免彼此干扰。如果启用了NUMA支持,最好将主机进程的巨页内存和QEMU进程放在同一个NUMA节点上。
为了获得跨所有NUMA节点的最佳性能,每个QEMU核心应该固定在相应NUMA节点上的主机CPU核心上。还应设置QEMU的虚拟NUMA节点以对应物理NUMA节点。有关如何设置DPDK和QEMU NUMA支持的更多信息,可以在DPDK入门指南和QEMU文档中找到。
DPDK软件包中提供了一个名为cpu_layout.py的脚本(位于tools目录中),可用于确定哪些CPU核心对应于哪些NUMA节点。
在启动虚拟机之前,应该将QEMU IVSHMEM命令行创建视为最后一步。目前,QEMU IVSHMEM设备不支持热插拔,因此一旦创建了IVSHMEM设备,就无法向其添加额外的内存。因此,运行IVSHMEM应用程序的正确顺序是首先运行主机应用程序,获取每个IVSHMEM设备的命令行,然后在之后运行所有QEMU实例以及客户机应用程序。
需要注意的是,一旦启动了QEMU,它会保持对用于IVSHMEM设备的巨页的占用。因此,如果用户希望关闭或重启IVSHMEM主机应用程序,仅仅关闭应用程序是不够的。还必须关闭虚拟机(否则,它将保留过时的主机数据)。
LINK BONDING POLL MODE DRIVER LIBRARY
除了用于物理和虚拟硬件的轮询模式驱动程序(PMD)之外,DPDK还包括一个纯软件库,允许将物理PMD绑定在一起,创建一个单一的逻辑PMD。
链路绑定PMD库(librte_pmd_bond)支持将具有相同速度和双工的一组rte_eth_dev端口绑定在一起,以提供类似于Linux绑定驱动程序的功能,允许将多个(从)NIC聚合成服务器和交换机之间的单个逻辑接口。然后,新的绑定PMD将根据指定的操作模式处理这些接口,以支持冗余链接、容错性和/或负载平衡等功能。
librte_pmd_bond库导出了一个C API,提供了一个用于创建绑定设备以及配置和管理绑定设备及其从设备的API。
注意:链路绑定PMD库在构建配置文件中默认启用,可以通过设置 CONFIG_RTE_LIBRTE_PMD_BOND=n 来禁用该库,并重新编译DPDK。
10.1 链路绑定模式概述
目前,链路绑定PMD库支持4种操作模式:
-
循环轮询(Mode 0):
此模式通过从第一个可用的从设备到最后一个依次传输数据包来实现负载平衡和容错。数据包从设备中进行批量出队,然后以轮询方式进行服务。该模式不保证数据包按顺序接收,下游应能够处理无序数据包。
-
主备模式(Mode 1):
在此模式下,绑定中只有一个从设备是活动的,任何时候只有一个不同的从设备处于活动状态,只有在主要活动从设备故障时才会更改。因此提供从设备故障的容错性。单个逻辑绑定接口的MAC地址仅在一个NIC(端口)上外部可见,以避免混淆网络交换机。
-
平衡异或(Mode 2):
注意:数据包的着色差异用于识别由选定的传输策略计算出的不同流分类。 -
广播(Mode 3):
-
链路聚合802.3AD(Mode 4):
-
传输负载平衡(Mode 5):
10.2 实现细节
librte_pmd_bond绑定设备与DPDK API参考中描述的Ethernet PMDs导出的Ethernet设备API兼容。
链路绑定库支持在应用程序启动期间(在EAL初始化期间)使用 --vdev 选项或通过C API rte_eth_bond_create函数以编程方式创建绑定设备。
绑定设备支持使用 rte_eth_bond_slave_add / rte_eth_bond_slave_remove API 动态添加和移除从设备。
添加从设备到绑定设备后,从设备将使用 rte_eth_dev_stop 停止,然后使用 rte_eth_dev_configure 进行重新配置。RX和TX队列也会被重新配置。
此模式提供了传输负载平衡(基于所选的传输策略)和容错性。默认策略(layer2)使用简单的计算,基于数据包流的源和目的MAC地址,以及可用于绑定设备的活动从设备数量,将数据包分类到特定的从设备进行传输。支持的备用传输策略有layer 2+3,这将IP源和目的地址纳入计算,以确定传输从设备端口,最后支持的策略是layer 3+4,它使用IP源和目的地址以及TCP/UDP源和目的端口。
此模式通过在所有从端口上传输数据包提供容错性。
使用 rte_eth_tx_queue_setup / rte_eth_rx_queue_setup 重新配置从设备的RX和TX队列,使用的参数与配置绑定设备时相同。
10.2.1 链路状态变化中断 / 轮询
链路绑定设备支持使用 rte_eth_dev_callback_register API 注册链路状态变化回调,当绑定设备的状态发生变化时将调用此回调。例如,对于具有3个从设备的绑定设备,在其中一个从设备变为活动状态时,链路状态将变为up;当所有从设备变为非活动状态时,链路状态将变为down。如果单个从设备的状态发生变化并且不符合前述条件,则不会有回调通知。如果用户希望监视单个从设备,则必须直接向该从设备注册回调。
链路绑定库还支持不实现链路状态变化中断的设备,这是通过在定义的周期内对设备的链路状态进行轮询来实现的,使用 rte_eth_bond_link_monitoring_set API 设置轮询间隔,默认轮询间隔为10毫秒。当设备作为绑定设备的从设备添加时,使用 RTE_PCI_DRV_INTR_LSC 标志确定设备是否支持中断,或者是否应通过轮询来监视链路状态。
10.2.2 要求 / 限制
当前的实现仅支持支持相同速度和双工的设备作为绑定设备的从设备。绑定设备从首个添加到绑定设备的活动从设备继承这些属性,然后所有后续添加到绑定设备的从设备必须支持这些参数。
此模式根据802.3ad规范提供了动态链路聚合。它使用选择的平衡传输策略来协商和监视共享相同速度和双工设置的聚合组,以平衡出站流量。
DPDK实现此模式对应用程序有一些额外要求:
- 必须以少于100毫秒的间隔调用 rte_eth_tx_burst 和 rte_eth_rx_burst 函数。
- 调用 rte_eth_tx_burst 的缓冲区大小必须至少为2xN,其中N是从设备的数量。这是用于LACP帧的所需空间。此外,LACP数据包包含在统计信息中,但不会返回给应用程序。
此模式提供自适应传输负载平衡。根据计算的负载动态更改传输从设备。统计信息每100毫秒收集一次,并每10毫秒调度一次。
绑定设备在启动前必须至少有一个从设备。
与所有其他PMD一样,由PMD导出的所有函数都是无锁函数,假定不会并行在不同的逻辑核上对同一目标对象进行操作。
还应注意,一旦从设备被添加到绑定设备中,就不应直接在从设备上调用PMD接收函数,因为直接从从设备读取的数据包将不再对绑定设备可读。
10.2.3 配置
链路绑定设备是使用 rte_eth_bond_create API 创建的,该API需要一个唯一的设备名称、绑定模式和用于分配绑定设备资源的套接字ID。对于绑定设备的其他可配置参数包括其从设备、主要从设备、用户定义的MAC地址以及如果设备处于平衡异或模式下要使用的传输策略。
从设备
绑定设备最多支持RTE_MAX_ETHPORTS个相同速度和双工的从设备。以太网设备最多可以作为一个从设备添加到一个绑定设备中。当添加到绑定设备时,从设备将重新配置为绑定设备的配置。
当从绑定设备中删除从设备时,绑定设备保证将从设备的MAC地址返回到其原始值。
主要从设备
主要从设备用于定义绑定设备处于主备模式时使用的默认端口。仅当当前主要端口下线时才会使用不同的端口。如果用户未指定主要端口,则默认为添加到绑定设备的第一个端口。
MAC地址
绑定设备可以配置用户指定的MAC地址,此地址将根据操作模式传递给一些/所有从设备。如果设备处于主备模式,则只有主要设备将具有用户指定的MAC地址,所有其他从设备将保留其原始MAC地址。在模式0、2、3、4中,所有从设备都使用绑定设备的MAC地址进行配置。
如果未定义用户定义的MAC地址,则绑定设备将默认使用主要从设备的MAC地址。
平衡异或传输策略
在平衡异或模式下运行的绑定设备支持三种支持的传输策略。
- Layer 2:以太网MAC地址为基础的平衡是平衡异或绑定模式的默认传输策略。它对数据包的源MAC地址和目标MAC地址进行简单的异或计算,然后计算该值的模以确定要在哪个从设备上传输数据包。
- Layer 2 + 3:以太网MAC地址和IP地址为基础的平衡使用数据包的源/目标MAC地址和数据包的源/目标IP地址来决定数据包将在哪个从设备上传输。
- Layer 3 + 4:IP地址和UDP端口为基础的平衡使用数据包的源/目标IP地址和数据包的源/目标UDP端口来决定数据包将在哪个从设备上传输。
所有这些策略支持802.1Q VLAN以太网数据包,以及IPv4、IPv6和UDP协议进行负载平衡。
10.3 使用链路绑定设备
librte_pmd_bond库支持两种设备创建模式,即库导出完整的C API或使用EAL命令行在应用程序启动时静态配置链路绑定设备。使用EAL选项,可以在不需要具体了解库API的情况下透明地使用链路绑定功能。例如,可以将绑定功能(如主备模式)添加到不了解链路绑定C API的现有应用程序中。
10.3.1 从应用程序使用Poll模式驱动
使用librte_pmd_bond库的API,可以在任何应用程序中动态创建和管理链路绑定设备。链路绑定设备使用rte_eth_bond_create API创建,该API需要一个唯一的设备名称、链路绑定模式以及要分配设备资源的套接字ID。创建绑定设备后,必须使用通用以太网设备配置API rte_eth_dev_configure 进行配置,然后必须使用 rte_eth_tx_queue_setup / rte_eth_rx_queue_setup 设置要使用的RX和TX队列。
可以使用 rte_eth_bond_slave_add / rte_eth_bond_slave_remove API 动态添加和移除从设备,但在绑定设备启动之前,必须向链路绑定设备添加至少一个从设备,然后使用 rte_eth_dev_start 进行启动。
绑定设备的链路状态由其从设备的状态决定,如果所有从设备的链路状态都为down,或者如果所有从设备都从链路绑定设备中移除,则绑定设备的链路状态将为down。
还可以使用提供的API(如 rte_eth_bond_mode_set/get、rte_eth_bond_primary_set/get、rte_eth_bond_mac_set/reset 和 rte_eth_bond_xmit_policy_set/get)配置/查询绑定设备的控制参数。
10.3.2 从EAL命令行使用链路绑定设备
链路绑定设备可以在应用程序启动时使用 --vdev EAL命令行选项创建。设备名称必须以 eth_bond 前缀开头,后跟数字或字母。每个设备的名称必须是唯一的。每个设备可以具有多个选项,这些选项按逗号分隔成列表。通过多次调用 --vdev 选项可以安排多个设备定义。
设备名称和绑定选项之间必须用逗号分隔,如下所示:
$RTE_TARGET/app/testpmd -c f -n 4 \
--vdev 'eth_bond0,bond_opt0=..,bond opt1=..' \
--vdev 'eth_bond1,bond _opt0=..,bond_opt1=..'
链接绑定EAL选项
有多种方式可以定义和组合,只要遵守以下两个规则:
提供唯一的设备名称,格式为 eth_bondX,其中X可以是任意数字和/或字母,并且名称不超过32个字符。
对于每个绑定设备定义,至少提供一个从设备。
不同的选项包括:
mode
: 定义设备的绑定模式的整数值。当前支持模式0,1,2,3,4,5(循环轮询、主备模式、平衡、广播、链路聚合、传输负载平衡)。
mode=2
slave
: 定义要添加为绑定设备从设备的PMD设备。此选项可以多次选择,每个要添加为从设备的设备都应指定其PCI地址,格式为 domain🚌devid.function。
slave=0000:0a:00.0,slave=0000:0a:00.1
primary
: 可选参数,用于定义主要从设备端口,在主备模式下用于选择主要从设备进行数据TX/RX。如果未指定主要端口,它将默认为添加到设备的第一个从设备。
primary=0000:0a:00.0
socket_id
: 可选参数,用于选择NUMA设备上的哪个套接字来分配绑定设备的资源。
socket_id=0
mac
: 选择链路绑定设备的MAC地址的可选参数,这将覆盖主要从设备的值。
mac=00:1e:67:1d:fd:1d
xmit_policy
: 在绑定设备处于平衡模式时定义传输策略的可选参数。如果未指定,默认为l2(第2层)转发,其他可用的传输策略是l23(第2+3层)和l34(第3+4层)。
xmit_policy=l23
lsc_poll_period_ms
: 定义设备轮询链路状态变化的时间间隔(以毫秒为单位),用于不支持lsc中断的设备。
lsc_poll_period_ms=100
up_delay
: 添加到设备链路状态更改为up的传播的延迟时间(以毫秒为单位)。
up_delay=10
down_delay
: 添加到设备链路状态更改为down的传播的延迟时间(以毫秒为单位)。
down_delay=50
使用示例
以下是一些示例用法:
- 创建循环轮询模式的绑定设备,使用两个从设备的PCI地址:
$RTE_TARGET/app/testpmd -c '0xf' -n 4 \
--vdev 'eth_bond0,mode=0, slave=0000:00a:00.01,slave=0000:004:00.00' \
-- --port-topology=chained
- 创建循环轮询模式的绑定设备,使用两个从设备的PCI地址和覆盖的MAC地址:
$RTE_TARGET/app/testpmd -c '0xf' -n 4 \
--vdev 'eth_bond0,mode=0, slave=0000:00a:00.01,slave=0000:004:00.00,mac=00:1e:67:1d:fd:1d' \
-- --port-topology=chained
- 创建主备模式的绑定设备,使用两个从设备和一个主要从设备的PCI地址:
$RTE_TARGET/app/testpmd -c '0xf' -n 4 \
--vdev 'eth_bond0,mode=1, slave=0000:00a:00.01,slave=0000:004:00.00,primary=0000:00a:00.01' \
-- --port-topology=chained
- 创建平衡模式的绑定设备,使用两个从设备的PCI地址,并使用第3+4层转发的传输策略:
- `$RTE_TARGET/app/testpmd -c '0xf' -n 4 \
--vdev 'eth_bond0,mode=2, slave=0000:00a:00.01,slave=0000:004:00.00,xmit_policy=l34' \
-- --port-topology=chained`