当前位置: 首页 > news >正文

【系统搭建】DPDK关键概念与l2fwd源码解析

在这里插入图片描述

DPDK(Data Plane Development Kit)是一套用于高性能网络数据面处理的开发框架,其核心设计在于绕过内核协议栈,它提供了一个用户空间下的高效数据包处理库函数,可以用于快速开发高性能的网络应用程序,如网络包处理、数据包过滤、虚拟化、路由等。


一,DPDK关键概念与架构总结

1. DPDK核心架构框图

DMA
分配
核绑定
核绑定
无锁队列
无锁队列
物理网卡
巨页内存池
Mempool
Mbuf数据包
PMD轮询线程
CPU Core 0
CPU Core 1
逻辑核lcore

DPDK与内核的差异在于:​
中断 vs 轮询​(红色/绿色)
🔴 内核:每次收包触发中断,上下文切换开销大
🟢 DPDK:主动轮询网卡队列,无中断开销

​内存拷贝次数​(红色/绿色)
🔴 内核:至少 2 次拷贝(DMA→内核缓冲→用户空间)
🟢 DPDK:零拷贝(DMA直接到用户态内存池)

​协议栈位置​
🔴 内核:需要经过完整的内核协议栈
🟢 DPDK:绕过内核,用户态轻量级协议栈

​核心调度​
🔴 内核:依赖系统调度器,可能发生核心迁移
🟢 DPDK:固定核心绑定(lcore),消除缓存失效

DPDK处理流程
内核处理流程
PMD轮询发现新包
网卡收到数据包
零拷贝DMA到用户态
用户态协议栈处理
直接转发或修改
触发硬件中断
网卡收到数据包
CPU上下文切换
内核协议栈处理
数据拷贝到用户态
应用层处理

2. 核心概念理解

(1)巨页与内存管理

在高速网络数据包处理中,需要频繁地分配和释放大块内存,这种频繁的内存分配和释放操作会导致大量的 CPU 资源被浪费在内存管理上,从而降低网络处理的性能。因此,DPDK 采用巨页、 Mempool 和 Mbuf 等机制共同实现高效的内存管理和数据包处理机制。

巨页是 DPDK 优化内存性能的一种方式。 由于内存管理单元需要对于每个页面维护页表,而页表本身也需要占用内存,当页表数量较多时就会造成较大的内存开销和访问延迟。 DPDK 采用了巨页的方式来减少这种开销, 传统内存页的大小是4KB,而巨页是更大的内存页,通常为 2MB 或 1GB。使用巨页可以减少页表项、降低 CPU 的 TLB(Translation Lookaside Buffer)缺失率和 TLB 访问的延迟,提高CPU 的处理性能。

巨页初始化
2MB/1GB大页
Memory Pool预分配
Mbuf对象池
零拷贝数据存取

Mempool 是 DPDK 中用于管理大块内存的机制,它提供了一种预先分配和管理内存的方式,以避免频繁地进行内存分配和释放操作。 Mempool 可以看作是一个固定大小的内存块数组,其中每个内存块都是相同大小的。通过使用 Mempool,可以在初始化阶段一次性分配一定数量的内存块,并在需要时从内存池中获取空闲的内存块,而无需进行动态内存分配,可以大大提高数据包处理的效率和性能,且避免了内存碎片问题。

Mbuf 是 DPDK 中用于存储和操作数据包的结构体。每个 Mbuf 结构体中包含了数据包的相关信息,例如数据包的长度、数据指针、引用计数等。 Mbuf 结构体被组织成一个链表,形成了一个数据包缓冲区池。通过使用 Mbuf,可以高效地管理和操作数据包,例如接收、发送、修改数据包内容等操作。 Mbuf 结构体还提供了一些优化的特性,例如预留头部空间、避免内存对齐问题等,以提高数据包处理的效率。

  • 作用

    • 减少内存管理的开销,避免频繁的页表切换。
    • 提供连续的大块物理内存,DPDK利用巨页分配内存池(Memory Pool),确保数据包处理时无需频繁的内存映射/解映射。
  • 代码体现

    // 创建内存池(使用巨页)
    struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS,MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE,rte_socket_id());
    
(2) 核绑定(CPU Affinity / Core Binding)

又称“DPDK 的 CPU 亲和性”,是指将 DPDK 应用程序的线程或进程与特定的 CPU 核心绑定,使得这些线程或进程只会在指定的 CPU 核心上运行。

