DPDK(Data Plane Development Kit)是数据平面开发工具包,由用于加速在各种CPU架构上运行的数据包处理的库组成。
在Linux上捕获数据包有多种方式,常见的有libpcap,pf-ring等。DPDK以高性能著称,想必相比传统的数据包捕获方式,一定有其独到之处。
本文主要就DPDK所使用的技术点进行宏观的说明,并将其与libpcap,pf-ring进行对比,若有写的不对的地方请帮忙指出。
参考文档:
[1]绝对干货!初学者也能看懂的DPDK解析
[2]Linux 设备驱动之 UIO 机制(基本概念)
[3]PF_RING学习笔记
若出现侵权请联系作者删除。
1.DPDK 技术特点
传统的数据包捕获瓶颈往往在于Linux Kernel,数据流需要经过Linux Kernel,就会带来Kernel Spcae和User Space数据拷贝的消耗;系统调用的消耗;中断处理的消耗等。
DPDK针对Linux Kernel传统的数据包捕获模式的问题,进行了一定程度的优化。DPDK的优化可以概括为:
- UIO+mmap 实现零拷贝(zero copy)
- UIO+PMD 减少中断和CPU上下文切换
- HugePages 减少TLB miss
- 其他代码优化
2.UIO
2.1 UIO简介
UIO(Userspace I/O)是运行在用户空间的I/O技术。Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可,而UIO则是将驱动的很少一部分运行在内核空间,而在用户空间实现驱动的绝大多数功能。
一个设备驱动的主要任务有两个:
- 存取设备的内存
- 处理设备产生的中断
如上图所示,对于存取设备的内存 ,UIO 核心实现了mmap()
;对于处理设备产生的中断,内核空间有一小部分代码用处理中断,用户空间通过read()
接口/dev/uioX
来读取中断。
2.2 DPDK UIO机制
采用Linux提供UIO机制,可以旁路Kernel,将所有报文处理的工作在用户空间完成。
如上图,左边是传统的数据包获取方式,路径为:
网卡 -> Kernel驱动 -> Kernel TCP/IP协议栈 -> Socket接口 -> 业务
右边是DPDK的方式,基于UIO。数据路径为:
网卡 -> DPDK轮询模式-> DPDK基础库 -> 业务
2.3 DPDK 零拷贝(Zero Copy)
通过UIO+mmap 实现数据零拷贝(zero copy)是DPDK的特点之一。那么常规的Linux Kernel(libpcap)以及pf-ring处理数据包的流程要进行几次数据拷贝呢?
libpcap
libpcap采用的是传统的数据包获取方式,如上图左边路径。
网卡接收到数据包后,第一步需要把数据从网卡拷贝到主存(RX buffer)中。但是在这个拷贝的过程中使用了DMA(Direct Memory Access)技术。DMA 传输将数据从一个地址空间复制到另外一个地址空间。由CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成,CPU是不需要参与,也不会产生CPU资源消耗。因此这个第一步的拷贝可以忽略。
第二步,Kernel会将网卡通过DMA传输进入主存(RX buffer)的内容拷贝一份进行处理,这里进行了一次数据拷贝。
第三步,由于内核空间和用户空间的内存是不共享的,Kernel会将数据包内容再进行一次拷贝用于用户空间使用。
综上,传统的数据包捕获方式(libpcap)会进行两次数据包拷贝。pf-rfing
pf-ring采用mmap()
将传统的两次拷贝减少至一次拷贝。
第一步,同样使用DMA技术,把数据从网卡拷贝到主存(RX buffer)中。
第二步,pf-ring会将网卡通过DMA传输进入主存(RX buffer)的内容拷贝一份放入环形缓冲区中(ring)中,这里进行了一次数据拷贝。
第三步,使用mmap()
将环形缓冲区映射至用户空间,用户空间可以直接访问这个环形缓冲区中的数据。-
pf-ring zc
pf-ring zc(zero copy)更是将pf-ring的一次拷贝也省去,达到了零拷贝的目的,具体的:
第一步,使用DMA技术,把数据从网卡拷贝到主存(RX buffer)中。
第二步,使用mmap()
直接将RX buffer的数据映射到用户用户空间,使用户空间可以直接访问RX buffer的数据。
下图非常清晰地展示了pf-ring zc实现零拷贝的过程:
DPDK
DPDK实现零拷贝的方式与pf-ring zc类似,首先通过DMA将网卡数据拷贝至主存(RX buffer);随后使用mmap()
直接将RX buffer的数据映射到用户用户空间,使用户空间可以直接访问RX buffer的数据,以此实现了零拷贝。
由上述可以看出,pf-ring zc和dpdk均可以实现数据包的零拷贝,两者均旁路了内核,但是实现原理略有不同。pf-ring zc通过zc驱动(也在应用层)接管数据包,dpdk基于UIO实现。
除了UIO之外,dpdk还支持VFIO。关于VFIO的介绍可以参考下面链接:
https://kernelgo.org/vfio-introduction.html
值得注意的是,无论是pf-ring zc还是dpdk,旁路内核就意味着应用程序完全接管网卡,这样一来,当通过UIO驱动或ZC驱动打开设备时,设备将无法用于标准网络。
3.PMD
DPDK的UIO驱动屏蔽了硬件发出中断,然后在用户态采用主动轮询的方式,这种模式被称为PMD(Poll Mode Driver)。
正常情况下,网卡收到数据包后,会发出中断信号,CPU接收到中断信号后,会停止正在运行的程序,保护断点地址和处理机当前状态,转入相应的中断服务程序并执行中断服务程序,完成后,继续执行中断前的程序。
DPDK采用轮询的方式,直接访问RX和TX描述符而没有任何中断,以便在用户的应用程序中快速接收,处理和传送数据包。这样可以减少CPU频繁中断,切换上下文带来的消耗。
在网卡大流量时,DPDK这种PMD的方式会带来较大的性能提升;但是在流量小的时候,轮询会导致CPU空转(不断轮询),从而导致CPU性能浪费和功耗问题。
为此,DPDK还推出了Interrupt DPDK模式,它的原理和Linux的NAPI很像,就是没包可处理时进入睡眠,改为中断通知。并且可以和其他进程共享同个CPU Core,但是DPDK进程会有更高调度优先级。
与DPDK相比,pf-ring(no zc)使用的是NAPI polling和应用层polling,而pf-ring zc与DPDK类似,仅使用应用层polling。
4.HugePages
Linux系统默认采用4KB作为页的大小(page size),页的大小越小,设备的内存越大,页面的个数就越多,页表的开销就越大,页表的内存占用也越大。
在操作系统引入MMU(Memory Management Unit)后,CPU读取内存的数据需要两次访问内存。第一次要查询页表将逻辑地址转换为物理地址,然后访问该物理地址读取数据或指令。
为了减少页数过多,页表过大而导致的查询时间过长的问题,便引入了TLB(Translation Lookaside Buffer),可翻译为地址转换缓冲器。TLB是一个内存管理单元,一般存储在寄存器中,里面存储了当前最可能被访问到的一小部分页表项。
引入TLB后,CPU会首先去TLB中寻址,由于TLB存放在寄存器中,且其只包含一小部分页表项,因此查询速度非常快。若TLB中寻址成功(TLB hit),则无需再去RAM中查询页表;若TLB中寻址失败(TLB miss),则需要去RAM中查询页表,查询到后,会将该页更新至TLB中。
而DPDK采用HugePages ,在x86-64下支持2MB、1GB的页大小,大大降低了总页个数和页表的大小,从而大大降低TLB miss的几率,提升CPU寻址性能。
5. 其他代码优化
目前对DPDK的代码研究还不是很深入,这里摘录自参考文档1。
- SNA(Shared-nothing Architecture)
软件架构去中心化,尽量避免全局共享,带来全局竞争,失去横向扩展的能力。NUMA体系下不跨Node远程使用内存。
- SIMD(Single Instruction Multiple Data)
从最早的mmx/sse到最新的avx2,SIMD的能力一直在增强。DPDK采用批量同时处理多个包,再用向量编程,一个周期内对所有包进行处理。比如,memcpy就使用SIMD来提高速度。
SIMD在游戏后台比较常见,但是其他业务如果有类似批量处理的场景,要提高性能,也可看看能否满足。
不使用慢速API
cpu affinity
编译执行优化