背景描述
DPDK的引入
对网卡频繁中断问题,提出DMA方式和设备轮询机制,其中NAPI采用“中断加轮询”的方式工作;网卡驱动在中断量较大(设定阈值)时会关闭中断,并自动采用轮询方式手法报文。
针对网络协议栈多次复制和内核上下文切换问题,业界提出了大量零复制(zero-copy)技术,如UIO和PF_Ring。主要思想:用户态进程直接读取网卡缓冲区、旁路网络协议栈。
UIO(userspace I/O)技术通过设置内核态UIO驱动,不仅可以关闭中断,还可以通过内核的MMAP功能将内核空间映射至用户空间,用户通过标准的读写操作实现对网卡缓冲的零收发; PF_RING技术,他在内核定义一个定长的环形缓冲区,避免了标准内核协议栈的处理。
DPDK技术简介
DPDK技术框架可以划分为DPDK基本技术与DPDK优化技术两部分,前者指标准的DPDK数据平面开发包和I/O转发实现技术;后者是在DPDK应用过程中,进一步提高格林用户应用程序的转发性能,所采取的性能调优方法和关键配置。
软件架构
DPDK上层用户态有很多库组成,主要包括核心部件库(core Libraries)、平台相关模块(platform)、网卡轮询模式驱动没款、QoS库、报文转发分类算法等几大类,用户应用程序可以使用这些库进行二次开发。
核心部件库
该模块构成的运行环境建立在Linux上,通过环境抽象层(EAL)的运行环境进行初始化,包括巨页内存分配、内存/缓冲区/队列分配与无锁操作、CPU亲和性绑定等;其次,EAL实现了对操作系统内核与底层网卡I/O操作的屏蔽(I/O旁路了内核及其协议栈),为DPDK应用程序提供了一组调用接口,通过UIO或VFIO技术将PCI设备地址映射到用户空间,方便了应用程序的调用,避免了网络协议栈和内核切换造成的处理时延。另外,核心部件还包括创建适合报文处理的内存池、缓冲区分配管理、内存复制、定时器、环型缓冲区管理等。
b. 平台相关模块
其内部模块主要包括KNI、能耗管理以及IVSHMEM接口。其中,KNI模块主要通过kni.ko模块将数据报文从用户态传递给内核态协议栈处理,以便用户进程使用传统的Socket接口对相关报文进行处理;能耗管理则提供了一些API,应用程序可以根据分组接收速率动态调整处理器频率或进入处理器的不同休眠状态;另外,IVSHMEM模块提供了虚拟机与虚拟机之间,或者虚拟机与主机之间的零复制共享内存机制,当DPDK程序运行时,IVSHMEM模块会调用核心部件库API,把几个巨页映射为一个IVSHMEM设备池,并通过参数传递给QEMU,这样,就实现了虚拟机之间的零复制内存共享。
c. 轮询模式驱动模块
PMD相关API实现了在轮询方式下进行网卡报文收发,避免了常规报文处理方法中因采用中断方式造成的响应时延,极大提升了网卡收发性能。此外,该模块还同时支持物理和虚拟化两种网络接口,从仅仅支持Intel网卡,发展到支持Cisco、Broadcom、Mellanox、Chelsio等整个行业生态系统以及基于KVM、VMware、XEN等虚拟化网络接口。
DPDK技术具有一下特征
● 采用BSD license,保证了可合法用于商业产品。
● 支持Red Hat、Cent OS、Fedora、Ubuntu等大多数Linux系统,已开始进入主流Linux发布版本。
● DPDK支持run to completion和pipeline两种报文处理模式,用户可以依据需求灵活选择,或者混合使用。run to completion是一种水平调度方式,利用网卡的多队列,将报文分发给多个CPU核处理,每个核均独立处理到达该队列的报文,资源分配相对固定,减少了报文在核间的传递开销,可以随着核的数目灵活扩展处理能力;pipeline模式则通过共享环在核间传递数据报文或消息,将系统处理任务分解到不同的CPU核上处理,通过任务分发来减少处理等待时延。
● DPDK的库函数和样例程序十分丰富,包括L2/L3转发、散列、ACL、QoS、环型队列等大量示例供用户参考
巨页技术
为了减少页表的查找过程,Intel 处理器实现了以一块缓存来保存查找结果,这块缓存被称为 TLB (Translation Lookaside Buffer),它保存了虚拟地址到物理地址的映射关系。所有虚拟地址在转换为物理地址以前,处理器会首先在TLB中查找是否已经存在有效的映射关系,如果没有发现有效的映射,也就是TLS miss,处理器再进行页表的查找。页表的查找过程对性能影响极大,因此需要尽量减少TLB miss的发生。
x86处理器硬件在缺省配置下,页的大小是4 KB,但也可以支持更大的页表尺寸,例如2 MB或1 GB的页表。使用了巨页表功能后,一个TLB表项可以指向更大的内存区域,这样可以大幅减少TLB miss的发生。
DPDK则利用巨页技术,所有的内存都是从巨页里分配,实现对内存池(Mempool)的管理,并预先分配好同样大小的mbuf,供每一个数据分组使用。
轮询技术
为了减少中断处理开销,DPDK使用了轮询技术来处理网络报文。网卡收到报文后,直接将报文保存到处理器缓存中(有DDIO(Direct Data I/O)技术的情况下),或者内存中(没有DDIO技术的情况下),并设置报文到达的标志位。应用软件则周期性地轮询报文到达的标志位,检测是否有新报文需要处理。整个过程中完全没有中断处理过程,因此应用程序的网络报文处理能力得到极大的提升。
cpu亲和技术
CPU 亲和技术,就是将某个进程或者线程绑定到特定的一个或者多个核上执行,而不被迁移到其他核上,这样就保证了专用程序的性能。
DPDK性能影响因素
DPDK进行应用开发和环境配置时,应用程序性能的影响因素以及相应的优化调整方法。这些因素并非必然地劣化性能,可能因硬件能力、OS版本、各类软硬环境参数配置等的差异产生较大波动,或者存在较大的不稳定性,相关的调优方法需要用户结合自身的VNF应用部署在实践中不断完善。
硬件结构
为了实现DPDK应用程序的零分组丢失,需要对操作系统进行适当的配置,减少操作系统对DPDK应用程序的干扰。操作系统配置的总体思路如下:
● 将DPDK应用运行的处理器核进行隔离,减少干扰;
● 停用不需要的后台服务程序,将不需要的中断转移到其他处理器核上处理;
● 对于不能转移的中断,减少中断的次数。
内存管理
DPDK考虑了NUMA以及多内存通道的访问效率,会在系统运行前要求配置Linux的巨页,初始化时申请其内存池作为DPDK运行的主要内存资源。Linux巨页机制利用了处理器核上的的TLB的巨页表项,这可以减少内存地址转换的开销。
内存复制:
很多libc的API都没有考虑性能问题,因此,建议不要在高性能数据平面上使用libc提供的API,比如memcpy()或strcpy()。虽然DPDK也使用了很多libc的API,但均只是在软件配置方面用于方便程序移植和开发。
DPDK提供了一个优化版本的rte_memcpy() API,它充分利用了Intel的SIMD指令集,也考虑了数据的对齐特性和缓存操作友好性。
内存分配:
在某些情况下,应用程序使用libc提供的动态内存分配机制是必要的,如malloc()函数,它是一种灵活的内存分配和释放方式。但是,因为管理零散的堆内存代价昂贵,并且这种内存分配器对于并行的请求分配性能不佳,所以不建议在数据平面处理上使用类似malloc()的函数进行内存分配。
在有动态分配的需求下,建议使用DPDK提供的rte_malloc() API,该API可以在后台保证从本NUMA节点内存的巨页里分配内存,并实现cache line对齐以及无锁方式访问对象等功能。
NUMA架构
NUMA(Non Uniform Memory Access,非统一内存访问)架构与SMP(Symmetric Multi Processing,对称多处理)架构是两种典型的处理器对内存的访问架构。随着处理器进入多核时代,对于内存吞吐量和时延性能有了更高的要求,NUMA 架构已广泛用于最新的英特尔处理器中,为每个处理器提供分离的内存和内存控制器,以避免SMP架构中多个处理器同时访问同一个存储器产生的性能损失。
DPDK库函数
EAL(Environment Abstraction Layer,环境抽象层)用于获取底层资源(如硬件和内存空间)。EAL提供了一个通用接口来屏蔽应用和库的环境特殊性,同时也负责为初始化工作分配资源(内存空间、PCI设备、时钟、控制等)。
EAL主要提供以下几种典型服务:
内核初始化与启动
内核初始化和启动在函数rte_eal_init()中完成
EAL 主要由 pthread 库的调用组成,主要库函数有 pthread_self()、pthread_create()和pthread_setaffinity_np()。
内存
物理内存映射是EAL的特点。物理内存可以是不连续的,EAL通过描述符来描述内存,每一个描述符表示一个连续的内存分配。具体来说,这些内存区是为物理内存的连续分配预留的,当内存是预留状态时,这些内存区通过唯一的名字指针来标识。
多线程与亲和性
有两个公共线程API供使用,它们是:rte_thread_set_affinity()和rte_pthread_get_affinity()。使用API时,需要设置或者获取TLS(Thread Local Storage),它主要用于存储CPU位映射以及CPU的NUMA节点信息。
Ring库
DPDK中的Ring用于队列管理,主要具有以下几个属性:
Mempool库
DPDK的Mempool(内存池)库主要供mbuf(内存缓冲区)库和EAL使用[3]。Mempool是一个大小固定的对象,在DPDK中,Mempool使用字符串名字进行标识,通过Ring方式存储对象。Mempool库也提供了一些可选服务(例如核对象缓存和校准助手),确保新添加的对象被均匀地分布在所有的DRAM或者DDR3通道上。
mbuf库
mbuf(内存缓冲区)库用来分配和释放缓冲区(DPDK应用存储消息的缓冲区),该缓冲区存储在内存池(Mempool)中,使用Mempool库。Mempool库中的数据结构rte_mbuf是承载网络数据分组的缓冲区或一般的控制区(用CTRL_MBUF_FLAG来标识),此数据结构也可以扩展为其他类型。
数据存储
存储数据分组数据有下面两个方法。
PMD驱动
PMD(Poll Mode Driver 轮询模式驱动)
PMD由API组成,运行在用户空间,可以配置设备以及对应的队列。一个PMD可以无中断地直接访问RX和TX,进而快速的接收、处理和转发数据分组。
需求与设计
DPDK环境下的数据分组处理有两种模式:run-to-completion和pipe-line。
指定端口的RX ring(可以理解为分组接收队列)被选中接收数据分组,然后数据分组在相同的核上进行处理并放置在该端口TX ring(可以理解为分组发送队列)上,通过API进行转发。
在run-to-completion这种同步模式下,每一个分配给DPDK的逻辑核循环执行数据分组处理,步骤如下:
一个核选中一个或多个端口RX ring,数据分组接收后,通过一个ring传递给另一个核进行处理,处理完成后将数据分组放置在该端口TX ring上,通过API进行转发。
循环分组接收处理步骤如下:
循环处理数据分组步骤如下:
Timer库
Timer(定时器)库为DPDK执行单元提供了计时器服务,用于保证执行单元能够执行异步回调功能。Timer库的主要特征如下。
(1)定时器的使用频率可以是周期性或一次性的。
(2)定时器可以从一个核装载,在另外一个核执行。
(3)定时器可以提供较高的精准度。
(4)如果应用程序没有使用定时器的需要,则定时器应设置为关闭状态以保证性能。
定时器结构主要包含一个特殊的状态机,此状态机用于定时器状态切换(包括定时器停止、增加、运行、配置等),且为所属者(逻辑核ID)唯一的状态机。
对状态机的各个状态说明如下。
(1)STOPPED:没有所属者,也不存在于列表中。
(2)CONFIG:属于一个核,而且不能被另外的核修改,也许存在一个列表中,也许不存在,这主要依赖于前期状态。
(3)PENDING:属于一个核,存在于一个列表内。
(4)RUNNING:属于一个核,不能被另外的核修改,存在于一个列表内。
多进程支持
EAL允许生成两个DPDK进程,每个进程具有不同的巨页内存权限。
● 主进程:可以初始化共享内存,拥有所有共享内存的管理权限。
● 辅进程:不能初始化共享内存,但是可以连接预初始化的共享内存并在该内存中创建对象。
独立的DPDK进程是主进程,辅进程仅仅是主进程的辅助进程,或主进程已经为辅进程配置了巨页共享内存。
命令行参数如下。
● proc-type:进程类型,primary或secondary。
● file-prefix:进程不同步非共同内存区域。