在网络数据包处理中,数据包通常需要经过多个处理步骤,例如接收、解析、转发、过滤、封装等,每个步骤都需要一定的计算和处理。如果这些处理步骤在不同的 CPU 核心上进行,由于不同 CPU 核心之间的缓存共享等问题,会导致额外的延迟和性能下降。因此,将这些处理步骤绑定到特定的 CPU 核心上,可以避免这些问题,提高数据包处理的效率和可预测性。

CPU 亲和性还可以减少 CPU 上下文切换的开销,当应用程序的线程或进程在不同的 CPU 核心之间切换时,需要切换 CPU 寄存器、缓存等状态,会带来额外的开销。通过将线程或进程与特定的 CPU 核心绑定,可以减少这种开销,提高应用程序的性能。

  • 作用
    • 减少上下文切换:避免线程在多个核心间迁移时的缓存失效(Cache Miss)。
    • 提高缓存局部性:线程专用某个核心的L1/L2缓存,加速数据访问。
    • 隔离性:关键线程(如PMD轮询线程)独占核心,避免其他任务干扰。
  • DPDK中的应用
    • 使用lcore(逻辑核)模型,通过eal_thread_set_affinity绑定线程到指定CPU。
    • 例如:将PMD线程绑定到独立核心,确保实时轮询网卡队列。
独占
独占
DPDK进程
lcore 0
lcore 1
绑定CPU Core 2
绑定CPU Core 3
执行PMD轮询
  • 代码体现
    # 启动命令(绑定核心0-3)
    ./l2fwd -l 0-3 -- -q 2
    
(3)PMD工作模型

轮询模式驱动(Poll Mode Driver)是DPDK的用户态网卡驱动,通过主动轮询代替传统中断机制处理数据包。传统的网络驱动是在操作系统内核中实现的,它们使用中断处理程序来响应网络数据包, 网络中大量数据包到来时,会频繁产生中断请求, 这会引入很大的开销。与之相比,用户态驱动是运行在用户空间的网络驱动程序,可以绕过操作系统内核,直接处理网络数据包,因此可以实现更低的延迟和更高的吞吐量。 DPDK 通过 UIO(User-space I/O)和 VFIO(Virtual Function I/O)实现了用户态驱动 PMD(PollMode Driver)。

网卡队列 PMD线程 用户空间 转发逻辑 DMA写包 零拷贝读取 批量处理 直接回传 网卡队列 PMD线程 用户空间 转发逻辑

PMD 是基于用户态的轮询机制的驱动,它使用轮询机制来获取网络数据包,而不是使用中断,因此可以减少 CPU 上下文切换的开销,提高系统的吞吐量和响应性能。 PMD 通过使用 UIO 或 VFIO 将硬件设备映射到用户态空间,实现了对硬件设备的直接访问和控制,并且 EAL(Environment Abstraction Layer)提供了一组硬件抽象层接口,可以方便地管理和操作硬件资源。

UIO 是一种轻量级的驱动框架,允许用户态程序访问硬件设备。 UIO 通过创建设备节点将硬件设备映射到用户态空间,以便用户程序可以通过文件操作系统调用来直接访问硬件资源。 UIO 驱动在内核中运行,与硬件设备交互,处理硬件中断,将数据传递给用户空间的应用程序。 DPDK 在内核中安装了 igb_uio 模块,以此借助 UIO 技术来截获中断,并重设中断回调行为,从而绕过内核协议栈后续的处理流程。

VFIO 是一种更加灵活的设备虚拟化框架,也可以用于用户态驱动。 VFIO 使用 IOMMU 来实现硬件资源的隔离,将硬件设备映射到应用程序的虚拟地址空间中。与 UIO 相比, VFIO 提供更高级别的控制和访问硬件资源的灵活性,但需要更高的配置和管理。

  • 核心特点
    • 零拷贝(Zero-Copy):数据直接从网卡DMA到用户态内存,无需内核参与。
    • 无中断轮询:持续检查网卡接收/发送队列,避免中断上下文切换的开销。
    • 多队列支持:每个队列绑定独立CPU核心,实现并行处理。

概念间的协作关系

  1. 内存管理提供高效的内存基础;
  2. 核绑定确保线程独占CPU资源,减少切换开销;
  3. PMD基于前两者实现高吞吐、低延迟的数据包处理。
    三者共同支撑DPDK的核心理念:通过用户态、轮询、无锁、零拷贝,最大化数据面性能

二,l2fwd解析

1. 代码结构

