目录
1.什么是dpdk
2.dpdk的优势
2.1.传统协议栈交互模型
2.2.dpdk交互模型
3.dpdk基本原理
4.编译dpdk
4.1.源码
4.2.环境准备
4.3.编译
4.4.打包生成动态库
5.dpdk性能优化
5.1.轮询驱动
5.2.大页内存
5.3.CPU核亲缘性和独占
5.4.cache对齐
5.5.NUMA
5.6.内存池管理
1.什么是dpdk
DPDK(Data Plane Development Kit)是一个开源的高速数据包处理框架,专为高性能网络应用设计。它提供了一组用于处理数据包的库,旨在提高网络设备与处理器之间的数据传输速度,减少延迟,并且使得处理网络数据更加高效。
2.dpdk的优势
2.1.传统协议栈交互模型
首先,我们需要了解网络分层模型。OSI 模型中将网络划分为七层,从下到上依次为:物理层(Physical)、数据链路层(Date Link)、网络层(Network)、传输层(Transport)、会话层(Session)、表示层(Presentation)和应用层(Application)。目前实际广泛使用的 TCP/IP 协议框架体系内,我们一般将网络划分为五层,从下到上依次为:物理层、数据链路层、网络层、传输层和应用层,下层协议对上层协议透明,即上层协议无需关注下层协议的实现逻辑和机制。
Linux内核网络协议栈涉及其中三层,分别为数据链路层、网络层和传输层,不涉及物理层,应用层的任务也是由用户空间程序来实现的。Linux内核网络协议栈的任务:
1、接收数据包时:Linux内核网络协议栈将接收到的数据包从网络设备驱动程序传递给网络层(通常为IPv4或IPv6)。接下来,如果数据包目的地为当前设备,Linux内核网络协议栈就将其传递给传输层(应用TCP或UDP协议侦听套接字);如果数据包需要转发,就将其交还给数据链路层进行传输。
2、发送数据包时:Linux内核网络协议栈将本地生成的出站数据包,从传输层依次传递给网络层和数据链路层,再由网络设备驱动程序进行传输。
2.2.dpdk交互模型
由于包处理任务存在内核态与用户态的切换,以及多次的内存拷贝,系统消耗变大,以CPU为核心的系统存在很大的处理瓶颈。为了提升数据包处理效能,Intel推出了服务于IA(Intel Architecture)系统的DPDK技术。
DPDK是Data Plane Development Kit的缩写。简单说,DPDK应用程序运行在操作系统的User Space,利用自身提供的数据面库进行收发包处理,绕过了Linux内核态协议栈,以提升报文处理效率。
DPDK 能够绕过内核协议栈,本质上是得益于 UIO 技术,通过 UIO 能够拦截中断,并重设中断回调行为,从而绕过内核协议栈后续的处理流程。UIO 设备的实现机制其实是对用户空间暴露文件接口,比如当注册一个 UIO 设备 uioX,就会出现文件 /dev/uioX,对该文件的读写就是对设备内存的读写。除此之外,对设备的控制还可以通过 /sys/class/uio 下的各个文件的读写来完成,实际上就是将网卡设备暴露在用户态,网卡通过UIO进行内存映射,使得DPDK应用可以直接访问网卡的内存区域,绕过内核协议栈进行数据包的处理。通过这种方式,DPDK可以在用户空间快速处理大量的网络数据包。
3.dpdk基本原理
DPDK采用了轮询或者轮询混杂中断的模式来进行收包和发包,此前主流运行在操作系统内核态的网卡驱动程序基本都是基于异步中断处理模式。
1、异步中断模式
当有包进入网卡收包队列后,网卡会产生硬件 (MSIX/MSI/INTX)中断,进而触发CPU中断,进入中断服务程序,在 中断服务程序(包含下半部)来完成收包的处理。当然为了改善包处理 性能,也可以在中断处理过程中加入轮询,来避免过多的中断响应次 数。总体而言,基于异步中断信号模式的收包,是不断地在做中断处 理,上下文切换,每次处理这种开销是固定的,累加带来的负荷显而易 见。在CPU比I/O速率高很多时,这个负荷可以被相对忽略,问题不 大,但如果连接的是高速网卡且I/O频繁,大量数据进出系统,开销累 加就被充分放大。中断是异步方式,因此CPU无需阻塞等待,有效利用 率较高,特别是在收包吞吐率比较低或者没有包进入收包队列的时候, CPU可以用于其他任务处理。
当有包需要发送出去的时候,基于异步中断信号的驱动程序会准备 好要发送的包,配置好发送队列的各个描述符。在包被真正发送完成 时,网卡同样会产生硬件中断信号,进而触发CPU中断,进入中断服务 程序,来完成发包后的处理,例如释放缓存等。与收包一样,发送过程 也会包含不断地做中断处理,上下文切换,每次中断都带来CPU开销; 同上,CPU有效利用率高,特别是在发包吞吐率比较低或者完全没有发 包的情况。
2、轮询模式
DPDK采用轮询模式驱动(PMD, Poll Mode Driver)。PMD由用户空间的特定的驱动程序提供的API组成,用于对设备和它们相应的队列进行设置。抛弃了基于中断的异步信号发送机制为该架构带来很大的开销节省。避免中断性能瓶颈是DPDK提升数据包处理速度的关键之一。
DPDK环境为数据包处理应用考虑了两种模型:运行至完成(run-to-completion)模型和管道(pipeline)模型。在运行至完成模型中,一个API向某个特定端口的接收描述符环轮询以接收数据包。接着这个数据包在同一个核上被处理,之后被一个发送用API放到端口的传输描述符环上;在管道模型中,一个核心会通过API对一个或多个端口的接收描述符环进行轮询,数据包通过环被接收和传递给另一个核心,然后在这个核心上被处理,之后可能被发送用API放到端口的传输描述符环上。
收包流程:dpdk轮询驱动程序负责初始化好每一个收包描述符,将缓存区物理地址填充到描述符对应的位置,以及将接收成功的标志复位。然后队列管理器通过寄存器通知网卡有哪些队列已经准备好了,等待接收报文。网卡收到消息后,读取空闲收包描述符得知缓存区地址,将报文内容填充到缓存区中,如果一个缓存不能存下报文内容就会用多个缓存来存。重点来了,填充完成之后一定要设置一个标记收包成功的标志。这个标记是干什么呢,这个标记是用来告诉cpu,我的活干完了,已经根据你的要求把报文内容放到指定位置了,剩下就该你进行表演了。cpu为了能够及时处理到这个成功收包的标记,使用一个线程去轮询收包队列中的描述符是否已经收包成功。dpdk驱动程序检查到收包成功的标志之后,这时候驱动程序会解析相应的收包描述符,提取各种有用的信息,然后填充对应的缓冲内存块头部。这个内存块也就是mbuf。然后把mbuf存到发包函数数组中,接着再分配一个mbuf以备下一个报文使用。
总结:收包过程:dpdk驱动程序准备好接收报文的缓存之后-->通知网卡-->网卡填充缓存-->设置成功收包标记-->cpu解析内容将信息填充到mbuf
发包流程:每一个发包队列都有一个单独的线程去设 置需要发出去的包。dpdk的驱动根据缓存中的内容提取有用的信息,比如报文的长度,地址,校验和等,然后初始化给一个发包队列中的描述符。这里边涉及两个重要的标记位,EOP和RS.。dpdk驱动程序检查到收包成功的标志之后,这时候驱动程序会解析相应的收包描述符,提取各种有用的信息,然后填充对应的缓冲内存块头部。每当驱动程序设置好相应的发包描述符,硬件就可以开始根据发包描述符的内容来发包,那么驱动程序可能会需要知道什么时候发包完成,然后回收占用的发包描述符和内存缓冲块。RS就是用来由驱动程序来告诉网卡硬件什么时候需要报告发送结果的一个标志。发包的轮询就是轮询发包结束的硬件标志位。DPDK驱动程序根据需要发送的包的信息和内容,设置好相应的发包描述符,包含设置对应的RS标志,然后会在发包线程里不断查询发包是否结束。只有设置了RS标志的发包描述符,网卡硬件才会在发包完成时以写回的形式告诉发包结束。
4.编译dpdk
4.1.源码
github源码链接:GitHub - DPDK/dpdk: Data Plane Development Kit
4.2.环境准备
这里可以直接参照dpdk相关的开发手册,里面明确了编译和运行环境的必要条件。
dpdk用户手册链接:Getting Started Guide for Linux — Data Plane Development Kit 25.03.0-rc0 documentationhttp://doc.dpdk.org/guides/linux_gsg/ 参考步骤:
1.安装meson:
apt-get purge meson //卸载原有meson
sudo apt-get install python3 python3-pip
sudo pip3 install meson
2.安装ninja
sudo apt install ninja-build
3.安装pyelftools
pip3 install pyelftools
注:如果没有安装numa相关的安装包,需对照开发手册安装
4.3.编译
这里以dpdk-v21.11版本为例进行编译:
首先下载对应分支的源码,然后进入dpdk目录,并创建build目录,然后设置编译参数编译。
mkdir build
meson setup -Ddefault_library=static -Dmax_ethports=16 -Dmax_lcores=32 -Dbuildtype=debug -Dexamples=l2fwd -Dplatform=generic build
cd build
ninja
等待编译完成,会在build/lib目录生成相关的静态库和动态库文件。注:具体的编译参数可使用meson configure命令查看。常用的编译命令有buildtype(设置编译类型,debug or release)、max_ethports(支持的最大网口数量)、max_lcores(支持的最大核心数)、max_numa_nodes(支持的最大numa数量)、platform(编译的平台native or generic)、examples(需要编译的示例程序)、default_library(静态库or动态库)
4.4.打包生成动态库
编译完成后可将所有的静态库文件打包成一个动态库.so文件链接使用,注:一定要将drivers目录下的静态库一起打包,否则后续链接使用无法识别到网口。
gcc -shared -lrt -ldl -lm -lpthread -Wl,--whole-archive *.a -lpthread -ldl -lpcap -lnuma -ljansson -lelf -lm -latomic -lcrypto -libverbs -lmlx5 -lmlx4 -Wl,--no-whole-archive -o libdpdk.so
5.dpdk性能优化
5.1.轮询驱动
针对网卡实现了基于轮询方式的PMD
(Poll Mode Drivers
)驱动,该驱动由API
、用户空间运行的驱动程序构成,该驱动使用 无中断方式直接操作网卡的接收和发送队列(除了链路状态通知仍必须采用中断方式以外)。目前PMD
驱动支持Intel
的大部分1G
、10G
和40G
的网卡。PMD
驱动从网卡上接收到数据包后,会直接通过DMA
方式传输到预分配的内存中,同时更新无锁环形队列中的数据包指针,不断轮询的应用程序很快就能感知收到数据包,并在预分配的内存地址上直接处理数据包,这个过程非常简洁。如果要是让Linux来处理收包过程,首先网卡通过中断方式通知协议栈对数据包进行处理,协议栈先会对数据包进行合法性进行必要的校验,然后判断数据包目标是否本机的socket,满足条件则会将数据包拷贝一份向上递交给用户socket来处理,不仅处理路径冗长,还需要从内核到应用层的一次拷贝过程。
5.2.大页内存
第一是使用hugepage
的内存所需的页表项比较少,对于需要大量内存的进程来说节省了很多开销,像oracle
之类的大型数据库优化都使用了大页面配置;第二是TLB
冲突概率降低,TLB
是cpu
中单独的一块高速cache
,采用hugepage
可以大大降低TLB miss
的开销。
5.3.CPU核亲缘性和独占
多核则是每个CPU
核一个线程,核心之间访问数据无需上锁。为了最大限度减少线程调度的资源消耗,需要将Linux
绑定在特定的核上,释放其余核心来专供应用程序使用。 同时还需要考虑CPU
特性和系统是否支持NUMA
架构,如果支持的话,不同插槽上CPU
的进程要避免访问远端内存,尽量访问本端内存。避免不同核之间的频繁切换,从而避免cache miss和cache write back;
避免同一个核内多任务切换开销
5.4.cache
对齐
这也是程序开发中需要关注的。Cache line
是CPU从内存加载数据的最小单位,一般L1 cache
的cache line
大小为64字节。如果CPU访问的变量不在cache
中,就需要先从内存调入到cache,调度的最小单位就是cache line
。因此,内存访问如果没有按照cache line
边界对齐,就会多读写一次内存和cache
了。
5.5.NUMA
NUMA
来源于AMD Opteron
微架构,处理器和本地内存之间有更小的延迟和更大的带宽;每个处理器还可以有自己的总线。处理器访问本地的总线和内存时延迟低,而访问远程资源时则要高。
5.6.内存池管理
dpdk提供了完善的内存池管理技术,在多核系统中,DPDK的内存池管理特别注重避免多核之间的内存访问冲突和锁竞争。为了减少锁的使用和同步开销,DPDK采用了 per-core(每个核心有自己的内存池)或 local cache(局部缓存)机制,即每个CPU核心都维护一份自己的内存池缓存,从而避免了多个核心争夺同一内存池的资源。只有在内存池中没有空闲块时,核心才会访问全局内存池进行分配。这种方式有效减少了竞争条件和加锁的开销,提升了性能。
注:以上只是部分优化手段,实际上dpdk对性能优化不止这些,还包括无锁环形队列等。