VPP中的DPDK插件源码详解第一篇:DPDK插件的作用和意义以及整体架构

目录

第一部分:DPDK插件的作用和意义

  • 第1章:DPDK插件概述
    • 1.1 DPDK插件在VPP中的作用和意义
    • 1.2 DPDK插件与DPDK库的关系
    • 1.3 DPDK插件在VPP数据包转发流程中的位置
    • 1.4 DPDK插件的主要功能概述
    • 1.5 与其他输入/输出模块的对比

第二部分:DPDK插件的整体架构

  • 第2章:模块架构和文件组织

    • 2.1 DPDK插件的文件组织结构
    • 2.2 各文件的功能和职责
    • 2.3 模块间的依赖关系
    • 2.4 模块与外部系统的关系
  • 第3章:核心数据结构

    • 3.1 DPDK设备结构体(dpdk_device_t)
    • 3.2 接收队列结构体(dpdk_rx_queue_t)
    • 3.3 发送队列结构体(dpdk_tx_queue_t)
    • 3.4 每线程数据结构体(dpdk_per_thread_data_t)
    • 3.5 DPDK主结构体(dpdk_main_t)
    • 3.6 流表相关结构体
    • 3.7 数据结构之间的关系

第三部分:DPDK插件的初始化和管理

  • 第4章:模块初始化

    • 4.1 插件注册和入口(VLIB_PLUGIN_REGISTER)
    • 4.2 DPDK EAL初始化(dpdk_lib_init)
    • 4.3 DPDK设备发现和枚举
    • 4.4 缓冲区池创建(dpdk_buffer_pool_init)
    • 4.5 设备接口创建(vnet_eth_register_interface)
    • 4.6 RX/TX队列分配和配置(一起分配)
    • 4.7 dpdk-input节点注册
    • 4.8 dpdk-output节点注册(VNET_DEVICE_CLASS)
    • 4.9 线程初始化(dpdk_worker_thread_init)
    • 4.10 后台进程注册(dpdk_process、admin_up_down_process)
  • 第5章:DPDK设备管理

    • 5.1 设备发现和枚举
    • 5.2 设备配置(队列数、描述符数等)
    • 5.3 设备设置(dpdk_device_setup)
    • 5.4 RX/TX硬件卸载配置(offload配置)
    • 5.5 设备启动和停止(dpdk_device_start/stop)
    • 5.6 设备状态管理(ADMIN_UP标志)
    • 5.7 MAC地址管理(添加/删除MAC地址)
    • 5.8 链路状态管理(dpdk_update_link_state)
    • 5.9 统计信息管理(dpdk_update_counters)
    • 5.10 子接口(Sub-interface)和VLAN管理
    • 5.11 中断模式配置(dpdk_setup_interrupts)
    • 5.12 admin_up_down_process后台进程
    • 5.13 dpdk_process后台进程(统计和链路状态轮询)
  • 第6章:驱动管理

    • 6.1 驱动匹配机制
    • 6.2 驱动特性配置
    • 6.3 支持的驱动列表
    • 6.4 驱动特定优化
  • 第7章:缓冲区管理

    • 7.1 DPDK mbuf与VPP buffer的转换
    • 7.2 缓冲区池(mempool)的创建和管理
    • 7.3 缓冲区模板(buffer template)的使用
    • 7.4 缓冲区预取(prefetch)优化
    • 7.5 内存布局和兼容性
    • 7.6 mbuf验证(dpdk_validate_rte_mbuf)

第四部分:数据包接收(Input)

  • 第8章:dpdk-input节点核心处理

    • 8.1 dpdk-input节点的注册和类型
    • 8.2 节点的主要处理函数(dpdk_input_node)
    • 8.3 轮询向量(poll vector)的获取
    • 8.4 多设备轮询机制
    • 8.5 节点在VLIB图中的位置
  • 第9章:数据包接收处理

    • 9.1 dpdk_device_input函数详解
    • 9.2 rte_eth_rx_burst调用和批量接收
    • 9.3 dpdk_process_rx_burst函数处理接收的数据包
    • 9.4 mbuf到vlib_buffer的转换
    • 9.5 数据包元数据设置(sw_if_index、flags等)
    • 9.6 缓冲区模板的应用
  • 第10章:硬件卸载处理(接收侧)

    • 10.1 DPDK硬件卸载标志(ol_flags)的提取
    • 10.2 IP校验和卸载(IP checksum offload)
    • 10.3 L4校验和卸载(L4 checksum offload)
    • 10.4 VLAN处理
    • 10.5 RSS哈希处理
    • 10.6 硬件卸载标志的传递和使用
  • 第11章:多段数据包处理

    • 11.1 多段数据包(multi-segment packet)的识别
    • 11.2 dpdk_process_subseq_segs函数处理后续段
    • 11.3 缓冲区链的构建
    • 11.4 VLIB_BUFFER_NEXT_PRESENT标志的使用
    • 11.5 总长度计算
  • [第12章:流表处理(Flow Offload)](#第12章:流表处理(Flow Offload))

    • 12.1 Flow Offload的概念和作用
    • 12.2 dpdk_process_flow_offload函数处理流表
    • 12.3 FDIR(Flow Director)标志的处理
    • 12.4 流表查找和下一跳选择
    • 12.5 流ID和buffer advance的处理
  • [第13章:LRO处理(Large Receive Offload)](#第13章:LRO处理(Large Receive Offload))

    • 13.1 LRO的概念和作用
    • 13.2 dpdk_process_lro_offload函数处理LRO
    • 13.3 GSO(Generic Segmentation Offload)标志的设置
    • 13.4 L4头部大小的计算(dpdk_lro_find_l4_hdr_sz)
    • 13.5 GSO相关元数据的设置
  • 第14章:数据包分发和下一跳选择

    • 14.1 下一跳节点的选择机制(默认ethernet-input)
    • 14.2 默认下一跳:ethernet-input节点
    • 14.3 ethernet-input的优化标志(ETH_INPUT_FRAME_F_IP4_CKSUM_OK)
    • 14.4 Feature Arc的处理(vnet_feature_start_device_input)
    • 14.5 每接口下一跳索引(per_interface_next_index)重定向
    • 14.6 Flow Offload的每包下一跳选择
    • 14.7 数据包到下一节点的入队机制
    • 14.8 单一下一跳vs多下一跳的处理

第五部分:数据包发送(Output)

  • 第15章:dpdk-output节点核心处理

    • 15.1 dpdk-output节点的注册和类型
    • 15.2 节点的主要处理函数(VNET_DEVICE_CLASS_TX_FN)
    • 15.3 发送队列的选择机制
    • 15.4 节点在VLIB图中的位置
    • 15.5 Input和Output的协同工作
  • 第16章:数据包发送处理

    • 16.1 tx_burst_vector_internal函数详解
    • 16.2 rte_eth_tx_burst调用和批量发送
    • 16.3 vlib_buffer到mbuf的转换
    • 16.4 发送队列锁定机制
    • 16.5 批量发送优化
  • 第17章:硬件卸载处理(发送侧)

    • 17.1 dpdk_buffer_tx_offload函数详解
    • 17.2 TX硬件卸载标志的设置
    • 17.3 IP校验和卸载
    • 17.4 L4校验和卸载
    • 17.5 TSO(TCP Segmentation Offload)处理
    • 17.6 VXLAN隧道卸载
    • 17.7 头部长度计算(l2_len、l3_len、l4_len)
  • 第18章:发送队列管理

    • 18.1 发送队列的分配和配置
    • 18.2 队列与线程的绑定关系
    • 18.3 共享队列和独占队列
    • 18.4 队列锁定机制
    • 18.5 发送失败处理和mbuf释放
    • 18.6 mbuf验证(dpdk_validate_rte_mbuf)

第六部分:高级功能和优化

  • [第19章:流表管理(Flow Offload)](#第19章:流表管理(Flow Offload))

    • 19.1 Flow Offload流表的创建和删除
    • 19.2 流表条目的管理
    • 19.3 流表匹配结果的处理
    • 19.4 VPP流表规则到DPDK流表规则的转换
  • 第20章:统计和计数

    • 20.1 接收数据包和字节数的统计
    • 20.2 发送数据包和字节数的统计
    • 20.3 接口计数器的更新
    • 20.4 DPDK统计信息的获取(dpdk_update_counters)
    • 20.5 XSTATS统计的处理
    • 20.6 统计更新的时机和频率
  • 第21章:线程和队列管理

    • 21.1 多线程支持机制
    • 21.2 每线程数据结构(per_thread_data)
    • 21.3 接收队列(RX queue)的分配和管理
    • 21.4 发送队列(TX queue)的分配和管理
    • 21.5 队列与线程的绑定关系
    • 21.6 轮询向量(poll vector)的构建
    • 21.7 RSS(Receive Side Scaling)配置
    • 21.8 RSS队列配置(dpdk_interface_set_rss_queues)
    • 21.9 RETA(Redirection Table)配置
  • 第22章:性能优化

    • 22.1 批量处理优化(burst processing)
    • 22.2 预取(prefetch)优化
    • 22.3 缓冲区模板优化
    • 22.4 向量化处理
    • 22.5 缓存行对齐
    • 22.6 分支预测优化(PREDICT_TRUE/PREDICT_FALSE)
    • 22.7 零拷贝技术
  • 第23章:错误处理

    • 23.1 DPDK错误类型定义
    • 23.2 错误处理机制
    • 23.3 错误统计和报告
    • 23.4 数据包丢弃的原因和处理
    • 23.5 设备错误恢复

第七部分:管理和接口

第八部分:总结

  • 第26章:DPDK插件总结
    • 26.1 DPDK插件的关键特点
    • 26.2 在VPP数据包转发中的作用
    • 26.3 性能优化要点
    • 26.4 与其他模块的关系
    • 26.5 最佳实践和注意事项

第1章:DPDK插件概述

1.1 DPDK插件在VPP中的作用和意义

作用和实现原理

DPDK插件是VPP中负责与DPDK(Data Plane Development Kit)库集成的核心插件,它充当了VPP与物理网卡之间的"桥梁"。就像"快递分拣中心"需要"收货窗口"和"发货窗口"一样,DPDK插件提供了高性能的"数据包接收窗口"(dpdk-input)和"数据包发送窗口"(dpdk-output),让VPP能够直接操作物理网卡,实现高性能的数据包转发。

通俗理解

想象一下VPP是一个大型的"快递分拣中心":

  • DPDK插件 = "专业快递窗口":提供高性能的"收货"和"发货"服务
  • DPDK库 = "专业快递公司":提供专业的"快递车"(网卡驱动)和"运输服务"
  • 物理网卡 = "快递车":实际运输"包裹"(数据包)的"车辆"
  • 数据包 = "包裹":需要分拣和转发的"快递包裹"

DPDK插件的核心价值

  1. 高性能数据包处理

    • 利用DPDK的用户态驱动,绕过Linux内核,减少系统调用开销
    • 实现零拷贝技术,数据包直接从网卡DMA到用户态内存
    • 批量处理(burst),一次处理多个数据包,提高CPU缓存命中率
  2. 硬件卸载支持

    • IP校验和卸载:硬件计算IP校验和,减轻CPU负担
    • L4校验和卸载:硬件计算TCP/UDP校验和
    • TSO(TCP Segmentation Offload):硬件分片,提高大包发送效率
    • LRO(Large Receive Offload):硬件合并,减少小包处理开销
    • RSS(Receive Side Scaling):硬件负载均衡,多队列分发
  3. 生产环境就绪

    • 支持主流网卡(Intel、Mellanox等)
    • 支持多线程、多队列
    • 支持统计和监控
    • 支持Flow Offload等高级功能

源码证据src/plugins/dpdk/main.c:88):

c 复制代码
VLIB_PLUGIN_REGISTER () = {
    .version = VPP_BUILD_VER,
    .description = "Data Plane Development Kit (DPDK)",
};

这段代码表明DPDK插件是VPP的一个标准插件,通过VLIB_PLUGIN_REGISTER宏注册到VPP系统中。


1.2 DPDK插件与DPDK库的关系

作用和实现原理

DPDK插件是VPP与DPDK库之间的"适配层",它封装了DPDK的接口,将DPDK的功能集成到VPP的转发图中。DPDK插件不直接操作硬件,而是通过DPDK库提供的接口来操作硬件。

DPDK插件与DPDK库的关系图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                        VPP系统                                │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  VLIB转发图                                           │  │
│  │  ┌──────────────────────────────────────────────┐  │  │
│  │  │  dpdk-input节点                               │  │  │
│  │  │  ┌────────────────────────────────────────┐ │  │  │
│  │  │  │  DPDK插件(适配层)                      │ │  │  │
│  │  │  │  - 封装DPDK接口                          │ │  │  │
│  │  │  │  - 转换数据格式                          │ │  │  │
│  │  │  │  - 集成到VPP转发图                       │ │  │  │
│  │  │  └────────────────────────────────────────┘ │  │  │
│  │  └──────────────┬───────────────────────────────┘  │  │
│  └─────────────────┼──────────────────────────────────┘  │
└─────────────────────┼──────────────────────────────────────┘
                      │
                      │ 调用DPDK API
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                        DPDK库                                │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  DPDK API接口                                        │  │
│  │  - rte_eth_rx_burst()  (接收数据包)                 │  │
│  │  - rte_eth_tx_burst()  (发送数据包)                 │  │
│  │  - rte_eth_dev_configure()  (配置设备)              │  │
│  │  - rte_mempool_*()  (内存池管理)                    │  │
│  │  - rte_flow_*()  (流表管理)                         │  │
│  └──────────────┬───────────────────────────────────────┘  │
│                 │                                           │
│  ┌──────────────┼───────────────────────────────────────┐  │
│  │  PMD (Poll Mode Driver)                            │  │
│  │  - 用户态网卡驱动                                    │  │
│  │  - 零拷贝技术                                        │  │
│  │  - 批量处理                                         │  │
│  └──────────────┬───────────────────────────────────────┘  │
└─────────────────┼──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│                    硬件网卡                                   │
│  - Intel 82599/X710                                        │
│  - Mellanox ConnectX                                       │
│  - 其他支持DPDK的网卡                                        │
└─────────────────────────────────────────────────────────────┘

DPDK插件如何使用DPDK库

  1. 初始化阶段

    • 调用rte_eal_init()初始化DPDK的EAL(Environment Abstraction Layer)
    • 调用rte_eth_dev_configure()配置DPDK设备
    • 调用rte_mempool_create()创建内存池
  2. 数据包接收阶段

    • 调用rte_eth_rx_burst()批量接收数据包
    • 获取DPDK的mbuf结构体
    • 转换为VPP的buffer格式
  3. 数据包发送阶段

    • 将VPP的buffer转换为DPDK的mbuf格式
    • 调用rte_eth_tx_burst()批量发送数据包
  4. 设备管理阶段

    • 调用rte_eth_dev_start()启动设备
    • 调用rte_eth_dev_stop()停止设备
    • 调用rte_eth_stats_get()获取统计信息

通俗理解

就像"快递分拣中心"与"快递公司"的关系:

  • DPDK库 = "快递公司":提供专业的"快递车"(网卡驱动)和"运输服务"(数据包接收/发送)
  • DPDK插件 = "分拣中心的快递窗口":使用"快递公司"的服务,接收和发送"包裹"(数据包)
  • VPP = "分拣中心":对接收到的"包裹"进行分拣和转发

DPDK插件不直接操作硬件,而是通过DPDK库提供的接口来操作硬件,这样既利用了DPDK的高性能,又保持了VPP的模块化设计。

关键特点

  • 封装性:DPDK插件封装了DPDK的复杂性,为VPP提供统一的接口
  • 转换性:DPDK插件负责将DPDK的mbuf格式转换为VPP的buffer格式
  • 集成性:DPDK插件将DPDK的功能集成到VPP的转发图中
  • 可扩展性:DPDK插件支持DPDK的新功能和特性

1.3 DPDK插件在VPP数据包转发流程中的位置

作用和实现原理

DPDK插件位于VPP数据包转发流程的最前端(接收)和最末端(发送),是数据包进入和离开VPP系统的"门户"。它属于VLIB的INPUT节点类型 (接收)和设备类TX函数(发送),在VLIB的调度机制中,INPUT节点具有最高优先级,会被优先调度执行。

VPP数据包转发流程图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    VPP数据包转发流程                          │
└─────────────────────────────────────────────────────────────┘

┌──────────────┐
│  物理网卡     │
│ (DPDK管理)   │
└──────┬───────┘
       │ 数据包接收
       ▼
┌─────────────────────────────────────────────────────────────┐
│  INPUT节点层(最高优先级)                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  dpdk-input节点                                       │  │
│  │  - 从DPDK接收数据包                                    │  │
│  │  - mbuf → buffer转换                                   │  │
│  │  - 设置元数据                                          │  │
│  │  - 硬件卸载处理                                        │  │
│  └──────────────┬───────────────────────────────────────┘  │
└─────────────────┼──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│  Feature Arc层                                              │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  device-input feature arc                           │  │
│  │  - worker-handoff (线程切换)                        │  │
│  │  - span-input (端口镜像)                            │  │
│  │  - l2-patch (L2补丁)                                │  │
│  └──────────────┬───────────────────────────────────────┘  │
└─────────────────┼──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│  协议处理层                                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  ethernet-input节点                                  │  │
│  │  └─> ip4-input / ip6-input节点                      │  │
│  │      └─> ip4-lookup / ip6-lookup节点                │  │
│  │          └─> ip4-rewrite / ip6-rewrite节点          │  │
│  │              └─> interface-output节点               │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────┼──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│  设备输出层                                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  dpdk-output (VNET_DEVICE_CLASS_TX_FN)              │  │
│  │  - buffer → mbuf转换                                   │  │
│  │  - 硬件卸载处理                                        │  │
│  │  - 批量发送数据包                                      │  │
│  └──────────────┬───────────────────────────────────────┘  │
└─────────────────┼──────────────────────────────────────────┘
                  │
                  ▼
┌──────────────┐
│  物理网卡     │
│ (DPDK管理)   │
└──────────────┘

VLIB节点调度机制

复制代码
VLIB调度器
    │
    ├─ INPUT节点(最高优先级)
    │   ├─ dpdk-input
    │   ├─ memif-input
    │   ├─ af-packet-input
    │   └─ ...
    │
    ├─ PROCESS节点(进程节点)
    │   └─ dpdk-process (统计、链路状态轮询)
    │
    └─ INTERNAL节点(内部节点)
        ├─ ethernet-input
        ├─ ip4-input
        ├─ ip4-lookup
        ├─ interface-output
        └─ ...

dpdk-input节点的位置

  1. 最前端:数据包进入VPP的第一个节点
  2. INPUT类型VLIB_NODE_TYPE_INPUT,具有最高调度优先级
  3. device-input的兄弟节点.sibling_of = "device-input",与其他设备输入节点共享相同的下一跳节点

dpdk-output节点的位置

  1. 最末端:数据包离开VPP的最后一个节点
  2. 设备类TX函数VNET_DEVICE_CLASS_TX_FN,由interface-output节点调用
  3. 设备类注册 :通过VNET_DEVICE_CLASS宏注册到VPP设备系统

源码证据src/plugins/dpdk/device/node.c:563):

c 复制代码
VLIB_REGISTER_NODE (dpdk_input_node) = {
  .type = VLIB_NODE_TYPE_INPUT,
  .name = "dpdk-input",
  .sibling_of = "device-input",
  .flags = VLIB_NODE_FLAG_TRACE_SUPPORTED,
  .state = VLIB_NODE_STATE_DISABLED,
  // ...
};

源码证据src/plugins/dpdk/device/device.c:726):