main()
├── rte_eal_init()          // EAL环境初始化
├── rte_eth_dev_configure()// 网卡配置
├── rte_eth_rx_queue_setup()// 接收队列初始化
├── rte_eth_tx_queue_setup()// 发送队列初始化
└── rte_eal_remote_launch() // 启动数据面线程

2. DPDK特性在代码中的体现

(1)巨页内存初始化
// 启动参数(指定巨页目录)
const char *eal_args[] = {"l2fwd", "-n", "4", "--huge-dir=/dev/hugepages",
};
rte_eal_init(sizeof(eal_args)/sizeof(eal_args[0]), (char**)eal_args);
(2)CPU核绑定
RTE_LCORE_FOREACH_WORKER(lcore_id) {rte_eal_remote_launch(l2fwd_launch_one_lcore, NULL, lcore_id);
}
// 线程函数内自动绑定核心
(3)PMD轮询模式
while (1) {// 批量收包(零拷贝)nb_rx = rte_eth_rx_burst(port, queue, pkts_burst, MAX_PKT_BURST);// 批量转发nb_tx = rte_eth_tx_burst(port^1, queue, pkts_burst, nb_rx);
}
(4)Mempool与Mbuf
// 从内存池获取mbuf
struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
rte_pktmbuf_alloc_bulk(mbuf_pool, pkts_burst, nb_rx);// 释放mbuf(实际是放回内存池)
rte_pktmbuf_free_bulk(pkts_burst, nb_tx);

以下是对DPDK l2fwd示例代码的核心逻辑解析、代码注释及作为接收程序的可行性分析:

(5)功能逻辑:
  1. 初始化阶段

    • EAL环境初始化(rte_eal_init)
    • 命令行参数解析(端口掩码、混杂模式、队列数量等)
    • 内存池创建(rte_pktmbuf_pool_create)
    • 端口配置:
      • 设置RX/TX队列
      • 启用混杂模式(可选)
      • MAC地址获取
      • 启动网卡(rte_eth_dev_start)
  2. 转发逻辑

    while (!force_quit) {// 1. 定时刷新发送缓冲区if (tsc超时) {rte_eth_tx_buffer_flush();print_stats(); // 定时打印统计信息}// 2. 接收数据包for (每个端口队列) {nb_rx = rte_eth_rx_burst(); // 批量收包for (每个数据包) {l2fwd_simple_forward(); // 处理并转发}}
    }
    
  3. 数据包处理

    static void l2fwd_simple_forward(struct rte_mbuf *m, unsigned portid) {// 修改目标MAC为02:00:00:00:00:PORTID// 源MAC替换为端口MACrte_eth_tx_buffer(); // 加入发送缓冲区
    }
    

相关文章:

  • 迭代器模式(Iterator Pattern)
  • oracle查询当前用户所有字段和表
  • GPU怎么绑定到服务器上
  • 纳什均衡(Nash Equilibrium) 的详细解析,涵盖定义、关键特性、经典案例及应用价值
  • Java JDK 17 自带的 java.net.http.HttpClient入门案例
  • 十二脏腑阴阳属性的全面总结
  • Qt6 以后,QSettings 读取ini 文件固定使用utf-8 编码
  • Coco-AI 接入自定义数据源
  • 基于springboot医药连锁店管理系统(源码+lw+部署文档+讲解),源码可白嫖!
  • selenium 实现模拟登录中的滑块验证功能
  • 【android telecom 框架分析 01】【基本介绍 1】【telecom服务是干什么的?】
  • Linux软件仓库
  • 管家婆工贸ERP BB104.采购费用均价分摊
  • 【.net core】【watercloud】数据库连接报错问题
  • 总结【过往部分项目经历一(计算机图形学方向)】
  • 基于X86/Nvidia+FPGA大模型具身智能机器人控制器解决方案,同时拥有算力与实时的便利
  • 计算机网络 - UDP协议
  • 图像预处理-添加水印
  • 端侧大模型综述On-Device Language Models: A Comprehensive Review
  • 23种设计模式-创建型模式之工厂方法模式(Java版本)
  • 联合国秘书长古特雷斯呼吁印巴保持最大克制
  • 美国加州州长:加州继续对中国“敞开贸易大门”
  • 巴菲特批评贸易保护主义:贸易不该被当成武器来使用
  • 传奇落幕!波波维奇卸任马刺队主教练,转型全职球队总裁
  • 新华每日电讯头版聚焦上海:科创高地向未来
  • 解放日报:抢占科技制高点,赋能新质生产力