目录
DPDK 框架简介
DPDK(Data Plane Development Kit) 是一个高性能数据包处理开发框架,专为网络设备、数据中心和云计算环境设计。它通过绕过内核协议栈、优化硬件访问和提供高效数据结构,实现了低延迟、高吞吐量的数据包处理能力。
核心特性
用户空间驱动
直接在用户空间操作物理网卡(通过 UIO 或 VFIO 技术),避免内核上下文切换和数据拷贝开销。
支持轮询模式驱动(PMD),减少中断处理延迟。
零拷贝与内存优化
使用大页内存(Huge Pages)减少内存分页开销。
通过环形缓冲区(Ring Buffer)实现无锁队列,降低线程间同步成本。
多线程并行处理
支持多线程 / 多核 CPU,通过 RSS(Receive Side Scaling)技术将流量分发到不同核,提升并行处理能力。
硬件加速
支持 Intel® QuickAssist Technology(QAT)等硬件加速引擎,优化加密、压缩等操作。
工作原理
绕过内核协议栈
DPDK 驱动(如 igb_uio)将网卡映射到用户空间,应用程序直接通过内存映射访问网卡寄存器。数据包无需经过内核 TCP/IP 栈,直接由用户空间程序处理。
轮询模式(Polling Mode)
传统中断驱动依赖硬件中断触发处理,DPDK 通过轮询方式主动检查数据包到达,减少中断延迟(尤其在高负载下)。
数据平面与控制平面分离
控制平面(如路由决策)仍由内核处理,数据平面(数据包转发)由 DPDK 用户空间程序高效执行。
典型应用场景
网络功能虚拟化(NFV)加速虚拟路由器、防火墙、负载均衡器(如 DPDK 与 OpenStack 集成)。
5G 核心网用户面功能(UPF)的高吞吐量数据包转发。
数据中心网络虚拟交换机(如 DPDK 加速的 Open vSwitch)、RDMA over Converged Ethernet(RoCE)。
高性能计算低延迟通信中间件(如 DPDK-based 消息队列)。
启动DPDK
1.关闭内核里面的一个网卡
①
ifconfig // 查看网卡
②
ifconfig eth0 down //关闭etho号网卡
若想查看eth0网卡要输入ifconfig -a
2.dpdk的打开操作
①
cd 到dpdk库里面 输入
export RTE_SDK=/home/king/share/dpdk/dpdk-stable-19.08.2/
编译或运行 DPDK 相关程序、脚本时,工具需通过 RTE_SDK
环境变量定位 DPDK 库文件,确保编译环境、依赖路径正确
然后再输入
export RTE_TARGET=x86_64-native-linux-gcc/
编译 DPDK 库或应用程序时,工具链通过 RTE_TARGET
确定编译规则、依赖路径,生成适配目标环境的二进制文件,确保 DPDK 功能在指定平台上正常运行。
②
cd 到usertools目录下
启动dpdk-setup.sh执行
输入43
操作:脚本先卸载系统中已存在的 DPDK UIO 相关内核模块,随后加载基础的uio
内核模块,再加载 DPDK 专用的 UIO 模块(如uio_pci_generic
)。
目的:确保 DPDK 能通过 UIO 机制访问 PCI 设备,为后续网卡驱动绑定到 DPDK 驱动做准备,使 DPDK 程序可在用户空间直接操作硬件。
输入44
Unloading any existing VFIO module
:卸载系统中已加载的旧 VFIO 模块,避免冲突。
Loading VFIO module
:重新加载 VFIO 内核模块,为 DPDK 提供设备虚拟化支持,使 DPDK 能通过 VFIO 驱动管理 PCI 设备(如网卡)。
chmod /dev/vfio
:修改 /dev/vfio
设备文件权限,确保用户或程序有访问权限,让 DPDK 后续能正常使用 VFIO 机制操作硬件,实现高性能数据包处理。
输入 45
Unloading any existing DPDK KNI module
:卸载系统中已加载的旧版 DPDK KNI 模块,避免新旧模块冲突。
Loading DPDK KNI module
:重新加载 DPDK KNI 模块。该模块允许 DPDK 应用程序将用户空间处理的网络数据包注入内核网络栈,便于调试、兼容传统内核网络功能(如让 DPDK 处理的流量通过内核防火墙规则),实现用户空间高性能处理与内核网络栈的交互。
输入46
Removing currently reserved hugepages
:清除系统当前已预留的大页内存,避免重复配置冲突。
Unmounting /mnt/huge and removing directory
:卸载大页内存挂载点并删除目录,重置大页内存环境。
输入大页数量:提示用户输入 1048576kB
(即 1GB)大页的页数(如图中 512
),脚本会根据输入预留对应大小的大页内存,为 DPDK 提供高效内存支持,优化数据包处理性能。
输入47
Removing currently reserved hugepages
:清除系统当前已预留的大页内存,避免配置冲突。
Unmounting /mnt/huge and removing directory
:卸载大页内存挂载点并删除目录,重置大页内存环境。
输入节点大页数量:提示用户输入每个内存节点(如 node0
)的大页数量(图中输入 512
),脚本会按输入为指定节点预留大页内存,为 DPDK 提供高效内存支持,优化数据包处理性能,适用于多节点 NUMA 架构的服务器环境。
输入48
列出系统中使用内核驱动的网络设备,标注可被 DPDK 驱动(如 igb_uio
、vfio-pci
)接管的设备,帮助用户确认哪些设备可用于 DPDK 高性能数据包处理,为后续设备驱动绑定(如输入 49
绑定设备)提供参考。
同时显示其他类型设备(基带、加密等)的检测结果(图中均未检测到),全面展示系统硬件与 DPDK 的适配情况。
输入49
dpdk接管eth0号网卡
最后输入60 退出
3.dpdk启动完成
基于DPDK简单实现一个UDP协议栈
#include <stdio.h>
#include <unistd.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <arpa/inet.h>
#define NUM_MBUFS 2048 //指定数据包缓冲区池的缓冲区的数量
#define BURST_SIZE 128
#define ENABLE_SEND 1
int gDpdkPortId=0; //指定要使用的以太网端口ID
//src_mac dst_mac 8
//src_ip dst_ip 32
//src_port dst_port 16
#if ENABLE_SEND
uint8_t gSrcMac[RTE_ETHER_ADDR_LEN];
uint8_t gDstMac[RTE_ETHER_ADDR_LEN];
uint32_t gSrcIp;
uint32_t gDstIp;
uint16_t gSrcPort;
uint16_t gDstPort;
#endif
static int ustack_encode_udp_pkt(uint8_t*msg,char*data,uint16_t total_length){
//ether
struct rte_ether_hdr*eth=(struct rte_ether_hdr*)msg;
rte_memcpy(eth->s_addr.addr_bytes,gSrcMac,RTE_ETHER_ADDR_LEN);
rte_memcpy(eth->d_addr.addr_bytes,gDstMac,RTE_ETHER_ADDR_LEN);
eth->ether_type=htons(RTE_ETHER_TYPE_IPV4);
//iphdr
struct rte_ipv4_hdr*iphdr=(struct rte_ipv4_hdr*)(eth+1);
iphdr->version_ihl=0x45;
iphdr->type_of_service=0x0;
iphdr->total_length=htons(total_length-sizeof(struct rte_ether_hdr));
iphdr->time_to_live=0;
iphdr->packet_id=0;
iphdr->fragment_offset=0;
iphdr->next_proto_id = IPPROTO_UDP;
iphdr->src_addr=gSrcIp;
iphdr->dst_addr=gDstIp;
iphdr->hdr_checksum=0;
iphdr->hdr_checksum = rte_ipv4_cksum(iphdr);
//udphdr
struct rte_udp_hdr*udphdr=(struct rte_udp_hdr*)(iphdr+1);
udphdr->src_port=gSrcPort;
udphdr->dst_port=gDstPort;
uint16_t udplen = total_length - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
udphdr->dgram_len=htons(udplen);
rte_memcpy((uint8_t*)(udphdr+1), data, udplen-sizeof(struct rte_udp_hdr));
udphdr->dgram_cksum=0;
udphdr->dgram_cksum=rte_ipv4_udptcp_cksum(iphdr,udphdr);
return total_length;
}
static struct rte_mbuf *ustack_send(struct rte_mempool *mbuf_pool, char *data, uint16_t length) {
//计算长度
const unsigned total_length = length+sizeof(struct rte_ether_hdr)+sizeof(struct rte_ipv4_hdr)+sizeof(struct rte_udp_hdr);
//开辟内存池缓冲区
struct rte_mbuf*mbuf = rte_pktmbuf_alloc(mbuf_pool);
if(!mbuf){
rte_exit(EXIT_FAILURE,"Error with EAL init\n");
}
mbuf->pkt_len=total_length;//设置数据包的长度
mbuf->data_len=total_length;//设置数据的长度
uint8_t*pktdata = rte_pktmbuf_mtod(mbuf,uint8_t*);
ustack_encode_udp_pkt(pktdata, data, total_length);
return mbuf;
}
static const struct rte_eth_conf port_conf_default={
.rxmode={.max_rx_pkt_len=RTE_ETHER_MAX_LEN}
};//定义了一个默认的以太网端口的配置,设置接收模式下最大接收数据包3为以太网帧的最大长度
int main(int argc,char*argv[]){
if (rte_eal_init(argc,argv)<0)
{
rte_exit(EXIT_FAILURE,"Error with EAL init\n");
/* code */
}//初始化
uint16_t nb_sys_ports = rte_eth_dev_count_avail();
if (nb_sys_ports == 0)
{
rte_exit(EXIT_FAILURE,"No Support eth found\n");
/* code */
}//检查可用的以太网端口
printf("nb_sys_ports:%d\n",nb_sys_ports);
struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbufpool", NUM_MBUFS, 0, 0,
RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());//创建一个数据包缓冲区池,用于存储数据包
if (!mbuf_pool)
{
rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
}
struct rte_eth_dev_info dev_info;
rte_eth_dev_info_get(gDpdkPortId,&dev_info);//使gDpdkPortID获取指定以太网端口的设备信息,存储在dev_info结构体中
const int num_rx_queues =1;//指定接收队列的数量为1
const int num_tx_queues =1;//指定发送队列的数量为1
struct rte_eth_conf port_conf = port_conf_default;//使用默认的以太网配置
if(rte_eth_dev_configure(gDpdkPortId,num_rx_queues,num_tx_queues,&port_conf)<0)
{
rte_exit(EXIT_FAILURE, "Could not configure\n");
};
if(rte_eth_rx_queue_setup(gDpdkPortId,0,512,rte_eth_dev_socket_id(gDpdkPortId),NULL,mbuf_pool)<0)
{
rte_exit(EXIT_FAILURE, "Could not setup RX pool\n");
}//设置接收队列参数
#if ENABLE_SEND
struct rte_eth_txconf txq_conf = dev_info.default_txconf;
txq_conf.offloads = port_conf.rxmode.offloads;
if (rte_eth_tx_queue_setup(gDpdkPortId, 0, 512, rte_eth_dev_socket_id(gDpdkPortId), &txq_conf) < 0) {
rte_exit(EXIT_FAILURE, "Could not setup TX queue\n");
}
#endif
if (rte_eth_dev_start(gDpdkPortId)<0)
{
rte_exit(EXIT_FAILURE,"Could not start\n");
}//启动指定的以太网端口
printf("dev start success\n");
//数据包接收循环
while (1) {
struct rte_mbuf *mbufs[BURST_SIZE];
unsigned nb_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_SIZE);
if (nb_recvd > BURST_SIZE) {
rte_exit(EXIT_FAILURE, "Error with rte_eth_rx_burst\n");
}
/*
+------------+---------------+-------------------+--------------+
| ethhdr | iphdr | udphdr/tcphdr | payload |
+------------+---------------+-------------------+--------------+
*/
unsigned i = 0;
for (i = 0;i < nb_recvd;i ++) {
//解析以太网头
struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);
if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
continue;
}
//解析ip头
struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
if(iphdr->next_proto_id == IPPROTO_UDP)//检查ip头部中的下一个协议是否为UDP
{
struct rte_udp_hdr *udphdr =(struct rte_udp_hdr*)(iphdr+1);
#if ENABLE_SEND //交换mac地址 ip地址 和端口
//源ip是用来接收数据的 目的ip是用来发送数据的(个人理解)
rte_memcpy(gSrcMac,ehdr->d_addr.addr_bytes,RTE_ETHER_ADDR_LEN);
rte_memcpy(gDstMac,ehdr->s_addr.addr_bytes,RTE_ETHER_ADDR_LEN);
rte_memcpy(&gSrcIp,&iphdr->dst_addr,sizeof(uint32_t));
rte_memcpy(&gDstIp,&iphdr->src_addr,sizeof(uint32_t));
rte_memcpy(&gSrcPort,&udphdr->dst_port,sizeof(uint16_t));
rte_memcpy(&gDstPort,&udphdr->src_port,sizeof(uint16_t));
#endif
uint16_t length = ntohs(udphdr->dgram_len) - sizeof(struct rte_udp_hdr);
printf("length:%d,content:%s\n",length,(char*)(udphdr+1));
#if ENABLE_SEND
struct rte_mbuf *txbuf = ustack_send(mbuf_pool, (char *)(udphdr+1), length);
rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);
//printf("ustack_send\n");
rte_pktmbuf_free(txbuf);
#endif
}
}
}
return 0;
}
接收数据的过程(拆包解析)
①
综上,rte_eth_conf
是 DPDK 框架中用于定义 以太网端口配置 的结构体,包含以太网端口的接收模式、队列参数、硬件特性配置等信息,是 DPDK 处理以太网端口时的核心配置载体。
②
rte_eal_init 作用是初始化 DPDK 运行环境
③
rte_eth_dev_count_avail 是 DPDK 中的函数,用于获取系统当前可用的以太网设备数量。
④
通过 rte_pktmbuf_pool_create
函数创建一个用于存储网络数据包的内存池,为后续数据包收发提供缓存空间
⑤
通过调用 rte_eth_dev_info_get
,获取目标端口的硬件特性、驱动状态等信息,为后续配置端口(如设置接收队列、启用硬件加速功能等)提供依据,确保 DPDK 应用适配设备能力。
⑥
rte_eth_dev_configure
:配置以太网端口,参数包括端口 ID、接收队列数、发送队列数、配置结构体。若返回 < 0,说明配置失败,通过rte_exit
终止程序
rte_eth_rx_queue_setup
:设置接收队列参数,参数有端口 ID、队列索引、队列大小、所在 NUMA 节点、队列回调函数、关联的内存池。失败时同样终止程序,确保接收队列正确初始化,为后续数据包接收提供基础。
rte_eth_tx_queue_setup
函数:
功能:初始化发送队列,参数包括端口 ID、队列索引、队列大小、所在 NUMA 节点、发送队列配置结构体。
错误处理:若配置失败,通过 rte_exit
终止程序,确保发送队列正常工作,保障数据包发送功能
⑦
rte_eth_dev_start
是 DPDK 中用于启动以太网端口的核心函数,作用是使配置好的端口进入工作状态,开始处理数据包收发。
⑧
调用 rte_eth_rx_burst
函数从指定以太网端口接收数据包
⑨
以上都是接收数据的过程
发送数据的过程(组包发送)
①
交换mac地址 ip地址 和 端口
②
计算包的长度
在内存池中开辟一片缓冲区
设置包的长度
设置数据的长度
rte_pktmbuf_mtod
:DPDK 提供的宏,用于从 rte_mbuf
(数据包缓冲区结构体)中获取实际数据包数据的指针。
③
设置以太网头的源ip地址和目的ip地址和类型
④
version_ihl=0x45
:设置 IPv4 版本(4)和首部长度(5×4 字节 = 20 字节)。
type_of_service=0x0
:服务类型设为默认值。
total_length=htons(total_length-sizeof(struct rte_ether_hdr))
:计算 IPv4 包总长度(扣除以太网头部),并转换为网络字节序。
time_to_live=0
:生存时间设为 0(实际场景需合理赋值)。
packet_id=0
、fragment_offset=0
:禁用分片,包 ID 和分片偏移设为 0。
next_proto_id = IPPROTO_UDP
:指定上层协议为 UDP。
src_addr=gSrcIp
、dst_addr=gDstIp
:设置源 / 目的 IP 地址。
hdr_checksum = rte_ipv4_cksum(iphdr);
先清零 hdr_checksum
,再通过 rte_ipv4_cksum
函数计算 IPv4 头部校验和,确保头部数据完整性。
⑤
udphdr->src_port=gSrcPort;
:设置 UDP 源端口。
udphdr->dst_port=gDstPort;
:设置 UDP 目的端口。
uint16_t udplen = total_length - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
:计算 UDP 数据总长度,扣除以太网头部、IPv4 头部长度。
udphdr->dgram_len=htons(udplen);
:将长度值转换为网络字节序,存入 UDP 头部长度字段
rte_memcpy((uint8_t*)(udphdr+1), data, udplen-sizeof(struct rte_udp_hdr));
将负载数据(data
)复制到 UDP 头部之后的内存空间,完成 UDP 数据部分填充。
udphdr->dgram_cksum=0;
:先清零校验和字段。
udphdr->dgram_cksum=rte_ipv4_udptcp_cksum(iphdr,udphdr);
:调用 DPDK 函数,基于 IPv4 头部(iphdr
)和 UDP 头部(udphdr
)计算校验和,确保 UDP 数据完整性。
最后实现发送
rte_eth_tx_burst
是 DPDK 中用于实现以太网端口 批量发送数据包 的核心函数,设计目的是通过一次调用发送多个数据包,减少函数调用开销,提升数据发送效率
结果展示
DPDK总结
DPDK(数据平面开发套件)是一套通过用户空间驱动、大页内存、多核优化等技术,提升网络数据包收发与处理性能的开源库,主要用于高性能网络应用(如路由器、防火墙)开发。