c 复制代码
VNET_DEVICE_CLASS (dpdk_device_class) = {
  .name = "dpdk",
  .tx_function_n_errors = DPDK_TX_FUNC_N_ERROR,
  .tx_function_error_strings = dpdk_tx_func_error_strings,
  // ...
};

通俗理解

就像"快递分拣中心"的流程:

  • dpdk-input = "收货窗口":第一个接触"包裹"的地方,负责从"快递车"(网卡)接收"包裹"(数据包)
  • device-input feature arc = "初步检查区":检查"包裹"是否需要特殊处理
  • ethernet-input = "分拣区":根据"包裹"类型(以太网)进行分拣
  • ip4-input / ip6-input = "详细分拣区":根据"包裹"内容(IP地址)进行详细分拣
  • ip4-lookup = "路由查询区":查询"包裹"应该发送到哪里
  • ip4-rewrite = "打包区":重新打包"包裹"准备发送
  • dpdk-output = "发货窗口":最后一个接触"包裹"的地方,负责将"包裹"发送到"快递车"(网卡)

1.4 DPDK插件的主要功能概述

作用和实现原理

DPDK插件的主要功能可以概括为"接收、转换、处理、发送"四个核心步骤,同时提供设备管理、统计监控等辅助功能。

功能架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│              DPDK插件功能架构                                  │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  1. 数据包接收(Input)                                       │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  - 轮询DPDK设备队列                                   │  │
│  │  - 批量接收数据包(rte_eth_rx_burst)                │  │
│  │  - mbuf → buffer转换                                   │  │
│  │  - 设置buffer元数据                                    │  │
│  │  - 提取硬件卸载标志                                    │  │
│  │  - 处理多段数据包                                      │  │
│  │  - Flow Offload处理                                  │  │
│  │  - LRO处理                                           │  │
│  │  - 分发到下一跳节点                                   │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────┬──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│  2. 数据包发送(Output)                                      │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  - buffer → mbuf转换                                   │  │
│  │  - 设置硬件卸载标志                                    │  │
│  │  - TSO处理                                           │  │
│  │  - 批量发送数据包(rte_eth_tx_burst)                │  │
│  │  - 发送失败处理                                       │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────┬──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│  3. 设备管理                                                 │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  - 设备发现和枚举                                     │  │
│  │  - 设备配置(队列数、描述符数等)                     │  │
│  │  - 设备启动和停止                                     │  │
│  │  - MAC地址管理                                       │  │
│  │  - 链路状态管理                                       │  │
│  │  - 子接口和VLAN管理                                  │  │
│  │  - 中断模式配置                                       │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────┬──────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────────────┐
│  4. 高级功能                                                 │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  - Flow Offload流表管理                              │  │
│  │  - RSS配置                                           │  │
│  │  - 统计和计数                                        │  │
│  │  - 加密设备支持(Cryptodev)                         │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

主要功能详解

1. 数据包接收(Input)
  • 批量接收 :使用rte_eth_rx_burst()一次接收多个数据包(最多32个)
  • 格式转换 :将DPDK的rte_mbuf转换为VPP的vlib_buffer_t
  • 元数据设置:设置接收接口索引、发送接口索引、buffer标志等
  • 硬件卸载处理:提取和处理硬件计算的校验和、VLAN、RSS等
  • 多段数据包处理:处理跨多个mbuf的数据包
  • Flow Offload处理:处理硬件流表匹配结果
  • LRO处理:处理硬件合并的大数据包
2. 数据包发送(Output)
  • 格式转换 :将VPP的vlib_buffer_t转换为DPDK的rte_mbuf
  • 硬件卸载设置:设置IP校验和、L4校验和、TSO等硬件卸载标志
  • 批量发送 :使用rte_eth_tx_burst()一次发送多个数据包
  • 发送失败处理:处理发送失败的数据包,释放mbuf
3. 设备管理
  • 设备发现:发现和枚举DPDK设备
  • 设备配置:配置队列数、描述符数、RSS等
  • 设备启动/停止:启动和停止DPDK设备
  • MAC地址管理:添加、删除、修改MAC地址
  • 链路状态管理:轮询和更新链路状态
  • 子接口管理:创建和管理VLAN子接口
  • 中断模式:配置中断模式(轮询/中断)
4. 高级功能
  • Flow Offload:硬件流表管理,支持流表规则创建和删除
  • RSS配置:配置RSS哈希函数和队列分配
  • 统计和计数:收集和更新接口统计信息
  • 加密设备支持:支持DPDK的加密设备(Cryptodev)

通俗理解

就像"快递分拣中心"的工作流程:

  1. 接收:从"快递车"(DPDK)接收"包裹"(数据包)
  2. 转换:将"包裹"从"快递公司格式"(mbuf)转换为"分拣中心格式"(buffer)
  3. 处理:检查"包裹"的"标签"(硬件卸载标志),看是否需要特殊处理
  4. 发送:将"包裹"从"分拣中心格式"(buffer)转换为"快递公司格式"(mbuf),发送到"快递车"(DPDK)

同时,"分拣中心"还需要管理"快递车"(设备管理)、统计"包裹"数量(统计和计数)、提供"VIP通道"(Flow Offload)等服务。


1.5 与其他输入/输出模块的对比

作用和实现原理

VPP中有多个类似的数据输入/输出模块,它们都是device-input的兄弟节点(sibling)或设备类(device class),具有相同的下一跳节点或接口,但使用不同的底层技术来接收和发送数据包。

VPP数据输入/输出模块架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│              VPP数据输入/输出模块架构                           │
└─────────────────────────────────────────────────────────────┘

                    device-input (父节点)
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│  dpdk-input   │   │  memif-input  │   │af-packet-input│
│  (DPDK网卡)   │   │  (内存接口)   │   │ (AF_PACKET)   │
└───────┬───────┘   └───────┬───────┘   └───────┬───────┘
        │                   │                   │
        └───────────────────┼───────────────────┘
                            │
                    (共享下一跳节点)
                            │
                            ▼
                    ethernet-input




                        数据输出
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ dpdk-output   │   │ memif-output  │   │af-packet-output│
│ (DPDK网卡)    │   │ (内存接口)    │   │ (AF_PACKET)   │
└───────────────┘   └───────────────┘   └───────────────┘

主要数据输入/输出模块对比表

