# 【NO.594】一篇文章读懂dpdk——dpdk原理详解

## 1.DPDK特点

DPDK全称为Date planedevelopment kit，是一个用来进行包数据处理加速的软件库。与传统的数据包处理相比，DPDK具有以下特点：

\1) 轮询：在包处理时避免中断上下文切换的开销，

\2) 用户态驱动：规避不必要的内存拷贝和系统调用，便于快速迭代优化

\3) 亲和性与独占：特定任务可以被指定只在某个核上工作，避免线程在不同核间频繁切换，保证更多的cache命中

\4) 降低访存开销：利用内存大页HUGEPAGE降低TLB miss，利用内存多通道交错访问提高内存访问有效带宽

\5) 软件调优：cache行对齐，预取数据，多元数据批量操作

## 2.DPDK框架

![img](https://pic4.zhimg.com/80/v2-fe293ceb86fb21a3b39b065918e5f5e7_720w.webp)

图1 DPDK框图

在上图中，核心库Core Libs提供系统抽象、大页内存、缓存池、定时器及无锁环等基础组件。

PMD库，提供全用户态驱动，以便通过轮询和线程绑定得到极高网络吞吐，支持各种本地和虚拟网卡。

Classify库，支持精确匹配，最长匹配和通配符匹配，提供常用包处理的查表操作。

Qos库，提供网络服务质量相关组件，限速和调度。

Mellanox DPDK中保留了Linux内核态驱动，框图如下：

![img](https://pic4.zhimg.com/80/v2-61f413e987d620d9949a42c6c784265f_720w.webp)

图2 MellanoxDPDK框图

Mellanox DPDK在用户空间使用PMD驱动，与网卡之间有两条路径，控制路径使用user verbs，经过内核，用于对象的创建、初始化、修改、查询和释放。数据路径之间访问网卡，进行数据的收发。

Mellanox DPDK与传统的Linux内核态驱动可以共存，当前未被DPDK使用的端口可以通过Linux网络协议栈进行报文收发。

![img](https://pic2.zhimg.com/80/v2-6559edfae77641ebf0279ced3be03ab5_720w.webp)

图3 MellanoxDPDK与传统内核态驱动

## 3.Hugepage配置

DPDK中，如果有多个核可能需要同时访问一个网卡，那DPDK中会为每个核准备一个单独的接收队列/发送队列，这样避免了竞争，也避免了cache一致性问题。

一般的常规页大小为4K字节，使用大页时页大小设置为2M或1G字节。修改方式如下：

Bashrc文件内添加：

非NUMA系统

Echo 1024> /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

预留了1024个2M大页，即2GB内存。

NUMA系统

Echo 1024>/sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages

Echo 1024>/sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

上述命令为每个节点各预留了1024个2M大页，即4GB内存。

/etc/fstab内添加

挂载2M大页：Nodev /mnt/huge hugetlbfs defaults 0 0

挂载1G大页：Nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0

## 4.多线程配置

DPDK线程基于pthread接口创建，属于抢占式线程模型，受内核调度支配。通过在多核设备上创建多个线程，每个线程绑定到单独的核上，减少线程调度的开销，以提高性能。控制线程一般绑定到MASTER核上，接受用户配置，并传递配置参数给数据线程等；数据线程分布在不同核上处理数据包。

DPDK中初始化及执行任务分发示例如下图所示：

![img](https://pic2.zhimg.com/80/v2-0b5890c264fa87cf542cab9804eb5555_720w.webp)

图4 lcore初始化及执行任务分发

上图中一共使用了三个cpu核，master core负责进行任务初始化和分发，两个lcore执行任务。

## 5.转发模型

DPDK转发框架分为run tocompletion模型和pipeline模型，对比图如下：

![img](https://pic4.zhimg.com/80/v2-7ca8453946b03bcec05310837f2747c3_720w.webp)

图5 DPDK转发模型对比

DPDK run to completion模型中每个报文的生命周期只能在一个线程中出现，每个物理核都负责处理整个报文的生命周期从RX到TX。

DPDK pipeline模型中不同的工作交给不同的模块，每一个模块只单独处理特定的事务，各模块之间有输入输出，通过这些输入输出连接起来，完成复杂的网络功能。

## 6.内存管理

DPDK将内存封装在Mbuf结构体内，Mbuf主要用来封装网络帧缓存，所有的应用使用Mbuf结构来传输网络帧。对网络帧封装和处理时，将网络帧元数据和帧本身存放在固定大小的同一段缓存中，网络帧元数据的一部分内容由DPDK的网卡驱动写入，结构如下图：

![img](https://pic4.zhimg.com/80/v2-504628d4dffaa17e9633ae652d5c2513_720w.webp)

图6 单帧Mbuf结构

上图中，head room用来存储和系统中其他实体交互的信息，如控制信息、帧内容、事件等。Head room长度由RTE_PKTMBUF_HEADROOM定义，默认为128.

Rte_mbuf结构对象存放在内存池中，内存池使用环形缓存区来保存空闲对象，逻辑结构如下图所示：

![img](https://pic4.zhimg.com/80/v2-c1afca9627570b5ae3f7a00c8dab38bf_720w.webp)

图7 内存池双环形缓存区结构

当一个网络帧被网卡接收时，DPDK网卡驱动将其存储在一个高效的环形缓冲区中，同时Mbuf的环形缓存区中创建了一个Mbuf对象。Mbuf对象被创建好后，网卡驱动根据分析出的帧信息将其初始化，并将其和实际帧对象逻辑相连，对网络帧的分析处理都集中在Mbuf，仅在必要的时候访问实际网络帧。

## 7.性能优化

### **7.1 收发包流程概述**

DPDK中普遍采用纯轮询模式进行数据包收发，所有的收发包有关的中断在物理端口初始化的时候都会关闭。物理端口上的每一个收包队列，都会有一个对应的由收包描述符组成的软件队列来进行硬件和软件的交互。

DPDK驱动程序负责初始化好每一个收包描述符，其中包含把包缓冲内存块的物理地址填充到收包描述符对应的位置，并把对应的收包成功标志复位，通知网卡硬件把收到的包进行填充。网卡硬件将收到的包一一填充到对应的收包描述符表示的缓冲内存块中，将必要信息填充到收包描述符内，标记好收包成功标志。若一个缓冲区内存块大小不够存放一个完整数据包时，可能需要多个收包描述符来处理一个包。

针对每一个收包队列，DPDK都会有一个对应的软件线程负责轮询其中的收包描述符收包成功的标志位。当发现一个收包描述符的收包成功标志被硬件置位了，意味着有包进入网卡并且已经存储到描述符对应的缓冲区内存块中。驱动程序将解析相应的收包描述符，填充对应缓冲内存块头部，将收包缓冲内存块存放到收包函数提供的数组中，同时分配好一个新的缓冲内存块给这个描述符，以便下一次收包。

针对每一个发包队列，DPDK都有一个对应的软件线程负责设置需要发送出去的包，DPDK驱动程序负责提取发包缓冲内存块的有效信息，根据内存缓存块中包的内容来负责初始化好每一个发包描述符，驱动程序将每个包翻译为一个或者多个发包描述符内能够理解的内容，写入发包描述符。当驱动程序设置好相应的发包描述符，硬件就开始依据发包描述符内容来发包，驱动程序通过获取发包描述符内的RS发送状态位来决定发包是否结束以及对发包描述符和内存缓冲块进行回收。发包轮询就是轮询发包结束的硬件标志位，DPDK驱动程序会在发包线程内不断查询发包是否结束，网卡根据RS以写回方式通知发包结束。

### **7.2 Burst收发包**

Burst为一次完成多个数据包的收发，通过把收发包复杂的处理过程进行分解，打散成不同的相对较小的处理阶段，把相邻数据访问、相似的数据运算集中处理，尽可能减少对内存或者低一级的处理器缓存的访问次数。网卡收发包描述符一般为16字节或32字节，处理器缓存基本单位为64字节，可以存放2个或4个描述符，处理器预期机制会每次存取相邻多个缓存，若一次处理4个缓存单元，则可以更新16个描述符，Burst就可以设置为16.目前常用的Burst为32.

### **7.3 平台优化配置**

硬件：

PCIe Gen3 x 8,x16.

网卡多队列收发包，支持收包RSS时分发进多个队列。

软件：

关闭CPU以及设备的省电模式；

让内存运行在所支持的最高频率上；

内核初始化时设置好大页；

增加内核启动参数，预先指定DPDK使用的逻辑核，不被操作系统调度；

DPDK参数配置：

收包队列长度，即每个收包队列分配的收包描述符个数，反映了在软件驱动程序读取所收到包之前最大的缓存包的能力。太长占用资源，太短容易丢包，默认长度为128.

发包队列长度，即每个发包队列分配的发包描述符个数，默认长度为512.

收包队列可释放描述符数量阈值，DPDK驱动程序并没有每次收包都更新收包队列尾部索引寄存器，而是在可释放的收包描述符数量达到一个阈值时更新，设置合理，可以减少没有必要的过多收包队列尾部索引寄存器的访问。默认值为32.

发包队列可释放描述符数量阈值，通过将回写标记设置在一定间隔的发包描述符上，减少不必要的回写次数，改善性能，默认为32.

发包描述符释放阈值，当用来重新配置的发包描述符数量少于阈值时，才会启动描述符和Mbuf释放动作，设置太大释放频繁，影响性能，设置太小可用描述符少，容易丢包，默认值为32.

### **7.4 多队列配置**

DPDK中，通过将网卡的某个接收队列分配给某个核，从该队列中收到的所有报文都应当在该指定的核上处理结束，不同的核操作不同的队列。

目前可以通过RSS(接收方扩展)把数据包分配到不同的队列中，该机制根据关键字通过哈希函数计算出哈希值，再有哈希值确定队列。不同的数据包类型具有不同的关键字，例如IPv4 UDP四元组（源IP地址、目的IP地址、源端口号、目的端口号）。

另一种是Flow Director技术，根据包的字段精确匹配，查找Flow Director表项，将其分配到某个特定队列。

DPDK中可以采用RSS来负载均衡，将报文发到多个核上，同时使用Flow Director将控制报文分配到指定的队列上，使用单独的核来处理，如下图所示；

![img](https://pic2.zhimg.com/80/v2-fbf8782acc17403435c42db9c55c6ce1_720w.webp)

图8 DPDK中RSS和FDIR的应用

### **7.5 硬件加速功能**

VLAN Tag的插入和剥离由网卡硬件完成。

1）收包时VLAN tag过滤，网卡硬件端口设计了VLAN过滤表，无法在过滤表中匹配的VLAN包会被丢弃。

2）收包时VLAN tag剥离，网卡硬件会在硬件描述符中设置两个域，将需要的信息通知驱动软件，包含此包是否曾被剥离了VLAN tag以及被剥离的tag。

3）发包时VLAN tag插入，TPID值固定，可通过寄存器进行设置，TCL进行逐包设置。

TCP分片功能由硬件提供，将现有较大的TCP分片拆分为较小的TCP分片，仅需要复制TCP的包头，更新头里面的长度相关信息，重新计算校验和。

RSC组包功能针对TCP数据包，由硬件将拆分的TCP分片聚合成一个大的分片，从而减轻软件的处理。

原文地址：https://zhuanlan.zhihu.com/p/490946603

作者：CPP后端技术