浅入高性能网络框架DPDK

目录

DPDK 框架简介

核心特性

工作原理

典型应用场景

启动DPDK

1.关闭内核里面的一个网卡

2.dpdk的打开操作

3.dpdk启动完成

基于DPDK简单实现一个UDP协议栈

接收数据的过程(拆包解析)

发送数据的过程(组包发送)

结果展示

DPDK总结


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_uiovfio-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=0fragment_offset=0:禁用分片,包 ID 和分片偏移设为 0。

next_proto_id = IPPROTO_UDP:指定上层协议为 UDP。

src_addr=gSrcIpdst_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(数据平面开发套件)是一套通过用户空间驱动、大页内存、多核优化等技术,提升网络数据包收发与处理性能的开源库,主要用于高性能网络应用(如路由器、防火墙)开发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值