模块名称 节点名称 底层技术 主要用途 性能特点 适用场景
DPDK插件 dpdk-input dpdk-output DPDK用户态驱动 高性能物理网卡 ⭐⭐⭐⭐⭐ 最高性能、零拷贝、硬件卸载 生产环境、NFV、高性能转发
Memif memif-input memif-output 共享内存 容器间通信、VM间通信 ⭐⭐⭐⭐⭐ 零拷贝、低延迟、高吞吐 Kubernetes、Docker、VM迁移
AF_PACKET af-packet-input af-packet-output Linux AF_PACKET Linux TAP接口 ⭐⭐⭐ Linux原生支持、易于使用 开发测试、Linux环境
AF_XDP af-xdp-input af-xdp-output Linux AF_XDP 高性能Linux接口 ⭐⭐⭐⭐ Linux XDP技术、高性能 Linux环境下的高性能转发
RDMA rdma-input rdma-output RDMA (InfiniBand) RDMA网卡 ⭐⭐⭐⭐⭐ 超低延迟、RDMA技术 HPC、超低延迟应用
Virtio virtio-input virtio-output Virtio 虚拟化环境 ⭐⭐⭐ 虚拟化标准、通用性强 KVM、QEMU、虚拟化环境
Vhost-User vhost-user-input vhost-user-output Vhost-User QEMU/KVM ⭐⭐⭐⭐ 用户态vhost、高性能 虚拟化环境、NFV
TUN/TAP tuntap-input tuntap-output Linux TUN/TAP TUN/TAP接口 ⭐⭐ 简单易用、通用性强 开发测试、Linux环境

各模块的特点和用途

1. DPDK插件(本文档主题)

特点

  • 高性能:利用DPDK用户态驱动,绕过内核
  • 零拷贝:数据包直接从网卡DMA到用户态内存
  • 硬件卸载:支持IP校验和、L4校验和、TSO、LRO等
  • 批量处理:一次处理多个数据包,提高效率
  • 多线程支持:支持多线程、多队列

适用场景

  • 生产环境中的高性能物理网卡接收/发送
  • 数据中心、NFV、高性能转发
  • 需要硬件卸载的场景

源码证据src/plugins/dpdk/device/node.c:370):

c 复制代码
n = rte_eth_rx_burst (xd->port_id, queue_id, ptd->mbufs + n_rx_packets,
                      n_to_rx);
2. Memif

特点

  • 零拷贝:使用共享内存,无需数据拷贝
  • 低延迟:直接内存访问,延迟极低
  • 高吞吐:适合容器间、VM间通信

适用场景

  • Kubernetes、Docker容器间通信
  • VM间通信
  • 需要零拷贝的场景
3. AF_PACKET

特点

  • Linux原生支持:使用Linux的AF_PACKET套接字
  • 易于使用:配置简单,适合开发测试
  • 通用性强:支持所有Linux网卡

适用场景

  • 开发测试环境
  • Linux环境下的简单应用
  • 不需要极致性能的场景
4. AF_XDP

特点

  • Linux XDP技术:利用Linux的XDP(eXpress Data Path)
  • 高性能:比AF_PACKET性能更高
  • 需要Linux内核支持

适用场景

  • Linux环境下的高性能转发
  • 需要比AF_PACKET更高性能的场景
5. RDMA

特点

  • 超低延迟:RDMA技术,延迟极低
  • InfiniBand支持:支持InfiniBand网卡
  • 专用硬件:需要RDMA网卡

适用场景

  • HPC(高性能计算)环境
  • 超低延迟应用
  • 需要RDMA的场景
6. Virtio

特点

  • 虚拟化标准:Virtio是虚拟化标准
  • 通用性强:支持所有Virtio设备
  • 易于集成:与虚拟化环境集成简单

适用场景

  • KVM、QEMU虚拟化环境
  • 虚拟化环境下的网络转发
  • 需要虚拟化支持的场景
7. Vhost-User

特点

  • 用户态vhost:用户态实现的vhost
  • 高性能:比内核vhost性能更高
  • QEMU/KVM支持:与QEMU/KVM集成良好

适用场景

  • 虚拟化环境下的高性能转发
  • NFV场景
  • 需要用户态vhost的场景

模块选择建议

  • 生产环境高性能 :使用DPDK插件
  • 容器/VM通信 :使用Memif
  • Linux开发测试 :使用AF_PACKETAF_XDP
  • 虚拟化环境 :使用VirtioVhost-User
  • 超低延迟 :使用RDMA

通俗理解

就像"快递分拣中心"有多种"收货窗口"和"发货窗口":

  • DPDK插件 = "专业快递窗口":接收/发送"专业快递公司"(DPDK网卡)的"包裹",性能最高
  • Memif = "内部快递窗口":接收/发送"内部快递"(共享内存)的"包裹",零拷贝、低延迟
  • AF_PACKET = "普通快递窗口":接收/发送"普通快递"(Linux接口)的"包裹",简单易用
  • Virtio = "虚拟快递窗口":接收/发送"虚拟快递"(虚拟化环境)的"包裹",适合虚拟化

虽然"收货窗口"和"发货窗口"不同,但"包裹"进入"分拣中心"后,都会经过相同的"分拣流程"(device-input feature arc → ethernet-input → ...),最后通过相应的"发货窗口"发送出去。


1.6 本章总结

DPDK插件的关键特点

  1. 位置:VPP数据包转发流程的最前端(接收)和最末端(发送)
  2. 作用:从DPDK管理的物理网卡接收/发送数据包,转换为VPP格式
  3. 关系:VPP与DPDK库之间的"桥梁",封装DPDK接口
  4. 功能:接收、转换、硬件卸载处理、发送、设备管理、统计监控
  5. 同类模块:与其他10+个输入/输出模块共享相同的接口,但性能最高

在VPP架构中的重要性

DPDK插件是VPP高性能数据包转发的"基石",它:

  • 提供了高性能的数据包接收/发送能力
  • 支持硬件卸载,减轻CPU负担
  • 支持多线程、多队列,实现高并发
  • 与其他输入/输出模块统一接口,保持架构一致性
  • 是生产环境中最常用的数据包输入/输出模块

后续章节预告

  • 第2章:详细介绍模块的文件组织和架构
  • 第3章:深入讲解核心数据结构
  • 第4章:详细分析模块初始化流程
  • 第8-14章:详细讲解数据包接收的每个步骤
  • 第15-18章:详细讲解数据包发送的每个步骤

相关源码文件

  • src/plugins/dpdk/main.c - 插件入口
  • src/plugins/dpdk/device/dpdk.h - 模块头文件
  • src/plugins/dpdk/device/node.c - input节点实现
  • src/plugins/dpdk/device/device.c - output节点实现

第2章:模块架构和文件组织

2.1 DPDK插件的文件组织结构

作用和实现原理

DPDK插件采用分层、模块化的文件组织结构,将不同功能划分到不同的文件中,便于维护和扩展。整个模块就像一个"大型工厂",每个文件负责不同的"生产环节"。

文件组织结构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│              DPDK插件文件组织结构                             │
└─────────────────────────────────────────────────────────────┘

src/plugins/dpdk/
│
├── main.c                    [插件入口层]
│   └── 插件注册、延迟函数注册
│
├── buffer.c / buffer.h       [缓冲区管理层]
│   └── DPDK mbuf与VPP buffer的转换
│
├── device/                   [设备管理层]
│   ├── dpdk.h                [公共头文件层]
│   │   └── 数据结构定义、公共接口
│   │
│   ├── dpdk_priv.h           [私有头文件层]
│   │   └── 私有数据结构、内部宏定义
│   │
│   ├── init.c                [初始化层]
│   │   └── DPDK EAL初始化、设备发现、接口创建
│   │
│   ├── node.c                [数据平面层 - Input]
│   │   └── dpdk-input节点实现(核心)
│   │
│   ├── device.c              [数据平面层 - Output]
│   │   └── dpdk-output节点实现、设备管理
│   │
│   ├── common.c              [设备管理层]
│   │   └── 设备设置、启动/停止、中断配置
│   │
│   ├── driver.c              [驱动管理层]
│   │   └── 驱动匹配、驱动特性配置
│   │
│   ├── flow.c                [流表管理层]
│   │   └── Flow Offload流表管理
│   │
│   ├── format.c              [格式化输出层]
│   │   └── 调试信息格式化输出
│   │
│   └── cli.c                 [CLI命令层]
│       └── 命令行接口实现
│
└── cryptodev/                [加密设备层 - 可选]
    ├── cryptodev.c
    ├── cryptodev_op_data_path.c
    └── cryptodev_raw_data_path.c

文件统计

  • 核心文件:13个(不包括cryptodev)
  • 头文件:3个(dpdk.h、dpdk_priv.h、buffer.h)
  • 实现文件:10个(main.c、buffer.c、device目录下的8个.c文件)
  • 可选模块:cryptodev(加密设备支持)

通俗理解

就像"快递分拣中心"的组织结构:

  • main.c = "总调度室":负责整个"分拣中心"的启动和协调
  • buffer.c/h = "包裹转换区":将"快递公司格式"(mbuf)转换为"分拣中心格式"(buffer)
  • device/ = "设备管理区":管理各种"收货设备"(网卡)
    • init.c = "设备安装区":安装和配置"收货设备"
    • node.c = "收货窗口":实际接收"包裹"的地方(核心)
    • device.c = "发货窗口":实际发送"包裹"的地方(核心)
    • common.c = "设备维护区":维护"收货设备"的状态
    • driver.c = "司机管理区":管理"快递司机"(驱动)
    • flow.c = "快速通道":管理"VIP通道"(Flow Offload)
    • format.c = "信息展示区":展示"分拣中心"的运行信息
    • cli.c = "客服窗口":提供"客户服务"(命令行接口)

2.2 各文件的功能和职责

作用和实现原理

每个文件都有明确的职责分工,遵循"单一职责原则",便于代码维护和功能扩展。下面按照"从入口到核心,从核心到辅助"的顺序详细介绍各文件。

2.2.1 插件入口层

main.c - 插件主入口

功能

  • 插件注册(VLIB_PLUGIN_REGISTER
  • DPDK延迟函数覆盖(rte_delay_us_override
  • 插件初始化(dpdk_main_init

关键代码src/plugins/dpdk/main.c:88):

c 复制代码
VLIB_PLUGIN_REGISTER () = {
    .version = VPP_BUILD_VER,
    .description = "Data Plane Development Kit (DPDK)",
};

关键代码src/plugins/dpdk/main.c:73):

c 复制代码
static clib_error_t * dpdk_main_init (vlib_main_t * vm)
{
  clib_error_t * error = 0;
  
  /* register custom delay function */
  rte_delay_us_callback_register (rte_delay_us_override_cb);
  
  return error;
}

职责

  • 向VPP注册DPDK插件
  • 提供插件版本和描述信息
  • 注册自定义的延迟函数,优化DPDK在VPP中的延迟行为

通俗理解:就像"快递分拣中心"的"总调度室",负责整个"分拣中心"的启动和协调。

依赖关系

  • 依赖:dpdk/device/dpdk.h(设备相关接口)
  • 被依赖:无(这是入口文件)

2.2.2 缓冲区管理层

buffer.c / buffer.h - 缓冲区管理

功能

  • DPDK mbuf与VPP buffer的转换
  • 内存池(mempool)的创建和管理
  • mbuf模板的创建

关键功能

  • dpdk_buffer_pool_init:初始化缓冲区池
  • vlib_buffer_from_rte_mbuf:从mbuf获取buffer指针
  • rte_mbuf_from_vlib_buffer:从buffer获取mbuf指针

关键代码src/plugins/dpdk/buffer.h:19):

c 复制代码
#define rte_mbuf_from_vlib_buffer(x) (((struct rte_mbuf *)x) - 1)
#define vlib_buffer_from_rte_mbuf(x) ((vlib_buffer_t *)(x+1))

内存布局

复制代码
┌─────────────────────────────────────────┐
│  rte_mbuf (DPDK)                        │
│  └── 元数据(128字节)                    │
├─────────────────────────────────────────┤
│  vlib_buffer_t (VPP)                   │
│  └── 元数据(128字节)                    │
├─────────────────────────────────────────┤
│  数据区域                                │
│  └── 实际数据包数据                       │
└─────────────────────────────────────────┘

职责

  • 管理DPDK和VPP之间的缓冲区转换
  • 确保mbuf和buffer的内存布局兼容
  • 提供缓冲区池的创建和管理接口

通俗理解:就像"包裹转换区",负责将"快递公司格式"(mbuf)转换为"分拣中心格式"(buffer)。

依赖关系

  • 依赖:rte_mbuf.hrte_mempool.h(DPDK库)、vlib/buffer.h(VPP)
  • 被依赖:device/node.cdevice/device.cdevice/init.cdevice/common.c

2.2.3 设备管理层 - 公共头文件

device/dpdk.h - 公共头文件

功能

  • 定义公共数据结构(dpdk_device_tdpdk_rx_queue_tdpdk_tx_queue_t等)
  • 定义公共接口函数声明
  • 定义设备标志(flags)枚举
  • 导出模块的公共符号

关键数据结构

  • dpdk_device_t:DPDK设备结构体(包含RX和TX队列)
  • dpdk_rx_queue_t:接收队列结构体
  • dpdk_tx_queue_t:发送队列结构体
  • dpdk_per_thread_data_t:每线程数据结构体
  • dpdk_main_t:DPDK主结构体
  • dpdk_flow_entry_t:流表条目结构体

关键代码src/plugins/dpdk/device/dpdk.h:57):

