摘要
本文对dpdk数据包捕获技术原理进行了分析,对其优缺点进行了介绍。
对使用基于EAL的应用程序进行了分析,作出了程序流程图。
引言
背景浅析
随着云计算产业的异军突起,网络技术的不断创新,越来越多的网络设备基础架构逐步向基于通用处理器平台的架构方向融合,从传统的物理网络到虚拟网络,从扁平化的网络结构到基于 SDN(Software Defined Network)分层的网络结构,无不体现出这种创新与融合。
这在使得网络变得更加可控制和成本更低的同时,也能够支持大规模用户或应用程序的性能需求,以及海量数据的处理。究其原因,其实是高性能网络编程技术随着网络架构的演进不断突破的一种必然结果。
如今,关注的更多是 C10M 问题(即单机 1 千万个并发连接问题)。很多计算机领域的大佬们从硬件上和软件上都提出了多种解决方案。从硬件上,比如说,现在的类似很多 40Gpbs、32-cores、256G RAM 这样配置的 X86 服务器完全可以处理 1 千万个以上的并发连接。
在网络安全领域中,数据包捕获技术的应用十分广泛,入侵检测系统、入侵防御系统、数据库防护系统和信息审计系统等网络安全产品依赖的基础都是数据包捕获技术。网络数据包的实时捕获效率低下是影响数据包捕获技术发展的原因之一。
高性能系统需要在很短的时间内,成功地收集和处理大量数据,目标系统的实时数据需要被收集、管理或控制。在当前高速网络环境中,如何高效、完整、快速捕获数据包是通融准确分析网络数据的基础和网络安全防护系统的关键技术。
面对这些问题,可以从硬件方面实现也可以从软件方面实现。硬件方面需要进行专门的定制,优点是性能较好,基本不会发生丢包的现象,缺点是功能单一,可扩展性差,并且定制价格较高。而软件实现成本低,且实现灵活多样,易于扩展,可以提供一种有效的解决方案。
基于OS内核的数据传输存在的问题
1、中断处理。当网络中大量数据包到来时,会产生频繁的硬件中断请求,这些硬件中断可以打断之前较低优先级的软中断或者系统调用的执行过程,如果这种打断频繁的话,将会产生较高的性能开销。
2、内存拷贝。正常情况下,一个网络数据包从网卡到应用程序需要经过如下的过程:数据从网卡通过 DMA 等方式传到内核开辟的缓冲区,然后从内核空间拷贝到用户态空间,在 Linux 内核协议栈中,这个耗时操作甚至占到了数据包整个处理流程的 57.1%。
3、上下文切换。频繁到达的硬件中断和软中断都可能随时抢占系统调用的运行,这会产生大量的上下文切换开销。另外,在基于多线程的服务器设计框架中,线程间的调度也会产生频繁的上下文切换开销,同样,锁竞争的耗能也是一个非常严重的问题。
4、局部性失效。如今主流的处理器都是多个核心的,这意味着一个数据包的处理可能跨多个 CPU 核心,比如一个数据包可能中断在 cpu0,内核态处理在 cpu1,用户态处理在 cpu2,这样跨多个核心,容易造成 CPU 缓存失效,造成局部性失效。如果是 NUMA 架构,更会造成跨 NUMA 访问内存,性能受到很大影响。
5、内存管理。传统服务器内存页为 4K,为了提高内存的访问速度,避免 cache miss,可以增加 cache 中映射表的条目,但这又会影响 CPU 的检索效率。
综合以上问题,可以看出内核本身就是一个非常大的瓶颈所在。那很明显解决方案就是想办法绕过内核。
探索解决方法
1、控制层和数据层分离。将数据包处理、内存管理、处理器调度等任务转移到用户空间去完成,而内核仅仅负责部分控制指令的处理。这样就不存在上述所说的系统中断、上下文切换、系统调用、系统调度等等问题。
2、使用多核编程技术代替多线程技术,并设置 CPU 的亲和性,将线程和 CPU 核进行一比一绑定,减少彼此之间调度切换。
3、针对 NUMA (Non Uniform Memory Access Architecture)系统,尽量使 CPU 核使用所在 NUMA 节点的内存,避免跨内存访问。
4、使用大页内存代替普通的内存,减少 cache-miss。
5、采用无锁技术解决资源竞争问题。
经很多前辈先驱的研究,目前业内已经出现了很多优秀的集成了上述技术方案的高性能网络数据处理框架,如 6wind、windriver、netmap、dpdk 等,其中,Intel 的 dpdk 在众多方案脱颖而出,一骑绝尘。
DPDK原理分析
DPDK概述
dpdk 为 Intel 处理器架构下用户空间高效的数据包处理提供了库函数和驱动的支持,它不同于 Linux 系统以通用性设计为目的,而是专注于网络应用中数据包的高性能处理。
也就是 dpdk 绕过了 Linux 内核协议栈对数据包的处理过程,在用户空间实现了一套数据平面来进行数据包的收发与处理。在内核看来,dpdk 就是一个普通的用户态进程,它的编译、连接和加载方式和普通程序没有什么两样。
dpdk是一款数据包转发处理套件。首先,它不是网络协议栈,不提供二层、三层转发功能,不具备防火墙ACL功能,但通过dpdk可以很轻松地开发出上述功能。其次,它是一套强大、高度优化的用户空间库和驱动程序,可以帮助用户净控制面和数据面平台进行整合,并在Inter架构的通用处理器上有效地执行数据路径包处理。dpdk主要具有以下技术点:
1.uio:dpdk 能够绕过内核协议栈,本质上是得益于 UIO 技术,通过 UIO 能够拦截中断,并重设中断回调行为,从而绕过内核协议栈后续的处理流程。UIO 设备的实现机制其实是对用户空间暴露文件接口,比如当注册一个 UIO 设备 uioX,就会出现文件 /dev/uioX,对该文件的读写就是对设备内存的读写。除此之外,对设备的控制还可以通过 /sys/class/uio 下的各个文件的读写来完成。
2.内存池:dpdk 在用户空间实现了一套精巧的内存池技术,内核空间和用户空间的内存交互不进行拷贝,只做控制权转移。这样,当收发数据包时,就减少了内存拷贝的开销。
3.大页内存管理:dpdk 实现了一组大页内存分配、使用和释放的 API,上层应用可以很方便使用 API 申请使用大页内存,同时也兼容普通的内存申请。
4.无锁环形队列:dpdk 基于 Linux 内核的无锁环形缓冲 kfifo 实现了自己的一套无锁机制。支持单生产者入列/单消费者出列和多生产者入列/多消费者出列操作,在数据传输的时候,降低性能的同时还能保证数据的同步。
5.poll-mode网卡驱动:DPDK网卡驱动完全抛弃中断模式,基于轮询方式收包,避免了中断开销。
6.NUMA:dpdk 内存分配上通过 proc 提供的内存信息,使 CPU 核心尽量使用靠近其所在节点的内存,避免了跨 NUMA 节点远程访问内存的性能问题。
7.cpu affinity: dpdk 利用 CPU 的亲和性将一个线程或多个线程绑定到一个或多个 CPU 上,这样在线程执行过程中,就不会被随意调度,一方面减少了线程间的频繁切换带来的开销,另一方面避免了 CPU 缓存的局部失效性,增加了 CPU 缓存的命中率。
原理分析
dpdk总体上是一个两层的应用,本来驱动做的事情拿到应用层做了,并且根据系统结构的变化提供了各种各样的优化,一般只用来做I/O操作,当然dpdk也提供了很多三层的API,转发的API、Ipm的API等,功能相当于linux设备的无关接口层,处于socket之下,驱动之上。
另外,dpdk和内核网络栈不是对等的概念,它并没有提供开源的高性能TCP/IP协议栈。
dpdk总体框架图如图所示(使用的试用版亿图画的,竟然还带水印,将就看一眼)。
dpdk框架图" />
底层硬件网络适配器实现对数据的接收。内核态的两个模块中,KNI模块提供传统的网络工具,UIO模块实现将网卡硬件寄存器映射到用户态,在用户空间运行驱动设备。EAL环境抽象层作为dpdk的关键模块,为底层资源的访问提供用户层入口,完成资源的分配及初始化。此外,dpdk核心部件库提供内存池、缓冲区管理、轮询模块、定时等接口,服务于上层应用程序。
dpdk_81">dpdk核心组件
dpdk的核心组件提供了一组涵盖用于开发高性能包处理工具所需全部要素的库中,其核心组件的体系架构如图所示(箭头指向表示调用,如a指向b表示a调用b)。
dpdk核心组件" />
整个体系结构以EAL和libc为核心,为用户提供了包括内存分配、定时器、内存池、缓冲区、ring队列等各种组件。libc在dpdk中主要用于创建Linux用户空间应用。
-
rte_eal + libc:内存的统一组织管理者
-
librte_malloc:为用户提供可用于分配任意尺寸内存的API,它与传统的malloc的区别是,malloc从堆中分配内存,而该函数从HugePages memory中分配内存
-
librte_ring:提供无锁队列,它使用了rte_eal管理的内存
-
librte_mempool:利用rte_eal管理的内存和rte_ring提供内存池的功能,核心使用ring来管理空闲的对象,要求被分配的对象应具有固定的大小
-
librte_mbuf:提供dpdk应用程序数据存储释放操作的一系列接口
-
librte_timer:提供时间操作的接口,主要用于各种服务间同步作用
EAL_99">EAL实现分析
EAL(Environment Abstraction Layer)环境抽象层,提供一种通用的接口,这种接口为应用和库在访问底层资源如硬件和内存时屏幕了具体的环境细节,其提供的主要服务如下:
1.dpdk的加载和启动。dpdk框架和应用被链接为一个单独的应用,需要通过EAL来加载启动
2.多进程和多线程的执行方式。dpdk应用支持以多线程或多进程的方式运行
3.cpu核心的亲和性处理:EAL负责创建执行实例,并提供了将执行单元指派到指定cpu核心的功能
4.系统内存的分配和释放
5.原子操作及锁操作
6.pci总线访问:EAL提供了一个访问PIC地址空间的接口
7.追踪和调试功能:日志,调用栈的dump等
8.cpu特性识别:在运行时确定cpu是否支持特定的特性,同时可以断定cpu是否支持应用所需的特性集合
9.中断处理:负责注册/卸载回调函数到指定的中断源
10.报警操作:负责注册/卸载运行于时间的回调函数
以多线程为例,Liunx环境下的EAL初始化流程如图所示。EAL的初始化共分为三个阶段,第一个阶段是通用功能初始化,包括内存、日志、PCI总线等的初始化。第二个阶段是多线程框架的初始化,dpdk的多线程的实现底层依赖于pthread,在这一阶段EAL会创建一定数量的线程,并根据配置绑定线程到特定的核心。第三个阶段是在各个核心上执行应用初始化代码,初始化完毕后即通过ret_eal_remote_launch(app)方式启动每个核心上的线程。
dpdk初始化启动" />
数据包接收流程分析
数据包接收的整体流程如图所示。
dpdk数据包接收" />
基本流程:
1.函数rte_eal_init(argc, argv)是对dpdk参数的处理,初始化EAL
2.利用函数init_mbuf_pools()初始化dpdk的mbuf与pool队列
3.函数init_port(ports>id[i])来初始化端口配置,查看端口状态
4.rte_eth_rx_burst(port_num, 0, buf, PACKET_READ_SIZE)用来从端口接收数据包,若是多端口收包,使用for循环遍历收包端口接收数据
5.包解析处理实现对每个数据包的解析和处理,用来分析数据包所属类型和端口,并进行队列分发
至此,包处理函数已完成对数据包的物理层、链路层、网络层信息的初步解析,即能够区分出数据包的MAC地址、目的或源IP、端口号、TCP/UDP类型等信息。为了提高数据包应用层数据解析速率,在每个数据包队列分发前需要采用五元组做哈希进行队列区分,这样做达到了数据包并行解析的目的,增加了单位时间内数据包处理个数,提升了系统整体效率。