
DPDK(version 2002)函数rte_pci_map_device用来映射pci device resource到用户态:
这个函数执行前,DPDK已经通过扫描sys文件系统,获取了pci设备绑定的driver,当设备绑定到vfio-pci或者igb_uio的时候,会使用不同的方式映射设备。以下分别介绍:
当设备绑定到vfio-pci时,调用函数pci_vfio_map_resource
我们在此对函数pci_vfio_map_resource_primary的主要部分进行分析。
此函数的主要工作内容如下:
通过读取设备的PCI配置空间,读取的方法是通过上一步取得的设备句柄,获取msix的配置信息。并保存到vfio_res结构体中。
获取设备的BAR REGION(寄存器,中断等信息),并完成寄存器的mmap映射,让用户态程序能够直接访问PCI设备的寄存器。
这个函数首先设置中断,将第一个中断添加到系统的中断轮训链表去。
然后设置开启设备,并对设备复位。1、DPDK
DPDK是X86平台报文快速处理的库和驱动的集合,大多数情况下运行在linux的用户态空间。
2、Open vSwitch
简称OVS是一个虚拟交换软件,主要用于虚拟机VM环境,作为一个虚拟交换机,支持Xen/XenServer, KVM, and VirtualBox多种虚拟化技术。
在这种某一台机器的虚拟化的环境中,一个虚拟交换机(vswitch)主要有两个作用:传递虚拟机VM之间的流量,以及实现VM和外界网络的通信。
在 OVS 中, 有几个非常重要的概念:
Bridge: Bridge 代表一个以太网交换机(Switch),一个主机中可以创建一个或者多个 Bridge 设备。
Port: 端口与物理交换机的端口概念类似,每个 Port 都隶属于一个 Bridge。
Interface: 连接到 Port 的网络接口设备。在通常情况下,Port 和 Interface 是一对一的关系, 只有在配置 Port 为 bond 模式后,Port 和 Interface 是一对多的关系。
Controller: OpenFlow 控制器。OVS 可以同时接受一个或者多个 OpenFlow 控制器的管理。
Datapath: 在 OVS 中,datapath 负责执行数据交换,也就是把从接收端口收到的数据包在流表中进行匹配,并执行匹配到的动作。
Flow table: 每个 datapath 都和一个“flow table”关联,当 datapath 接收到数据之后, OVS 会在 flow table 中查找可以匹配的 flow,执行对应的 *** 作, 例如转发数据到另外的端口。
3、OVDK>rte_schedh文件包含port,subport和pipe的配置功能。
Port调度入队API非常类似于DPDK PMD TX功能的API。
Port调度入队API非常类似于DPDK PMD RX功能的API。
内部数据结构示意图,详细内容如下。
多核缩放策略如下:
上面强调过,同一个端口的出队和入队需要由同一个线程执行。因为,在不同core上对同一个输出端口执行出队和入队 *** 作,可能会对调度程序的性能造成重大影响,因此不推荐这样做。
同一端口的入队和出队 *** 作共享以下数据结构的访问权限:
可能存在使性能下降的原因如下:
当调度程序入队和出队 *** 作必须在同一个线程运行,允许队列和位图 *** 作非线程安全,并将调度程序数据结构保持在同一个core上,可以很大程度上保证性能。
扩展NIC端口数量只需要保证用于流量调度的CPU内核数量按比例增加即可。
每个数据包的入队步骤:
应该注意到这些步骤之间具有很强的数据依赖性,因为步骤2和3在步骤1和2的结果变得可用之前无法启动,这样就无法使用处理器乱序执行引擎上提供任何显着的性能优化。
考虑这样一种情况,给定的输入报文速率很高,队列数量大,可以想象得到,入队当前数据包需要访问的数据结构不存在于当前core的L1或L2 data cache中,此时,上述3个内存访问 *** 作将会产生L1和L2 data cache miss。就性能考虑而言,每个数据包出现3次L1 / L2 data cache miss是不可接受的。
解决方法是提前预取所需的数据结构。预取 *** 作具有执行延迟,在此期间处理器不应尝试访问当前正在进行预取的数据结构,此时处理器转向执行其他工作。可用的其他工作可以是对其他输入报文执行不同阶段的入队序列,从而实现入队 *** 作的流水线实现。
下图展示出了具有4级水线的入队 *** 作实现,并且每个阶段 *** 作2个不同的输入报文。在给定的时间点上,任何报文只能在水线某个阶段进行处理。
由上图描述的入队水线实现的拥塞管理方案是非常基础的:数据包排队入队,直到指定队列变满为止;当满时,到这个队列的所有数据包将被丢弃,直到队列中有数据包出队。可以通过使用RED/WRED作为入队水线的一部分来改进,该流程查看队列占用率和报文优先级,以便产生特定数据包的入队/丢弃决定(与入队所有数据包/不加区分地丢弃所有数据包不一样)。
从当前pipe调度下一个数据包的步骤如下:
为了避免cache miss,上述数据结构(pipe,queue,queue array,mbufs)在被访问之前被预取。隐藏预取 *** 作的延迟的策略是在为当前pipe发出预取后立即从当前pipe(在grinder A中)切换到另一个pipe(在grinderB中)。这样就可以在执行切换回pipe(grinder A)之前,有足够的时间完成预取 *** 作。
出pipe状态机将数据存在处理器高速缓存中,因此它尝试从相同的pipe TC和pipe(尽可能多的数据包和信用)发送尽可能多的数据包,然后再移动到下一个活动TC pipe(如果有)或另一个活动pipe。
输出端口被建模为字节槽的传送带,需要由调度器填充用于传输的数据。对于10GbE,每秒需要由调度器填充125亿个字节的槽位。如果调度程序填充不够快,只要存在足够的报文和信用,则一些时隙将被闲置并且带宽将被浪费。
原则上,层次调度程序出队 *** 作应由NIC TX触发。通常,一旦NIC TX输入队列的占用率下降到预定义的阈值以下,端口调度器将被唤醒(基于中断或基于轮询,通过连续监视队列占用)来填充更多的数据包进入队列。
调度器需要跟踪信用逻辑的时间演化,因为信用需要基于时间更新(例如,子流量和管道流量整形,流量级上限执行等)。
每当调度程序决定将数据包发送到NIC TX进行传输时,调度器将相应地增加其内部时间参考。因此,以字节为单位保持内部时间基准是方便的,其中字节表示物理接口在传输介质上发送字节所需的持续时间。这样,当报文被调度用于传输时,时间以(n + h)递增,其中n是以字节为单位的报文长度,h是每个报文的成帧开销字节数。
调度器需要将其内部时间参考对齐到端口传送带的步速。原因是要确保调度程序不以比物理介质的线路速率更多的字节来馈送NIC TX,以防止数据包丢失。
调度程序读取每个出队调用的当前时间。可以通过读取时间戳计数器(TSC)寄存器或高精度事件定时器(HPET)寄存器来获取CPU时间戳。 当前CPU时间戳将CPU时钟数转换为字节数:time_bytes = time_cycles / cycles_per_byte,其中cycles_per_byte是等效于线上一个字节的传输时间的CPU周期数(例如CPU频率 2 GHz和10GbE端口, cycles_per_byte = 16 )。
调度程序维护NIC time的内部时间参考。 每当分组被调度时,NIC time随分组长度(包括帧开销)增加。在每次出队调用时,调度程序将检查其NIC time的内部引用与当前时间的关系:
调度器往返延迟(SRTD)是指调度器在同一个pipe的两次连续检验之间的时间(CPU周期数)。
为了跟上输出端口(即避免带宽丢失),调度程序应该能够比NIC TX发送的n个数据包更快地调度n个数据包。
假设没有端口超过流量,调度程序需要跟上管道令牌桶配置的每个管道的速率。这意味着管道令牌桶的大小应该设置得足够高,以防止它由于大的SRTD而溢出,因为这将导致管道的信用损失(带宽损失)。
当满足以下所有条件时,从(subport S,pipe P,traffic class TC,queue Q)发送下一个分组的调度决定(分组被发送):
如果满足所有上述条件,则选择分组进行传输,并从子接口S,子接口S流量类TC,管道P,管道P流量类TC中减去必要的信用。
232462 帧开销
由于所有数据包长度的最大公约数为1个字节,所以信用单位被选为1个字节。传输n个字节的报文所需的信用数量等于(n + h),其中h等于每个报文的成帧开销字节数。
Subport和pipe的流量整形使用每个subport/pipe的令牌桶来实现。令牌桶使用一个饱和计数器实现,该计数器跟踪可用信用数量。
令牌桶通用参数和 *** 作如下表所示。
为了实现上述的令牌桶通用 *** 作,当前的设计使用表238中所示的数据结构,而令牌桶 *** 作的实现在表239中描述。
桶速率(以字节为单位)可以用以下公式计算:
同一管道内流量级别的严格优先级调度由管理出队状态机实现,该队列按升序选择队列。因此,1215(TC 3,最低优先级TC),队列811(TC 2),队列47(TC 1,比TC 0的优先级低),队列03(TC 0,最高优先级TC相关联)。
Pipe和Subport级别的流量类别不是流量整形,因此在此上下文中不存在令牌桶。通过周期性地重新填充subport/pipe流量信用计数器来执行subport和pipe级别的流量类别的上限,每次为该subport/pipe调度数据包时消耗信用量,如表2310所述 和表2311。报文缓冲区库(Mbuf)提供了申请和释放缓冲区的功能,DPDK应用程序使用这些buffer存储消息缓冲。消息缓冲存储在mempool中,使用内存池库 。
数据结构rte_mbuf可以承载网络数据包buffer或者通用控制消息buffer(由CTRL_MBUF_FLAG指示)。也可以扩展到其他类型。rte_mbuf头部结构尽可能小,目前只使用两个缓存行,最常用的字段位于第一个缓存行中。
为了存储数据包数据(包括协议头部),考虑了两种方法:
第一种方法的优点是他只需要一个 *** 作来分配/释放数据包的整个存储表示。但是,第二种方法更加灵活,并允许将元数据的分配与报文数据缓冲区的分配完全分离。
DPDK选择了第一种方法。Metadata包含诸如消息类型,长度,到数据开头的偏移量等控制信息,以及允许缓冲链接的附加mbuf结构指针。
用于承载网络数据包buffer的消息缓冲可以处理需要多个缓冲区来保存完整数据包的情况。许多通过下一个字段链接在一起的mbuf组成的jumbo帧,就是这种情况。
对于新分配的mbuf,数据开始的区域是buffer之后 RTE_PKTMBUF_HEADROOM 字节的位置,这是缓存对齐的。 Message buffers可以在系统中的不同实体中携带控制信息,报文,事件等。 Message buffers也可以使用起buffer指针来指向其他消息缓冲的数据字段或其他数据结构。
Buffer Manager实现了一组相当标准的buffer访问 *** 作来 *** 纵网络数据包。
Buffer Manager使用内存池库来申请buffer。因此确保了数据包头部均衡分布到信道上,有利于L3处理。mbuf中包含一个字段,用于表示它从哪个池中申请出来。当调用 rte_ctrlmbuf_free(m) 或 rte_pktmbuf_free(m),mbuf被释放到原来的池中。
Packet及control mbuf构造函数由API提供。接口rte_pktmbuf_init()及rte_ctrlmbuf_init()初始化mbuf结构中的某些字段,这些字段一旦创建将不会被用户修改(如mbuf类型、源池、缓冲区起始地址等)。此函数在池创建时作为rte_mempool_create()函数的回掉函数给出。
分配一个新mbuf需要用户指定从哪个池中申请。对于任意新分配的mbuf,它包含一个段,长度为0。 缓冲区到数据的偏移量被初始化,以便使得buffer具有一些字节(RTE_PKTMBUF_HEADROOM)的headroom。
释放mbuf意味着将其返回到原始的mempool。当mbuf的内容存储在一个池中(作为一个空闲的mbuf)时,mbuf的内容不会被修改。由构造函数初始化的字段不需要在mbuf分配时重新初始化。
当释放包含多个段的数据包mbuf时,他们都被释放,并返回到原始mempool。
这个库提供了一些 *** 作数据包mbuf中的数据的功能。例如:
数据包的一些信息由网络驱动程序检索并存储在mbuf中使得处理更简单。例如,VLAN、RSS哈希结果(参见 Poll Mode Driver)及校验和由硬件计算的标志等。
一个报文缓冲区中还包含数据源端口和报文链中mbuf数目。对于链接的mbuf,只有链的第一个mbuf存储这个元信息。
例如,对于IEEE1588数据包,RX侧就是这种情况,时间戳机制,VLAN标记和IP校验和计算。
在TX端,应用程序还可以将一些处理委托给硬件。 例如,PKT_TX_IP_CKSUM标志允许卸载IPv4校验和的计算。
以下示例说明如何在vxlan封装的tcp数据包上配置不同的TX offloads:out_eth/out_ip/out_udp/vxlan/in_eth/in_ip/in_tcp/payload
Flage标记的意义在mbuf API文档(rte_mbufh)中有详细描述。 更多详细信息还可以参阅testpmd 源码(特别是csumonlyc)。
直接缓冲区是指缓冲区完全独立。间接缓冲区的行为类似于直接缓冲区,但缓冲区的指针和数据偏移量指的是另一个直接缓冲区的数据。这在数据包需要复制或分段的情况下是很有用的,因为间接缓冲区提供跨越多个缓冲区重用相同数据包数据的手段。
当使用接口 rte_pktmbuf_attach() 函数将缓冲区附加到直接缓冲区时,该缓冲区变成间接缓冲区。每个缓冲区有一个引用计数器字段,每当直接缓冲区附加一个间接缓冲区时,直接缓冲区上的引用计数器递增。类似的,每当间接缓冲区被分裂时,直接缓冲区上的引用计数器递减。如果生成的引用计数器为0,则直接缓冲区将被释放,因为它不再使用。
处理间接缓冲区时需要注意几件事情。首先,间接缓冲区从不附加到另一个间接缓冲区。尝试将缓冲区A附加到间接缓冲区B(且B附加到C上了),将使得rte_pktmbuf_attach() 自动将A附加到C上。其次,为了使缓冲区变成间接缓冲区,其引用计数必须等于1,也就是说它不能被另一个间接缓冲区引用。最后,不可能将间接缓冲区重新链接到直接缓冲区(除非它已经被分离了)。
虽然可以使用推荐的rte_pktmbuf_attach()和rte_pktmbuf_detach()函数直接调用附加/分离 *** 作,但建议使用更高级的rte_pktmbuf_clone()函数,该函数负责间接缓冲区的正确初始化,并可以克隆具有多个段的缓冲区。
由于间接缓冲区不应该实际保存任何数据,间接缓冲区的内存池应配置为指示减少的内存消耗。可以在几个示例应用程序中找到用于间接缓冲区的内存池(以及间接缓冲区的用例示例)的初始化示例,例如IPv4组播示例应用程序。
在调试模式(CONFIG_RTE_MBUF_DEBUG使能)下,mbuf库的函数在任何 *** 作之前执行完整性检查(如缓冲区检查、类型错误等)。
所有网络应用程序都应该使用mbufs来传输网络数据包。
原文链接: >DPDK(Data Plane Development Kit)是数据平面开发工具包,由用于加速在各种CPU架构上运行的数据包处理的库组成。
在Linux上部署DPDK的方法请参考:
在Linux(CentOS)上部署DPDK------命令行方式
该章节的内容参照自官网的 DPDK build sample apps
当DPDK的target环境创建好后(例如 x86_64-native-linuxapp-gcc ), x86_64-native-linuxapp-gcc 目录中会包含编译应用程序的库和头文件。
在编译DPDK应用程序之前,首先需要指定两个环境变量:
在DPDK的 examples 路径下面有许多示例应用,这里以其中的 helloworld 为例进行编译:
DPDK的 examples 路径下面的应用均可以通过这种方式编译,也可以直接在 examples 路径下面运行 make ,将这些应用全都编译好。
在运行应用程序之前,需要确保:
DPDK应用程序与DPDK target环境的环境抽象层EAL(Environmental Abstraction Layer )库相关联,该库提供了一些通用于每个DPDK应用程序的选项。
EAL的详细配置请参照: DPDK EAL参数
可按照下列参数运行 /helloworld :
其中 -l 命令指定cpu cores list是EAL必须的配置;若没有指定 --socket-mem ,则默认会按照预留的hugepages size来分配。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)