c 复制代码
extern vnet_device_class_t dpdk_device_class;
extern vlib_node_registration_t dpdk_input_node;
extern vlib_node_registration_t admin_up_down_process_node;

职责

  • 提供模块的公共接口
  • 定义模块间交互的数据结构
  • 导出模块的公共函数

通俗理解:就像"分拣中心"的"公共规范",定义了各个"部门"之间交互的"标准格式"。

依赖关系

  • 依赖:vnet/devices/devices.h(VPP设备接口)、rte_ethdev.h(DPDK库)
  • 被依赖:所有device目录下的.c文件

device/dpdk_priv.h - 私有头文件

功能

  • 定义私有数据结构
  • 定义内部宏和常量
  • 定义内部辅助函数

关键内容

  • DPDK_NB_RX_DESC_DEFAULT:默认RX描述符数(1024)
  • DPDK_NB_TX_DESC_DEFAULT:默认TX描述符数(1024)
  • dpdk_device_flag_set:设备标志设置函数

职责

  • 封装模块内部实现细节
  • 提供内部使用的工具函数
  • 定义模块内部的常量

通俗理解:就像"分拣中心"的"内部规范",只有"内部员工"(模块内部)才能看到和使用。

依赖关系

  • 依赖:dpdk/device/dpdk.h(公共头文件)
  • 被依赖:所有device目录下的.c文件(内部使用)

2.2.4 设备管理层 - 初始化

device/init.c - 初始化代码

功能

  • DPDK EAL(Environment Abstraction Layer)初始化
  • DPDK设备发现和枚举
  • 设备接口创建和配置
  • 缓冲区池创建
  • dpdk-input节点注册
  • 线程初始化
  • 后台进程注册(dpdk_processadmin_up_down_process

关键函数

  • dpdk_init:模块初始化入口
  • dpdk_lib_init:DPDK库初始化
  • dpdk_process:后台进程(统计、链路状态轮询)
  • admin_up_down_process:设备启动/停止后台进程

关键代码src/plugins/dpdk/device/init.c:48):

c 复制代码
dpdk_main_t dpdk_main;
dpdk_config_main_t dpdk_config_main;

这两个全局变量存储了DPDK插件的所有状态信息。

初始化流程图

复制代码
┌─────────────────────────────────────────┐
│  dpdk_init (VLIB_INIT_FUNCTION)        │
│  └── 注册日志类                          │
│      └── 设置默认配置                     │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│  dpdk_lib_init (dpdk_process)          │
│  └── DPDK EAL初始化                      │
│      └── 设备发现和枚举                   │
│          └── 设备接口创建                 │
│              └── RX/TX队列分配          │
│                  └── 缓冲区池创建       │
│                      └── 节点注册       │
└─────────────────────────────────────────┘

职责

  • 负责整个模块的初始化工作
  • 发现和配置DPDK设备
  • 创建VPP接口
  • 注册dpdk-input节点
  • 启动后台进程

通俗理解:就像"设备安装区",负责安装和配置"收货设备"(网卡),让它们能够正常工作。

依赖关系

  • 依赖:buffer.c(缓冲区池)、dpdk.h(数据结构)、common.c(设备设置)
  • 被依赖:无(这是初始化文件)

2.2.5 数据平面层 - Input

device/node.c - 数据平面层(Input核心)

功能

  • dpdk-input节点的实现
  • 数据包批量接收(dpdk_device_input
  • 数据包处理(dpdk_process_rx_burst
  • 硬件卸载处理(校验和、LRO、Flow Offload)
  • 多段数据包处理
  • 数据包分发

关键函数

  • dpdk_input_node:dpdk-input节点主函数
  • dpdk_device_input:设备输入处理函数
  • dpdk_process_rx_burst:批量处理接收的数据包
  • dpdk_process_lro_offload:LRO处理
  • dpdk_process_flow_offload:Flow Offload处理
  • dpdk_process_subseq_segs:多段数据包处理

关键代码src/plugins/dpdk/device/node.c:563):

c 复制代码
VLIB_REGISTER_NODE (dpdk_input_node) = {
  .type = VLIB_NODE_TYPE_INPUT,
  .name = "dpdk-input",
  .sibling_of = "device-input",
  .flags = VLIB_NODE_FLAG_TRACE_SUPPORTED,
  .state = VLIB_NODE_STATE_DISABLED,
  // ...
};

职责

  • 实现数据包接收的核心逻辑
  • 处理硬件卸载功能
  • 将数据包分发到VPP转发图

通俗理解:就像"收货窗口",是实际接收"包裹"(数据包)的地方,是整个"分拣中心"的核心。

这是整个模块的核心文件之一,后续章节将详细讲解其实现。

依赖关系

  • 依赖:buffer.c(缓冲区转换)、dpdk.h(数据结构)、common.c(设备信息)
  • 被依赖:无(这是节点实现文件)

2.2.6 数据平面层 - Output

device/device.c - 数据平面层(Output核心)

功能

  • dpdk-output节点的实现(VNET_DEVICE_CLASS_TX_FN
  • 数据包批量发送(tx_burst_vector_internal
  • TX硬件卸载处理(dpdk_buffer_tx_offload
  • 设备管理(启动/停止、MAC地址、统计)
  • 设备类注册(VNET_DEVICE_CLASS

关键函数

  • VNET_DEVICE_CLASS_TX_FN:dpdk-output节点主函数
  • tx_burst_vector_internal:批量发送函数
  • dpdk_buffer_tx_offload:TX硬件卸载处理
  • dpdk_interface_admin_up_down:设备启动/停止
  • dpdk_add_del_mac_address:MAC地址管理
  • dpdk_set_interface_next_node:接口下一跳重定向

关键代码src/plugins/dpdk/device/device.c:726):

c 复制代码
VNET_DEVICE_CLASS (dpdk_device_class) = {
  .name = "dpdk",
  .tx_function_n_errors = DPDK_TX_FUNC_N_ERROR,
  .tx_function_error_strings = dpdk_tx_func_error_strings,
  .format_device_name = format_dpdk_device_name,
  .format_device = format_dpdk_device,
  .format_tx_trace = format_dpdk_tx_trace,
  .clear_counters = dpdk_clear_hw_interface_counters,
  .admin_up_down_function = dpdk_interface_admin_up_down,
  .subif_add_del_function = dpdk_subif_add_del_function,
  .rx_redirect_to_node = dpdk_set_interface_next_node,
  .mac_addr_change_function = dpdk_set_mac_address,
  .mac_addr_add_del_function = dpdk_add_del_mac_address,
  .format_flow = format_dpdk_flow,
  .flow_ops_function = dpdk_flow_ops_fn,
  .set_rss_queues_function = dpdk_interface_set_rss_queues,
  .rx_mode_change_function = dpdk_interface_rx_mode_change,
};

职责

  • 实现数据包发送的核心逻辑
  • 处理TX硬件卸载功能
  • 管理设备的状态和配置

通俗理解:就像"发货窗口",是实际发送"包裹"(数据包)的地方,是整个"分拣中心"的另一个核心。

这是整个模块的核心文件之一,后续章节将详细讲解其实现。

依赖关系

  • 依赖:buffer.c(缓冲区转换)、dpdk.h(数据结构)、common.c(设备设置)、flow.c(流表)
  • 被依赖:无(这是节点实现文件)

2.2.7 设备管理层 - 通用功能

device/common.c - 设备管理通用功能

功能

  • 设备设置(dpdk_device_setup
  • 设备启动(dpdk_device_start
  • 设备停止(dpdk_device_stop
  • 中断模式配置(dpdk_setup_interrupts
  • 错误处理(dpdk_device_error
  • RX/TX硬件卸载配置

关键函数

  • dpdk_device_setup:设备设置(包括RX/TX队列设置、offload配置)
  • dpdk_device_start:启动设备
  • dpdk_device_stop:停止设备
  • dpdk_setup_interrupts:配置中断模式

关键代码src/plugins/dpdk/device/common.c:57):

c 复制代码
void
dpdk_device_setup (dpdk_device_t * xd)
{
  // ... 配置RX/TX offload ...
  // ... 设置RX/TX队列 ...
  // ... 配置RSS ...
  // ... 启动设备(如果已up) ...
}

职责

  • 提供设备管理的通用功能
  • 处理设备的配置和状态管理
  • 配置硬件卸载功能

通俗理解:就像"设备维护区",负责维护"收货设备"(网卡)的状态,包括启动、停止、配置等。

依赖关系

  • 依赖:buffer.c(缓冲区池)、dpdk.h(数据结构)
  • 被依赖:init.c(设备初始化)、device.c(设备管理)

2.2.8 设备管理层 - 驱动管理

device/driver.c - 驱动管理

功能

  • 驱动匹配(dpdk_driver_find
  • 驱动特性配置(校验和、中断、RSS等)
  • 驱动特定参数设置

关键数据结构

  • dpdk_driver_t:驱动信息结构体
  • dpdk_driver_name_t:驱动名称结构体

支持的驱动src/plugins/dpdk/device/driver.c:20):

c 复制代码
static dpdk_driver_t dpdk_drivers[] = {
  {
    .drivers = DPDK_DRIVERS ({ "net_ixgbe", "Intel 82599" }),
    .enable_rxq_int = 1,
    .supported_flow_actions = supported_flow_actions_intel,
    .use_intel_phdr_cksum = 1,
  },
  {
    .drivers = DPDK_DRIVERS ({ "net_i40e", "Intel X710/XL710 Family" }),
    // ...
  },
  // ... 其他驱动
};

支持的驱动系列

  • Intel系列(ixgbe、i40e、ice、igc等)
  • Mellanox系列
  • Cavium系列
  • 其他DPDK支持的驱动

职责

  • 识别和匹配DPDK设备驱动
  • 根据驱动特性配置设备参数
  • 提供驱动特定的优化

通俗理解:就像"司机管理区",负责管理"快递司机"(驱动),根据"司机"的特点配置"快递车"(设备)。

依赖关系

  • 依赖:dpdk.h(数据结构)
  • 被依赖:init.c(驱动匹配)

2.2.9 设备管理层 - 流表管理

device/flow.c - 流表管理

功能

  • Flow Offload流表的创建和删除
  • 流表条目的管理
  • 流表匹配结果的处理
  • VPP流表规则到DPDK流表规则的转换

关键函数

  • dpdk_flow_add:添加流表条目
  • dpdk_flow_del:删除流表条目
  • dpdk_flow_query:查询流表条目
  • dpdk_flow_convert_rss_types:转换RSS类型

职责

  • 管理硬件流表(Flow Offload)
  • 将VPP的流表规则转换为DPDK的流表规则
  • 处理流表匹配结果

通俗理解:就像"快速通道",管理"VIP通道"(Flow Offload),让某些"包裹"(数据包)走"快速通道",提高处理效率。

依赖关系

  • 依赖:dpdk.h(数据结构)、vnet/flow/flow.h(VPP流表接口)
  • 被依赖:device.c(流表操作)、node.c(流表匹配)

2.2.10 格式化输出层

device/format.c - 格式化输出

功能

  • 调试信息的格式化输出
  • 设备信息的格式化显示
  • 统计信息的格式化显示
  • 流表信息的格式化显示

关键函数

  • format_dpdk_device:格式化设备信息
  • format_dpdk_rx_trace:格式化接收跟踪信息
  • format_dpdk_tx_trace:格式化发送跟踪信息
  • format_dpdk_flow:格式化流表信息
  • 各种格式化辅助函数

职责

  • 提供调试和诊断信息的格式化输出
  • 支持CLI命令的输出格式化
  • 支持跟踪信息的格式化

通俗理解:就像"信息展示区",负责将"分拣中心"的运行信息以可读的格式展示出来。

依赖关系

  • 依赖:dpdk.h(数据结构)
  • 被依赖:cli.c(CLI输出)、device.c(跟踪输出)

2.2.11 CLI命令层

device/cli.c - CLI命令

功能

  • 实现DPDK相关的CLI命令
  • 设备配置命令
  • 统计查询命令
  • 调试命令

主要CLI命令

  • show dpdk buffer:显示缓冲区信息
  • show dpdk device:显示设备信息
  • set dpdk ...:配置DPDK参数
  • 其他调试和诊断命令

关键代码src/plugins/dpdk/device/cli.c:34):

c 复制代码
/**
 * @file
 * @brief CLI for DPDK Abstraction Layer and pcap Tx Trace.
 *
 * This file contains the source code for CLI for DPDK
 * Abstraction Layer and pcap Tx Trace.
 */

职责

  • 提供命令行接口
  • 支持设备配置和管理
  • 支持统计查询和调试

通俗理解:就像"客服窗口",提供"客户服务"(命令行接口),让用户可以配置和管理"分拣中心"。

依赖关系

  • 依赖:dpdk.h(数据结构)、format.c(格式化输出)、buffer.h(缓冲区信息)
  • 被依赖:无(这是CLI接口文件)

2.2.12 加密设备层(可选)

cryptodev/ - 加密设备支持

功能

  • DPDK加密设备的初始化和管理
  • 加密操作的数据路径
  • 支持硬件和软件加密设备

文件

  • cryptodev.c:加密设备初始化
  • cryptodev_op_data_path.c:操作数据路径
  • cryptodev_raw_data_path.c:原始数据路径

职责

  • 提供加密设备支持(可选功能)
  • 支持IPSec等加密场景

通俗理解:就像"加密通道",提供"加密服务"(加密设备),让"包裹"(数据包)可以加密传输。

依赖关系

  • 依赖:dpdk.h(数据结构)、rte_cryptodev.h(DPDK加密设备库)
  • 被依赖:init.c(加密设备初始化)

2.3 模块间的依赖关系

作用和实现原理

DPDK插件模块内部文件之间存在清晰的依赖关系,遵循"依赖倒置原则",上层依赖下层,下层不依赖上层。

依赖关系详解

1. main.cdevice/dpdk.h
  • main.c依赖device/dpdk.h获取设备相关的接口
  • 插件注册时需要引用设备相关的接口
2. device/init.cbuffer.cdevice/dpdk.h
  • init.c依赖buffer.c创建缓冲区池
  • init.c依赖dpdk.h中的数据结构定义
3. device/node.cbuffer.cdevice/dpdk.h
  • node.c依赖buffer.c进行mbuf和buffer的转换
  • node.c依赖dpdk.h中的数据结构定义
4. device/device.cdevice/dpdk.hbuffer.c
  • device.c依赖dpdk.h中的数据结构
  • device.c依赖buffer.c进行缓冲区管理
5. device/common.cdevice/dpdk.hbuffer.c
  • common.c依赖dpdk.h中的数据结构
  • common.c依赖buffer.c进行缓冲区池管理
6. device/driver.cdevice/dpdk.h
  • driver.c依赖dpdk.h中的驱动相关数据结构
7. device/flow.cdevice/dpdk.h
  • flow.c依赖dpdk.h中的流表相关数据结构
8. device/cli.cdevice/dpdk.hbuffer.h
  • cli.c依赖dpdk.hbuffer.h显示信息
9. device/format.cdevice/dpdk.h
  • format.c依赖dpdk.h格式化输出
10. 所有device目录下的文件device/dpdk_priv.h
  • 所有device目录下的文件都依赖dpdk_priv.h获取私有定义

依赖层次

复制代码
第1层(最底层):
    - dpdk_priv.h(私有定义)
    - buffer.h(缓冲区接口)

第2层(数据定义层):
    - dpdk.h(公共数据结构)

第3层(功能实现层):
    - buffer.c(缓冲区实现)
    - device/common.c(通用功能)

第4层(核心功能层):
    - device/init.c(初始化)
    - device/node.c(数据平面Input)
    - device/device.c(数据平面Output)
    - device/driver.c(驱动管理)
    - device/flow.c(流表管理)

第5层(接口层):
    - device/cli.c(CLI接口)
    - device/format.c(格式化输出)

第6层(入口层):
    - main.c(插件入口)

第7层(可选模块):
    - cryptodev/(加密设备)

通俗理解

就像"快递分拣中心"的组织结构:

  • 第1层 = "基础规范":定义了"包裹格式"(数据结构)和"工具规范"(接口)
  • 第2层 = "公共规范":定义了"分拣中心"的"公共标准"
  • 第3层 = "工具库":提供了"分拣中心"使用的"工具"
  • 第4层 = "核心部门":负责"分拣中心"的核心工作
  • 第5层 = "服务窗口":提供"客户服务"(CLI)和"信息展示"(格式化)
  • 第6层 = "总调度室":负责整个"分拣中心"的启动
  • 第7层 = "增值服务":提供"加密服务"(可选)

依赖原则

  • 上层依赖下层:上层可以使用下层的功能
  • 下层不依赖上层:下层不依赖上层的实现
  • 同层可以相互依赖:同层的模块可以相互调用

2.4 模块与外部系统的关系

作用和实现原理

DPDK插件模块不仅内部有依赖关系,还与VPP的其他模块和DPDK库有依赖关系。

外部依赖关系图

复制代码
┌─────────────────────────────────────────────────────────────┐
│              DPDK插件模块外部依赖关系                          │
└─────────────────────────────────────────────────────────────┘

                    ┌──────────────┐
                    │  DPDK库      │
                    │ (rte_*.h)    │
                    └──────┬───────┘
                           │
                           │ 被依赖
                           ▼
        ┌──────────────────────────────────┐
        │                                  │
        │      DPDK插件模块                │
        │  ┌──────────────────────────┐  │
        │  │  device/node.c           │  │
        │  │  device/device.c         │  │
        │  │  device/init.c           │  │
        │  │  buffer.c                │  │
        │  └──────────────────────────┘  │
        │                                  │
        └───────────┬──────────────────────┘
                    │
        ┌───────────┼───────────┐
        │           │           │
        ▼           ▼           ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ VLIB     │ │ VNET     │ │ 其他模块 │
│ (调度)   │ │ (网络)   │ │ (功能)  │
└──────────┘ └──────────┘ └──────────┘

外部依赖详解

1. DPDK库依赖

主要DPDK头文件

  • rte_eal.h:EAL(Environment Abstraction Layer)接口
  • rte_ethdev.h:以太网设备接口(核心)
  • rte_mbuf.h:mbuf数据结构(核心)
  • rte_mempool.h:内存池接口
  • rte_version.h:DPDK版本信息
  • rte_flow.h:流表接口(Flow Offload)
  • rte_cryptodev.h:加密设备接口(可选)

关键代码src/plugins/dpdk/device/node.c:370):

c 复制代码
n = rte_eth_rx_burst (xd->port_id, queue_id, ptd->mbufs + n_rx_packets,
                      n_to_rx);

关键代码src/plugins/dpdk/device/device.c:173):

c 复制代码
n_sent = rte_eth_tx_burst (xd->port_id, queue_id, mb, n_left);
2. VPP VLIB依赖

主要VLIB头文件

  • vlib/vlib.h:VLIB核心接口(节点注册、调度)
  • vlib/node.h:节点相关接口
  • vlib/buffer.h:缓冲区接口
  • vlib/threads.h:线程管理接口

关键代码src/plugins/dpdk/device/node.c:563):

c 复制代码
VLIB_REGISTER_NODE (dpdk_input_node) = {
  .type = VLIB_NODE_TYPE_INPUT,
  .name = "dpdk-input",
  // ...
};
3. VPP VNET依赖

主要VNET头文件

  • vnet/vnet.h:VNET核心接口
  • vnet/devices/devices.h:设备接口(核心)
  • vnet/ethernet/ethernet.h:以太网接口
  • vnet/interface/rx_queue_funcs.h:接收队列接口
  • vnet/interface/tx_queue_funcs.h:发送队列接口
  • vnet/feature/feature.h:Feature Arc接口

关键代码src/plugins/dpdk/device/device.c:726):

c 复制代码
VNET_DEVICE_CLASS (dpdk_device_class) = {
  .name = "dpdk",
  // ...
};
4. VPP其他模块依赖

主要其他模块

  • vnet/flow/flow.h:流表接口
  • vnet/classify/vnet_classify.h:分类接口
  • vnet/mpls/packet.h:MPLS接口
  • vnet/tcp/tcp_packet.h:TCP接口
  • vlib/pci/pci.h:PCI接口
  • vlib/vmbus/vmbus.h:VMBus接口

依赖方向

复制代码
DPDK库
  ↑
  │ 被依赖
  │
DPDK插件模块
  ↑
  │ 被依赖
  │
VPP核心模块(VLIB、VNET)
  ↑
  │ 被依赖
  │
VPP其他功能模块

通俗理解

就像"快递分拣中心"与外部系统的关系:

  • DPDK库 = "快递公司":提供专业的"快递服务"(网卡驱动)
  • DPDK插件模块 = "分拣中心":使用"快递公司"的服务
  • VPP核心模块 = "分拣中心管理系统":提供"分拣中心"的基础设施
  • VPP其他模块 = "分拣中心的其他部门":提供各种"分拣服务"

关键特点

  • 封装性:DPDK插件封装了DPDK的复杂性,为VPP提供统一的接口
  • 集成性:DPDK插件将DPDK的功能集成到VPP的转发图中
  • 可扩展性:DPDK插件支持DPDK的新功能和特性
  • 模块化:DPDK插件是VPP的一个独立模块,可以单独编译和加载

2.5 文件编译和链接

作用和实现原理

DPDK插件的编译和链接通过CMakeLists.txt文件配置,支持多架构优化和可选模块。

编译配置src/plugins/dpdk/CMakeLists.txt:130):

cmake 复制代码
add_vpp_plugin(dpdk
  SOURCES
  buffer.c
  main.c
  device/cli.c
  device/common.c
  device/device.c
  device/driver.c
  device/flow.c
  device/format.c
  device/init.c
  device/node.c
  ${DPDK_CRYPTODEV_OP_SOURCE}
  ${DPDK_CRYPTODEV_SOURCE}
  ${DPDK_CRYPTODEV_RAW_SOURCE}

  MULTIARCH_SOURCES
  buffer.c
  device/device.c
  device/node.c

  INSTALL_HEADERS
  device/dpdk.h

  LINK_FLAGS
  "${DPDK_LINK_FLAGS}"

  LINK_LIBRARIES
  ${DPDK_LINK_LIBRARIES}
  ${OPENSSL_CRYPTO_LIBRARIES}
)

关键配置说明

  1. SOURCES:所有源文件列表
  2. MULTIARCH_SOURCES:支持多架构优化的源文件(性能关键路径)
  3. INSTALL_HEADERS:安装的公共头文件
  4. LINK_FLAGS:链接标志(DPDK库的特殊链接要求)
  5. LINK_LIBRARIES:链接的库(DPDK库、OpenSSL库)

多架构优化

MULTIARCH_SOURCES中的文件会针对不同的CPU架构(如AVX、AVX2、AVX512)进行优化编译,提高性能关键路径的执行效率。

通俗理解

就像"快递分拣中心"的"建设规划":

  • SOURCES = "需要建设的部门":列出所有需要建设的"部门"(源文件)
  • MULTIARCH_SOURCES = "重点优化部门":对"核心部门"(性能关键路径)进行"特殊优化"(多架构编译)
  • INSTALL_HEADERS = "对外接口":安装"公共接口"(头文件),供其他模块使用
  • LINK_FLAGS/LINK_LIBRARIES = "外部资源":链接"外部资源"(DPDK库、OpenSSL库)

2.6 本章总结

DPDK插件模块的文件组织特点

  1. 分层清晰:按照功能分层,每层职责明确

    • 入口层 → 数据定义层 → 功能实现层 → 核心功能层 → 接口层
  2. 模块化设计:每个文件负责特定功能,便于维护

    • Input和Output分离,但共享数据结构和管理功能
  3. 依赖关系清晰:遵循依赖倒置原则,上层依赖下层

    • 避免循环依赖,保持代码结构清晰
  4. 接口统一:通过公共头文件提供统一接口

    • dpdk.h提供公共接口,dpdk_priv.h封装内部实现

关键文件总结

文件 层级 主要职责 重要性 是否多架构优化
main.c 入口层 插件注册 ⭐⭐
buffer.c/h 基础层 缓冲区转换 ⭐⭐⭐
device/dpdk.h 数据层 数据结构定义 ⭐⭐⭐
device/dpdk_priv.h 数据层 私有数据结构 ⭐⭐
device/init.c 功能层 模块初始化 ⭐⭐⭐
device/node.c 核心层 数据包接收(核心) ⭐⭐⭐⭐⭐
device/device.c 核心层 数据包发送(核心) ⭐⭐⭐⭐⭐
device/common.c 功能层 设备管理 ⭐⭐⭐
device/driver.c 功能层 驱动管理 ⭐⭐
device/flow.c 功能层 流表管理 ⭐⭐
device/cli.c 接口层 CLI命令 ⭐⭐
device/format.c 接口层 格式化输出
cryptodev/ 可选层 加密设备 ⭐⭐

文件组织优势

  1. 可维护性:每个文件职责单一,便于理解和修改
  2. 可扩展性:新功能可以添加新文件,不影响现有代码
  3. 可测试性:每个模块可以独立测试
  4. 性能优化:关键路径文件支持多架构优化

后续章节预告

  • 第3章 :详细讲解核心数据结构(dpdk_device_tdpdk_rx_queue_tdpdk_tx_queue_t等)
  • 第4章 :详细分析模块初始化流程(init.c
  • 第8章 :详细讲解dpdk-input节点核心处理(node.c
  • 第15章 :详细讲解dpdk-output节点核心处理(device.c

相关源码文件

  • src/plugins/dpdk/ - 所有文件
  • src/plugins/dpdk/CMakeLists.txt - 编译配置

第3章:核心数据结构

3.1 DPDK设备结构体(dpdk_device_t)

作用和实现原理

dpdk_device_t是DPDK插件的核心数据结构,代表一个DPDK设备(网卡)。它包含了设备的所有信息,包括队列、统计、配置、流表等。就像"快递分拣中心"的"设备档案",记录了每个"收货设备"(网卡)的所有信息。

数据结构定义src/plugins/dpdk/device/dpdk.h:164):

c 复制代码
typedef struct
{
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);

  dpdk_rx_queue_t *rx_queues;      // 接收队列数组
  dpdk_tx_queue_t *tx_queues;      // 发送队列数组

  /* Instance ID to access internal device array. */
  u32 device_index;                // 设备索引(在devices数组中的位置)

  u32 hw_if_index;                 // 硬件接口索引(VPP)
  u32 sw_if_index;                  // 软件接口索引(VPP)
  u32 buffer_flags;                // 缓冲区标志

  /* next node index if we decide to steal the rx graph arc */
  u32 per_interface_next_index;    // 接口特定的下一跳节点索引

  u16 flags;                        // 设备标志(ADMIN_UP、PROMISC等)

  /* DPDK device port number */
  dpdk_portid_t port_id;           // DPDK端口ID
  i8 cpu_socket;                   // CPU socket(NUMA节点)

  CLIB_CACHE_LINE_ALIGN_MARK (cacheline1);

  u64 enabled_tx_off;              // 启用的TX硬件卸载功能
  u64 enabled_rx_off;              // 启用的RX硬件卸载功能
  dpdk_driver_t *driver;            // 驱动信息
  u8 *name;                         // 设备名称
  const char *if_desc;              // 接口描述

  /* number of sub-interfaces */
  u16 num_subifs;                  // 子接口数量

  /* flow related */
  u32 supported_flow_actions;       // 支持的流表动作
  dpdk_flow_entry_t *flow_entries; // 流表条目池
  dpdk_flow_lookup_entry_t *flow_lookup_entries; // 流表查找条目池
  u32 *parked_lookup_indexes;      // 暂停的查找索引向量
  u32 parked_loop_count;            // 暂停循环计数
  struct rte_flow_error last_flow_error; // 最后一个流表错误

  struct rte_eth_link link;         // 链路状态
  f64 time_last_link_update;       // 上次链路状态更新时间

  struct rte_eth_stats stats;       // 统计信息
  struct rte_eth_stats last_stats;  // 上次统计信息
  struct rte_eth_xstat *xstats;     // 扩展统计信息
  f64 time_last_stats_update;      // 上次统计更新时间
  vlib_simple_counter_main_t xstats_counters; // 扩展统计计数器
  u32 *xstats_symlinks;             // 扩展统计符号链接

  /* mac address */
  u8 *default_mac_address;          // 默认MAC地址

  /* maximum supported max frame size */
  u32 max_supported_frame_size;     // 最大支持的帧大小

  /* due to lack of API to get ethernet max_frame_size we store information
   * deducted from device info */
  u8 driver_frame_overhead;         // 驱动帧开销

  /* error string */
  clib_error_t *errors;             // 错误信息
  dpdk_port_conf_t conf;            // 端口配置
} dpdk_device_t;

关键字段详解

1. 队列相关字段
  • rx_queues:接收队列数组指针

    • 作用:存储所有接收队列的信息
    • 类型dpdk_rx_queue_t *(动态数组)
    • 通俗理解:就像"收货窗口"的"窗口列表",每个"窗口"(队列)负责接收"包裹"(数据包)
  • tx_queues:发送队列数组指针

    • 作用:存储所有发送队列的信息
    • 类型dpdk_tx_queue_t *(动态数组)
    • 通俗理解:就像"发货窗口"的"窗口列表",每个"窗口"(队列)负责发送"包裹"(数据包)
2. 索引相关字段
  • device_index:设备索引

    • 作用 :在dpdk_main.devices数组中的位置
    • 类型u32
    • 通俗理解:就像"设备档案"的"档案编号",用于快速查找
  • hw_if_index:硬件接口索引

    • 作用:VPP硬件接口索引
    • 类型u32
    • 通俗理解:就像"设备"在VPP系统中的"硬件ID"
  • sw_if_index:软件接口索引

    • 作用:VPP软件接口索引
    • 类型u32
    • 通俗理解:就像"设备"在VPP系统中的"软件ID"
3. DPDK相关字段
  • port_id:DPDK端口ID

    • 作用:DPDK库中的端口标识符
    • 类型dpdk_portid_tuint16_t
    • 通俗理解:就像"设备"在DPDK库中的"身份证号"
  • cpu_socket:CPU socket(NUMA节点)

    • 作用:设备所在的NUMA节点
    • 类型i8
    • 通俗理解:就像"设备"所在的"区域"(NUMA节点),用于优化内存访问
4. 硬件卸载相关字段
  • enabled_tx_off:启用的TX硬件卸载功能

    • 作用:记录启用的TX硬件卸载功能(校验和、TSO等)
    • 类型u64(位掩码)
    • 通俗理解:就像"发货窗口"的"自动化功能开关",记录哪些功能已经开启
  • enabled_rx_off:启用的RX硬件卸载功能

    • 作用:记录启用的RX硬件卸载功能(校验和、LRO等)
    • 类型u64(位掩码)
    • 通俗理解:就像"收货窗口"的"自动化功能开关",记录哪些功能已经开启
5. 驱动相关字段
  • driver :驱动信息指针
    • 作用 :指向驱动信息结构体(dpdk_driver_t
    • 类型dpdk_driver_t *
    • 通俗理解:就像"设备"的"司机信息",记录了"司机"(驱动)的特点和能力
6. 流表相关字段
  • flow_entries:流表条目池

    • 作用:存储Flow Offload流表条目
    • 类型dpdk_flow_entry_t *(pool)
    • 通俗理解:就像"快速通道"的"通道列表",记录了哪些"包裹"(数据包)可以走"快速通道"
  • flow_lookup_entries:流表查找条目池

    • 作用:存储流表查找条目(用于快速匹配)
    • 类型dpdk_flow_lookup_entry_t *(pool)
    • 通俗理解:就像"快速通道"的"查找表",用于快速查找"包裹"应该走哪个"通道"
7. 统计相关字段
  • stats:统计信息

    • 作用:存储DPDK设备的统计信息(收发包数、错误数等)
    • 类型struct rte_eth_stats
    • 通俗理解:就像"设备"的"工作记录",记录了"收货"和"发货"的数量
  • xstats:扩展统计信息

    • 作用:存储DPDK设备的扩展统计信息(更详细的统计)
    • 类型struct rte_eth_xstat *
    • 通俗理解:就像"设备"的"详细工作记录",记录了更详细的工作信息
8. 配置相关字段
  • conf :端口配置
    • 作用:存储端口的配置信息(队列数、描述符数、RSS等)
    • 类型dpdk_port_conf_t
    • 通俗理解:就像"设备"的"配置档案",记录了"设备"的各种配置参数

缓存行对齐

结构体使用了CLIB_CACHE_LINE_ALIGN_MARK宏来标记缓存行边界,这是为了优化多核性能,避免false sharing(伪共享)。

通俗理解

dpdk_device_t就像"快递分拣中心"的"设备档案",记录了每个"收货设备"(网卡)的所有信息:

  • 基本信息:设备名称、索引、端口ID
  • 队列信息:接收队列、发送队列
  • 能力信息:硬件卸载功能、流表支持
  • 状态信息:链路状态、统计信息
  • 配置信息:端口配置、驱动信息

使用场景

  • 设备初始化 :创建dpdk_device_t并填充信息
  • 数据包接收 :通过device_index查找设备,使用rx_queues接收数据包
  • 数据包发送 :通过device_index查找设备,使用tx_queues发送数据包
  • 统计查询 :读取statsxstats获取统计信息
  • 流表管理 :使用flow_entriesflow_lookup_entries管理流表

3.2 接收队列结构体(dpdk_rx_queue_t)

作用和实现原理

dpdk_rx_queue_t代表一个DPDK接收队列,用于接收数据包。每个设备可以有多个接收队列,用于多队列接收和RSS(Receive Side Scaling)。就像"快递分拣中心"的"收货窗口",每个"窗口"(队列)负责接收"包裹"(数据包)。

数据结构定义src/plugins/dpdk/device/dpdk.h:97):

c 复制代码
typedef struct
{
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  u8 buffer_pool_index;            // 缓冲区池索引
  u32 queue_index;                 // 队列索引(在队列数组中的位置)
  int efd;                         // 事件文件描述符(用于中断模式)
  uword clib_file_index;           // CLIB文件索引
} dpdk_rx_queue_t;

关键字段详解

1. buffer_pool_index:缓冲区池索引
  • 作用:指定该队列使用的缓冲区池
  • 类型u8
  • 通俗理解:就像"收货窗口"使用的"包裹存放区"(缓冲区池)的编号

关键代码src/plugins/dpdk/device/node.c:370):

c 复制代码
n = rte_eth_rx_burst (xd->port_id, queue_id, ptd->mbufs + n_rx_packets,
                      n_to_rx);
2. queue_index:队列索引
  • 作用:队列在队列数组中的位置
  • 类型u32
  • 通俗理解:就像"收货窗口"的"窗口编号",用于标识是第几个"窗口"
3. efd:事件文件描述符
  • 作用:用于中断模式的事件文件描述符
  • 类型int
  • 通俗理解:就像"收货窗口"的"通知铃",当有"包裹"到达时,"通知铃"会响(中断)

关键代码src/plugins/dpdk/device/common.c:320):

c 复制代码
static void
dpdk_setup_interrupts (dpdk_device_t *xd)
{
  // ... 设置中断模式 ...
  rq->efd = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);
  // ...
}
4. clib_file_index:CLIB文件索引
  • 作用:CLIB文件系统的文件索引(用于事件处理)
  • 类型uword
  • 通俗理解:就像"通知铃"在"管理系统"中的"注册号"

缓存行对齐

结构体使用CLIB_CACHE_LINE_ALIGN_MARK标记缓存行边界,避免false sharing。

队列与设备的关系

复制代码
┌─────────────────────────────────────────┐
│      dpdk_device_t                      │
│  ┌───────────────────────────────────┐  │
│  │  rx_queues (数组指针)             │  │
│  └──────────────┬────────────────────┘  │
│                 │                        │
│                 ▼                        │
│  ┌───────────────────────────────────┐  │
│  │  dpdk_rx_queue_t[0]              │  │
│  │    buffer_pool_index = 0          │  │
│  │    queue_index = 0                │  │
│  │    efd = ...                      │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  dpdk_rx_queue_t[1]              │  │
│  │    buffer_pool_index = 0          │  │
│  │    queue_index = 1                │  │
│  │    efd = ...                      │  │
│  └───────────────────────────────────┘  │
│  ... (更多队列)                         │
└─────────────────────────────────────────┘

通俗理解

dpdk_rx_queue_t就像"快递分拣中心"的"收货窗口":

  • 基本信息 :窗口编号(queue_index)、使用的存放区(buffer_pool_index
  • 通知机制 :通知铃(efd),当有"包裹"到达时会响

使用场景

  • 队列创建:在设备初始化时创建接收队列
  • 数据包接收:使用队列索引从DPDK接收数据包
  • 中断模式 :使用efd实现中断驱动的接收
  • RSS:多个队列用于RSS负载均衡

3.3 发送队列结构体(dpdk_tx_queue_t)

作用和实现原理

dpdk_tx_queue_t代表一个DPDK发送队列,用于发送数据包。每个设备可以有多个发送队列,用于多队列发送。就像"快递分拣中心"的"发货窗口",每个"窗口"(队列)负责发送"包裹"(数据包)。

数据结构定义src/plugins/dpdk/device/dpdk.h:106):

c 复制代码
typedef struct
{
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  clib_spinlock_t lock;            // 自旋锁(用于多线程保护)
  u32 queue_index;                 // 队列索引(在队列数组中的位置)
} dpdk_tx_queue_t;

关键字段详解

1. lock:自旋锁
  • 作用:保护队列的并发访问(多线程安全)
  • 类型clib_spinlock_t
  • 通俗理解:就像"发货窗口"的"门锁",确保同一时间只有一个"工作人员"(线程)可以操作"窗口"

为什么需要锁

发送队列可能被多个线程同时访问(多线程发送),需要使用锁来保护队列的并发访问。

关键代码src/plugins/dpdk/device/device.c:173):

c 复制代码
n_sent = rte_eth_tx_burst (xd->port_id, queue_id, mb, n_left);
2. queue_index:队列索引
  • 作用:队列在队列数组中的位置
  • 类型u32
  • 通俗理解:就像"发货窗口"的"窗口编号",用于标识是第几个"窗口"

缓存行对齐

结构体使用CLIB_CACHE_LINE_ALIGN_MARK标记缓存行边界,避免false sharing。

队列与设备的关系

复制代码
┌─────────────────────────────────────────┐
│      dpdk_device_t                      │
│  ┌───────────────────────────────────┐  │
│  │  tx_queues (数组指针)             │  │
│  └──────────────┬────────────────────┘  │
│                 │                        │
│                 ▼                        │
│  ┌───────────────────────────────────┐  │
│  │  dpdk_tx_queue_t[0]              │  │
│  │    lock = ...                    │  │
│  │    queue_index = 0                │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  dpdk_tx_queue_t[1]              │  │
│  │    lock = ...                    │  │
│  │    queue_index = 1                │  │
│  └───────────────────────────────────┘  │
│  ... (更多队列)                         │
└─────────────────────────────────────────┘

为什么RX队列不需要锁,而TX队列需要锁

  • RX队列:通常每个线程绑定一个队列,不需要锁
  • TX队列:多个线程可能共享一个队列,需要锁保护

通俗理解

dpdk_tx_queue_t就像"快递分拣中心"的"发货窗口":

  • 基本信息 :窗口编号(queue_index
  • 并发保护 :门锁(lock),确保多线程安全

使用场景

  • 队列创建:在设备初始化时创建发送队列
  • 数据包发送:使用队列索引向DPDK发送数据包
  • 多线程发送:使用锁保护队列的并发访问

3.4 每线程数据结构体(dpdk_per_thread_data_t)

作用和实现原理

dpdk_per_thread_data_t是每个线程的私有数据结构,用于存储线程特定的数据(如批量接收缓冲区、下一跳索引等)。就像"快递分拣中心"的"工作人员工作台",每个"工作人员"(线程)都有自己的"工作台"(每线程数据)。

数据结构定义src/plugins/dpdk/device/dpdk.h:318):

c 复制代码
#define DPDK_RX_BURST_SZ VLIB_FRAME_SIZE

typedef struct
{
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  struct rte_mbuf *mbufs[DPDK_RX_BURST_SZ];  // mbuf指针数组(批量接收)
  u32 buffers[DPDK_RX_BURST_SZ];            // buffer索引数组
  u16 next[DPDK_RX_BURST_SZ];                // 下一跳节点索引数组
  u16 etype[DPDK_RX_BURST_SZ];               // 以太网类型数组
  u32 flags[DPDK_RX_BURST_SZ];               // 标志数组
  vlib_buffer_t buffer_template;              // buffer模板
} dpdk_per_thread_data_t;

关键字段详解

1. mbufs:mbuf指针数组
  • 作用:存储批量接收的mbuf指针
  • 类型struct rte_mbuf *[DPDK_RX_BURST_SZ]
  • 大小DPDK_RX_BURST_SZ(等于VLIB_FRAME_SIZE,通常为256)
  • 通俗理解:就像"工作台"上的"包裹临时存放区",用于存放批量接收的"包裹"(数据包)

关键代码src/plugins/dpdk/device/node.c:370):

c 复制代码
n = rte_eth_rx_burst (xd->port_id, queue_id, ptd->mbufs + n_rx_packets,
                      n_to_rx);
2. buffers:buffer索引数组
  • 作用:存储VPP buffer的索引
  • 类型u32[DPDK_RX_BURST_SZ]
  • 通俗理解:就像"工作台"上的"包裹编号列表",记录了每个"包裹"(数据包)的"编号"(buffer索引)
3. next:下一跳节点索引数组
  • 作用:存储每个数据包的下一跳节点索引
  • 类型u16[DPDK_RX_BURST_SZ]
  • 通俗理解:就像"工作台"上的"包裹去向列表",记录了每个"包裹"(数据包)应该送到哪个"部门"(下一跳节点)

关键代码src/plugins/dpdk/device/node.c:443):

c 复制代码
vlib_buffer_enqueue_to_next (vm, node, ptd->buffers, ptd->next,
                              n_rx_packets);
4. etype:以太网类型数组
  • 作用:存储每个数据包的以太网类型(EtherType)
  • 类型u16[DPDK_RX_BURST_SZ]
  • 通俗理解:就像"工作台"上的"包裹类型列表",记录了每个"包裹"(数据包)的类型(IPv4、IPv6、ARP等)
5. flags:标志数组
  • 作用:存储每个数据包的标志(硬件卸载标志等)
  • 类型u32[DPDK_RX_BURST_SZ]
  • 通俗理解:就像"工作台"上的"包裹标记列表",记录了每个"包裹"(数据包)的特殊标记(校验和、LRO等)
6. buffer_template:buffer模板
  • 作用:用于初始化buffer的模板
  • 类型vlib_buffer_t
  • 通俗理解:就像"工作台"上的"标准模板",用于快速初始化"包裹"(数据包)的"标签"(buffer)

关键代码src/plugins/dpdk/device/init.c:316):

c 复制代码
dpdk_per_thread_data_t *ptd = vec_elt_at_index (dm->per_thread_data, i);
clib_memset (&ptd->buffer_template, 0, sizeof (vlib_buffer_t));
vnet_buffer (&ptd->buffer_template)->sw_if_index[VLIB_TX] = (u32) ~ 0;

为什么需要每线程数据

  • 性能优化:避免多线程之间的数据竞争,提高缓存命中率
  • 批量处理:支持批量接收和处理数据包
  • 减少锁竞争:每个线程有独立的数据结构,减少锁竞争

数据结构布局

复制代码
┌─────────────────────────────────────────┐
│  dpdk_main_t                            │
│  ┌───────────────────────────────────┐  │
│  │  per_thread_data (数组指针)        │  │
│  └──────────────┬────────────────────┘  │
│                 │                        │
│                 ▼                        │
│  ┌───────────────────────────────────┐  │
│  │  dpdk_per_thread_data_t[0]        │  │
│  │    mbufs[256]                     │  │
│  │    buffers[256]                   │  │
│  │    next[256]                      │  │
│  │    etype[256]                     │  │
│  │    flags[256]                     │  │
│  │    buffer_template                │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  dpdk_per_thread_data_t[1]        │  │
│  │    ... (线程1的数据)              │  │
│  └───────────────────────────────────┘  │
│  ... (更多线程)                         │
└─────────────────────────────────────────┘

通俗理解

dpdk_per_thread_data_t就像"快递分拣中心"的"工作人员工作台":

  • 临时存放区mbufsbuffers,用于存放批量接收的"包裹"(数据包)
  • 处理列表nextetypeflags,记录了每个"包裹"的处理信息
  • 标准模板buffer_template,用于快速初始化"包裹"的"标签"

使用场景

  • 批量接收 :使用mbufs数组批量接收数据包
  • 批量处理 :使用buffersnextetypeflags数组批量处理数据包
  • 性能优化:每个线程有独立的数据结构,避免数据竞争

3.5 DPDK主结构体(dpdk_main_t)

作用和实现原理

dpdk_main_t是DPDK插件的全局主结构体,存储了所有设备、每线程数据、配置等信息。就像"快递分拣中心"的"总档案室",记录了整个"分拣中心"的所有信息。

数据结构定义src/plugins/dpdk/device/dpdk.h:329):

c 复制代码
typedef struct
{
  /* Devices */
  dpdk_device_t *devices;                    // 设备数组
  dpdk_per_thread_data_t *per_thread_data;    // 每线程数据数组

  /*
   * flag indicating that a posted admin up/down
   * (via post_sw_interface_set_flags) is in progress
   */
  u8 admin_up_down_in_progress;               // 管理启动/停止进行中标志

  /* control interval of dpdk link state and stat polling */
  f64 link_state_poll_interval;               // 链路状态轮询间隔
  f64 stat_poll_interval;                     // 统计轮询间隔

  dpdk_config_main_t *conf;                   // 配置主结构体指针
  dpdk_port_conf_t default_port_conf;         // 默认端口配置

  /* API message ID base */
  u16 msg_id_base;                            // API消息ID基址

  /* logging */
  vlib_log_class_t log_default;               // 默认日志类
  vlib_log_class_t log_cryptodev;             // 加密设备日志类
} dpdk_main_t;

extern dpdk_main_t dpdk_main;

关键字段详解

1. devices:设备数组
  • 作用:存储所有DPDK设备
  • 类型dpdk_device_t *(动态数组)
  • 通俗理解:就像"总档案室"的"设备档案柜",存放了所有"收货设备"(网卡)的"档案"

关键代码src/plugins/dpdk/device/init.c:48):

c 复制代码
dpdk_main_t dpdk_main;
2. per_thread_data:每线程数据数组
  • 作用:存储所有线程的每线程数据
  • 类型dpdk_per_thread_data_t *(动态数组)
  • 通俗理解:就像"总档案室"的"工作人员工作台档案柜",存放了所有"工作人员"(线程)的"工作台"信息

关键代码src/plugins/dpdk/device/init.c:311):

c 复制代码
vec_validate_aligned (dm->per_thread_data, tm->n_vlib_mains - 1,
                      CLIB_CACHE_LINE_BYTES);
3. admin_up_down_in_progress:管理启动/停止进行中标志
  • 作用:标志是否有管理启动/停止操作正在进行
  • 类型u8
  • 通俗理解:就像"总档案室"的"正在操作"标志,表示有"设备"正在启动或停止
  • 作用:控制链路状态轮询的间隔时间
  • 类型f64(秒)
  • 默认值DPDK_LINK_POLL_INTERVAL(3.0秒)
  • 通俗理解:就像"总档案室"的"设备状态检查频率",每隔一段时间检查一次"设备"的"连接状态"
5. stat_poll_interval:统计轮询间隔
  • 作用:控制统计信息轮询的间隔时间
  • 类型f64(秒)
  • 默认值DPDK_STATS_POLL_INTERVAL(10.0秒)
  • 通俗理解:就像"总档案室"的"工作记录更新频率",每隔一段时间更新一次"设备"的"工作记录"(统计信息)
6. conf:配置主结构体指针
  • 作用 :指向配置主结构体(dpdk_config_main_t
  • 类型dpdk_config_main_t *
  • 通俗理解:就像"总档案室"的"配置档案柜",存放了"分拣中心"的"配置信息"
7. default_port_conf:默认端口配置
  • 作用:存储默认的端口配置
  • 类型dpdk_port_conf_t
  • 通俗理解:就像"总档案室"的"默认配置模板",新"设备"使用这个"模板"进行配置

关键代码src/plugins/dpdk/device/init.c:324):

c 复制代码
dm->default_port_conf.n_rx_desc = DPDK_NB_RX_DESC_DEFAULT;
dm->default_port_conf.n_tx_desc = DPDK_NB_TX_DESC_DEFAULT;
dm->default_port_conf.n_rx_queues = 1;
dm->default_port_conf.n_tx_queues = tm->n_vlib_mains;
8. msg_id_base:API消息ID基址
  • 作用:API消息ID的基址(用于API消息编号)
  • 类型u16
  • 通俗理解:就像"总档案室"的"消息编号起始值",用于给API消息编号
9. log_defaultlog_cryptodev:日志类
  • 作用:用于日志记录
  • 类型vlib_log_class_t
  • 通俗理解:就像"总档案室"的"日志记录本",用于记录"分拣中心"的"运行日志"

全局变量

dpdk_main是全局变量,在src/plugins/dpdk/device/init.c:48中定义:

c 复制代码
dpdk_main_t dpdk_main;

数据结构关系图

复制代码
┌─────────────────────────────────────────┐
│      dpdk_main_t (全局)                  │
│  ┌───────────────────────────────────┐  │
│  │  devices (设备数组)                │  │
│  │    └── dpdk_device_t[0]           │  │
│  │    └── dpdk_device_t[1]           │  │
│  │    └── ...                        │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  per_thread_data (每线程数据数组)  │  │
│  │    └── dpdk_per_thread_data_t[0]  │  │
│  │    └── dpdk_per_thread_data_t[1]  │  │
│  │    └── ...                        │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  conf (配置主结构体指针)            │  │
│  │    └── dpdk_config_main_t          │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  default_port_conf (默认端口配置)   │  │
│  │    └── dpdk_port_conf_t            │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

通俗理解

dpdk_main_t就像"快递分拣中心"的"总档案室":

  • 设备档案柜devices,存放所有"收货设备"(网卡)的"档案"
  • 工作台档案柜per_thread_data,存放所有"工作人员"(线程)的"工作台"信息
  • 配置档案柜conf,存放"分拣中心"的"配置信息"
  • 默认配置模板default_port_conf,新"设备"使用的"配置模板"
  • 运行参数:轮询间隔、日志类等

使用场景

  • 设备查找 :通过devices数组查找设备
  • 线程数据访问 :通过per_thread_data数组访问线程数据
  • 配置管理 :通过conf访问配置信息
  • 统计轮询 :使用stat_poll_interval控制统计轮询
  • 链路状态轮询 :使用link_state_poll_interval控制链路状态轮询

3.6 流表相关结构体

作用和实现原理

流表相关结构体用于管理Flow Offload功能,将VPP的流表规则转换为DPDK的流表规则,实现硬件加速。就像"快递分拣中心"的"快速通道",某些"包裹"(数据包)可以走"快速通道"(Flow Offload),提高处理效率。

3.6.1 流表条目结构体(dpdk_flow_entry_t)

数据结构定义src/plugins/dpdk/device/dpdk.h:83):

c 复制代码
typedef struct
{
  u32 flow_index;                  // VPP流表索引
  u32 mark;                         // 流表标记(用于查找)
  struct rte_flow *handle;          // DPDK流表句柄
} dpdk_flow_entry_t;

关键字段详解

  • flow_index:VPP流表索引

    • 作用:指向VPP流表的索引
    • 类型u32
    • 通俗理解:就像"快速通道"在VPP系统中的"通道编号"
  • mark:流表标记

    • 作用:用于快速查找流表条目
    • 类型u32
    • 通俗理解:就像"快速通道"的"标记",用于快速识别
  • handle:DPDK流表句柄

    • 作用:DPDK流表的句柄(用于操作DPDK流表)
    • 类型struct rte_flow *
    • 通俗理解:就像"快速通道"在DPDK系统中的"通道句柄",用于操作"通道"
3.6.2 流表查找条目结构体(dpdk_flow_lookup_entry_t)

数据结构定义src/plugins/dpdk/device/dpdk.h:90):

c 复制代码
typedef struct
{
  u32 flow_id;                      // 流表ID
  u16 next_index;                   // 下一跳节点索引
  i16 buffer_advance;                // 缓冲区前进字节数
} dpdk_flow_lookup_entry_t;

关键字段详解

  • flow_id:流表ID

    • 作用:流表的唯一标识符
    • 类型u32
    • 通俗理解:就像"快速通道"的"通道ID",用于唯一标识"通道"
  • next_index:下一跳节点索引

    • 作用:数据包匹配流表后的下一跳节点
    • 类型u16
    • 通俗理解:就像"快速通道"的"出口","包裹"(数据包)匹配后应该送到哪个"部门"(下一跳节点)
  • buffer_advance:缓冲区前进字节数

    • 作用:数据包匹配流表后,缓冲区指针需要前进的字节数
    • 类型i16
    • 通俗理解:就像"快速通道"的"位置调整","包裹"(数据包)匹配后需要调整"位置"(缓冲区指针)

流表关系图

复制代码
┌─────────────────────────────────────────┐
│      dpdk_device_t                      │
│  ┌───────────────────────────────────┐  │
│  │  flow_entries (流表条目池)         │  │
│  │    └── dpdk_flow_entry_t[0]        │  │
│  │        flow_index = 0             │  │
│  │        mark = 1                   │  │
│  │        handle = ...                │  │
│  │    └── dpdk_flow_entry_t[1]        │  │
│  │        ...                        │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │  flow_lookup_entries (查找条目池)  │  │
│  │    └── dpdk_flow_lookup_entry_t[0] │  │
│  │        flow_id = 1                │  │
│  │        next_index = ...           │  │
│  │        buffer_advance = ...       │  │
│  │    └── dpdk_flow_lookup_entry_t[1] │  │
│  │        ...                        │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

通俗理解

流表相关结构体就像"快递分拣中心"的"快速通道":

  • 通道档案dpdk_flow_entry_t,记录了"快速通道"的"档案"(VPP索引、标记、DPDK句柄)
  • 通道查找表dpdk_flow_lookup_entry_t,记录了"快速通道"的"查找表"(流表ID、下一跳、位置调整)

使用场景

  • 流表创建 :创建dpdk_flow_entry_t并添加到flow_entries
  • 流表匹配 :使用mark快速查找流表条目
  • 流表删除 :使用handle删除DPDK流表
  • 数据包处理 :匹配流表后,使用flow_lookup_entries获取下一跳和位置调整

3.7 数据结构之间的关系

作用和实现原理

DPDK插件的各个数据结构之间存在清晰的层次关系和引用关系,形成了一个完整的数据结构体系。就像"快递分拣中心"的"组织架构",各个"部门"(数据结构)之间有明确的"上下级关系"和"协作关系"。

数据结构关系图

复制代码
┌─────────────────────────────────────────────────────────────┐
│              DPDK插件数据结构关系图                           │
└─────────────────────────────────────────────────────────────┘

                    ┌─────────────────┐
                    │  dpdk_main_t    │  [全局主结构体]
                    │  (总档案室)     │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ▼                    ▼                    ▼
┌──────────────┐   ┌──────────────────┐   ┌──────────────┐
│  devices[]   │   │ per_thread_data[]│   │  conf        │
│  (设备数组)  │   │ (每线程数据数组)  │   │ (配置)       │
└──────┬───────┘   └──────────────────┘   └──────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  dpdk_device_t                          │
│  (设备档案)                             │
│  ┌───────────────────────────────────┐ │
│  │  rx_queues[]                      │ │
│  │    └── dpdk_rx_queue_t[0]         │ │
│  │    └── dpdk_rx_queue_t[1]         │ │
│  │    └── ...                        │ │
│  └───────────────────────────────────┘ │
│  ┌───────────────────────────────────┐ │
│  │  tx_queues[]                      │ │
│  │    └── dpdk_tx_queue_t[0]         │ │
│  │    └── dpdk_tx_queue_t[1]         │ │
│  │    └── ...                        │ │
│  └───────────────────────────────────┘ │
│  ┌───────────────────────────────────┐ │
│  │  flow_entries (pool)              │ │
│  │    └── dpdk_flow_entry_t          │ │
│  └───────────────────────────────────┘ │
│  ┌───────────────────────────────────┐ │
│  │  flow_lookup_entries (pool)        │ │
│  │    └── dpdk_flow_lookup_entry_t    │ │
│  └───────────────────────────────────┘ │
│  ┌───────────────────────────────────┐ │
│  │  driver                            │ │
│  │    └── dpdk_driver_t              │ │
│  └───────────────────────────────────┘ │
│  ┌───────────────────────────────────┐ │
│  │  conf                             │ │
│  │    └── dpdk_port_conf_t           │ │
│  └───────────────────────────────────┘ │
└─────────────────────────────────────────┘

关系详解

1. dpdk_main_t → dpdk_device_t
  • 关系:一对多(一个主结构体包含多个设备)
  • 引用方式dpdk_main.devices[device_index]
  • 通俗理解:就像"总档案室"包含多个"设备档案"

关键代码src/plugins/dpdk/device/device.c:49):

c 复制代码
dpdk_main_t *dm = &dpdk_main;
dpdk_device_t *xd = vec_elt_at_index (dm->devices, hi->dev_instance);
2. dpdk_main_t → dpdk_per_thread_data_t
  • 关系:一对多(一个主结构体包含多个线程数据)
  • 引用方式dpdk_main.per_thread_data[thread_index]
  • 通俗理解:就像"总档案室"包含多个"工作人员工作台档案"

关键代码src/plugins/dpdk/device/device.c:286):

c 复制代码
dpdk_per_thread_data_t *ptd = vec_elt_at_index (dm->per_thread_data,
                                                  thread_index);
3. dpdk_device_t → dpdk_rx_queue_t
  • 关系:一对多(一个设备包含多个接收队列)
  • 引用方式dpdk_device.rx_queues[queue_index]
  • 通俗理解:就像"设备档案"包含多个"收货窗口档案"

关键代码src/plugins/dpdk/device/node.c:370):

c 复制代码
dpdk_rx_queue_t *rq = vec_elt_at_index (xd->rx_queues, queue_id);
4. dpdk_device_t → dpdk_tx_queue_t
  • 关系:一对多(一个设备包含多个发送队列)
  • 引用方式dpdk_device.tx_queues[queue_index]
  • 通俗理解:就像"设备档案"包含多个"发货窗口档案"

关键代码src/plugins/dpdk/device/device.c:173):

c 复制代码
dpdk_tx_queue_t *tq = vec_elt_at_index (xd->tx_queues, queue_id);
5. dpdk_device_t → dpdk_flow_entry_t
  • 关系:一对多(一个设备包含多个流表条目)
  • 引用方式pool_elt_at_index(dpdk_device.flow_entries, index)
  • 通俗理解:就像"设备档案"包含多个"快速通道档案"

关键代码src/plugins/dpdk/device/flow.c:754):

c 复制代码
fe = vec_elt_at_index (xd->flow_entries, *private_data);
6. dpdk_device_t → dpdk_flow_lookup_entry_t
  • 关系:一对多(一个设备包含多个流表查找条目)
  • 引用方式pool_elt_at_index(dpdk_device.flow_lookup_entries, index)
  • 通俗理解:就像"设备档案"包含多个"快速通道查找表条目"
7. dpdk_device_t → dpdk_driver_t
  • 关系:多对一(多个设备可以共享一个驱动)
  • 引用方式dpdk_device.driver
  • 通俗理解:就像多个"设备档案"可以共享一个"司机信息"
8. dpdk_device_t → dpdk_port_conf_t
  • 关系:一对一(一个设备有一个端口配置)
  • 引用方式dpdk_device.conf
  • 通俗理解:就像"设备档案"包含一个"配置档案"

数据访问流程示例

示例1:通过设备索引访问设备

c 复制代码
// 1. 获取全局主结构体
dpdk_main_t *dm = &dpdk_main;

// 2. 通过设备索引访问设备
dpdk_device_t *xd = vec_elt_at_index (dm->devices, device_index);

// 3. 访问设备的接收队列
dpdk_rx_queue_t *rq = vec_elt_at_index (xd->rx_queues, queue_index);

示例2:通过线程索引访问每线程数据

c 复制代码
// 1. 获取全局主结构体
dpdk_main_t *dm = &dpdk_main;

// 2. 获取线程索引
u32 thread_index = vlib_get_thread_index ();

// 3. 通过线程索引访问每线程数据
dpdk_per_thread_data_t *ptd = vec_elt_at_index (dm->per_thread_data,
                                                  thread_index);

// 4. 使用每线程数据进行批量接收
n = rte_eth_rx_burst (xd->port_id, queue_id, ptd->mbufs, n_to_rx);

通俗理解

数据结构之间的关系就像"快递分拣中心"的"组织架构":

  • 总档案室dpdk_main_t):包含所有"部门"(数据结构)的"档案"
  • 设备档案柜devices[]):存放所有"收货设备"(网卡)的"档案"
  • 工作台档案柜per_thread_data[]):存放所有"工作人员"(线程)的"工作台"信息
  • 设备档案dpdk_device_t):包含"设备"的所有信息
    • 收货窗口列表rx_queues[]):包含多个"收货窗口"(接收队列)
    • 发货窗口列表tx_queues[]):包含多个"发货窗口"(发送队列)
    • 快速通道档案flow_entries):包含多个"快速通道"(流表条目)
    • 司机信息driver):指向"司机信息"(驱动信息)
    • 配置档案conf):包含"设备"的"配置信息"

设计优势

  1. 层次清晰:数据结构层次分明,便于理解和维护
  2. 访问高效:通过索引直接访问,O(1)时间复杂度
  3. 内存局部性:相关数据集中存储,提高缓存命中率
  4. 扩展性好:动态数组支持动态扩展,适应不同规模的系统

3.8 本章总结

核心数据结构总结

数据结构 作用 关键字段 重要性
dpdk_device_t 代表一个DPDK设备 rx_queuestx_queuesport_idstats ⭐⭐⭐⭐⭐
dpdk_rx_queue_t 代表一个接收队列 buffer_pool_indexqueue_indexefd ⭐⭐⭐⭐
dpdk_tx_queue_t 代表一个发送队列 lockqueue_index ⭐⭐⭐⭐
dpdk_per_thread_data_t 每线程数据 mbufsbuffersnextetypeflags ⭐⭐⭐⭐⭐
dpdk_main_t 全局主结构体 devicesper_thread_dataconf ⭐⭐⭐⭐⭐
dpdk_flow_entry_t 流表条目 flow_indexmarkhandle ⭐⭐⭐
dpdk_flow_lookup_entry_t 流表查找条目 flow_idnext_indexbuffer_advance ⭐⭐⭐

数据结构设计特点

  1. 缓存行对齐 :使用CLIB_CACHE_LINE_ALIGN_MARK避免false sharing
  2. 动态数组 :使用VPP的vec机制实现动态数组
  3. 内存池 :使用VPP的pool机制实现内存池(流表条目)
  4. 索引访问:通过索引直接访问,O(1)时间复杂度
  5. 线程安全:TX队列使用锁保护,RX队列通常单线程访问

后续章节预告

  • 第4章:详细分析模块初始化流程,讲解这些数据结构是如何创建和初始化的
  • 第8章:详细讲解dpdk-input节点核心处理,讲解如何使用这些数据结构接收数据包
  • 第15章:详细讲解dpdk-output节点核心处理,讲解如何使用这些数据结构发送数据包

相关源码文件

  • src/plugins/dpdk/device/dpdk.h - 数据结构定义
  • src/plugins/dpdk/device/dpdk_priv.h - 私有数据结构定义

相关推荐
分***82 小时前
新版局域网IP扫描神器IPScanner.exe 绿色版V1.28.2,支持跨网段扫描_端口扫描_系统端口查看工具
网络·tcp/ip·端口扫描·ipscanner·局域网扫描
测试人社区-千羽2 小时前
智能测试的终极形态:从自动化到自主化的范式变革
运维·人工智能·python·opencv·测试工具·自动化·开源软件
秋刀鱼 ..2 小时前
2026年机器人感知与智能控制国际学术会议(RPIC 2026)
运维·人工智能·科技·金融·机器人·自动化
roman_日积跬步-终至千里3 小时前
【源码分析】StarRocks 跨集群数据迁移工具 - 基于快照进行的快速迁移
运维
千天夜3 小时前
文件系统磁盘块分配方式:从隐式链接到索引结构
网络·网络协议
Jerry952706283 小时前
1.无服务器架构入门
云原生·架构·serverless·无服务器架构
潘晓可3 小时前
Docker部署Bookstack
运维·docker·容器
Serverless社区3 小时前
阿里云新发的AgentRun 有哪些“大招”,一文详解来了
运维·阿里云·云原生·serverless
誰能久伴不乏3 小时前
深入理解 `poll` 函数:详细解析与实际应用
linux·服务器·c语言·c++·unix