目录
第一部分:DPDK插件的作用和意义
- 第1章:DPDK插件概述
- 1.1 DPDK插件在VPP中的作用和意义
- 1.2 DPDK插件与DPDK库的关系
- 1.3 DPDK插件在VPP数据包转发流程中的位置
- 1.4 DPDK插件的主要功能概述
- 1.5 与其他输入/输出模块的对比
第二部分:DPDK插件的整体架构
-
- 2.1 DPDK插件的文件组织结构
- 2.2 各文件的功能和职责
- 2.3 模块间的依赖关系
- 2.4 模块与外部系统的关系
-
- 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.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.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.1 驱动匹配机制
- 6.2 驱动特性配置
- 6.3 支持的驱动列表
- 6.4 驱动特定优化
-
- 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.1 dpdk-input节点的注册和类型
- 8.2 节点的主要处理函数(dpdk_input_node)
- 8.3 轮询向量(poll vector)的获取
- 8.4 多设备轮询机制
- 8.5 节点在VLIB图中的位置
-
- 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.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.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.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.1 dpdk-output节点的注册和类型
- 15.2 节点的主要处理函数(VNET_DEVICE_CLASS_TX_FN)
- 15.3 发送队列的选择机制
- 15.4 节点在VLIB图中的位置
- 15.5 Input和Output的协同工作
-
- 16.1 tx_burst_vector_internal函数详解
- 16.2 rte_eth_tx_burst调用和批量发送
- 16.3 vlib_buffer到mbuf的转换
- 16.4 发送队列锁定机制
- 16.5 批量发送优化
-
- 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.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.1 接收数据包和字节数的统计
- 20.2 发送数据包和字节数的统计
- 20.3 接口计数器的更新
- 20.4 DPDK统计信息的获取(dpdk_update_counters)
- 20.5 XSTATS统计的处理
- 20.6 统计更新的时机和频率
-
- 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.1 批量处理优化(burst processing)
- 22.2 预取(prefetch)优化
- 22.3 缓冲区模板优化
- 22.4 向量化处理
- 22.5 缓存行对齐
- 22.6 分支预测优化(PREDICT_TRUE/PREDICT_FALSE)
- 22.7 零拷贝技术
-
- 23.1 DPDK错误类型定义
- 23.2 错误处理机制
- 23.3 错误统计和报告
- 23.4 数据包丢弃的原因和处理
- 23.5 设备错误恢复
第七部分:管理和接口
-
- 24.1 DPDK相关的CLI命令
- 24.2 设备配置命令
- 24.3 统计查询命令
- 24.4 调试命令
- 24.5 API接口(如果有)
-
- 25.1 Cryptodev的概念和作用
- 25.2 Cryptodev的初始化
- 25.3 加密设备的管理
- 25.4 加密操作的数据路径
第八部分:总结
- 第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插件的核心价值:
-
高性能数据包处理:
- 利用DPDK的用户态驱动,绕过Linux内核,减少系统调用开销
- 实现零拷贝技术,数据包直接从网卡DMA到用户态内存
- 批量处理(burst),一次处理多个数据包,提高CPU缓存命中率
-
硬件卸载支持:
- IP校验和卸载:硬件计算IP校验和,减轻CPU负担
- L4校验和卸载:硬件计算TCP/UDP校验和
- TSO(TCP Segmentation Offload):硬件分片,提高大包发送效率
- LRO(Large Receive Offload):硬件合并,减少小包处理开销
- RSS(Receive Side Scaling):硬件负载均衡,多队列分发
-
生产环境就绪:
- 支持主流网卡(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库:
-
初始化阶段:
- 调用
rte_eal_init()初始化DPDK的EAL(Environment Abstraction Layer) - 调用
rte_eth_dev_configure()配置DPDK设备 - 调用
rte_mempool_create()创建内存池
- 调用
-
数据包接收阶段:
- 调用
rte_eth_rx_burst()批量接收数据包 - 获取DPDK的mbuf结构体
- 转换为VPP的buffer格式
- 调用
-
数据包发送阶段:
- 将VPP的buffer转换为DPDK的mbuf格式
- 调用
rte_eth_tx_burst()批量发送数据包
-
设备管理阶段:
- 调用
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节点的位置:
- 最前端:数据包进入VPP的第一个节点
- INPUT类型 :
VLIB_NODE_TYPE_INPUT,具有最高调度优先级 - device-input的兄弟节点 :
.sibling_of = "device-input",与其他设备输入节点共享相同的下一跳节点
dpdk-output节点的位置:
- 最末端:数据包离开VPP的最后一个节点
- 设备类TX函数 :
VNET_DEVICE_CLASS_TX_FN,由interface-output节点调用 - 设备类注册 :通过
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)
通俗理解:
就像"快递分拣中心"的工作流程:
- 接收:从"快递车"(DPDK)接收"包裹"(数据包)
- 转换:将"包裹"从"快递公司格式"(mbuf)转换为"分拣中心格式"(buffer)
- 处理:检查"包裹"的"标签"(硬件卸载标志),看是否需要特殊处理
- 发送:将"包裹"从"分拣中心格式"(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_PACKET 或AF_XDP
- 虚拟化环境 :使用Virtio 或Vhost-User
- 超低延迟 :使用RDMA
通俗理解:
就像"快递分拣中心"有多种"收货窗口"和"发货窗口":
- DPDK插件 = "专业快递窗口":接收/发送"专业快递公司"(DPDK网卡)的"包裹",性能最高
- Memif = "内部快递窗口":接收/发送"内部快递"(共享内存)的"包裹",零拷贝、低延迟
- AF_PACKET = "普通快递窗口":接收/发送"普通快递"(Linux接口)的"包裹",简单易用
- Virtio = "虚拟快递窗口":接收/发送"虚拟快递"(虚拟化环境)的"包裹",适合虚拟化
虽然"收货窗口"和"发货窗口"不同,但"包裹"进入"分拣中心"后,都会经过相同的"分拣流程"(device-input feature arc → ethernet-input → ...),最后通过相应的"发货窗口"发送出去。
1.6 本章总结
DPDK插件的关键特点:
- 位置:VPP数据包转发流程的最前端(接收)和最末端(发送)
- 作用:从DPDK管理的物理网卡接收/发送数据包,转换为VPP格式
- 关系:VPP与DPDK库之间的"桥梁",封装DPDK接口
- 功能:接收、转换、硬件卸载处理、发送、设备管理、统计监控
- 同类模块:与其他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.h、rte_mempool.h(DPDK库)、vlib/buffer.h(VPP) - 被依赖:
device/node.c、device/device.c、device/init.c、device/common.c
2.2.3 设备管理层 - 公共头文件
device/dpdk.h - 公共头文件
功能:
- 定义公共数据结构(
dpdk_device_t、dpdk_rx_queue_t、dpdk_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_process、admin_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.c → device/dpdk.h
main.c依赖device/dpdk.h获取设备相关的接口- 插件注册时需要引用设备相关的接口
2. device/init.c → buffer.c 和 device/dpdk.h
init.c依赖buffer.c创建缓冲区池init.c依赖dpdk.h中的数据结构定义
3. device/node.c → buffer.c 和 device/dpdk.h
node.c依赖buffer.c进行mbuf和buffer的转换node.c依赖dpdk.h中的数据结构定义
4. device/device.c → device/dpdk.h 和 buffer.c
device.c依赖dpdk.h中的数据结构device.c依赖buffer.c进行缓冲区管理
5. device/common.c → device/dpdk.h 和 buffer.c
common.c依赖dpdk.h中的数据结构common.c依赖buffer.c进行缓冲区池管理
6. device/driver.c → device/dpdk.h
driver.c依赖dpdk.h中的驱动相关数据结构
7. device/flow.c → device/dpdk.h
flow.c依赖dpdk.h中的流表相关数据结构
8. device/cli.c → device/dpdk.h 和 buffer.h
cli.c依赖dpdk.h和buffer.h显示信息
9. device/format.c → device/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}
)
关键配置说明:
- SOURCES:所有源文件列表
- MULTIARCH_SOURCES:支持多架构优化的源文件(性能关键路径)
- INSTALL_HEADERS:安装的公共头文件
- LINK_FLAGS:链接标志(DPDK库的特殊链接要求)
- LINK_LIBRARIES:链接的库(DPDK库、OpenSSL库)
多架构优化:
MULTIARCH_SOURCES中的文件会针对不同的CPU架构(如AVX、AVX2、AVX512)进行优化编译,提高性能关键路径的执行效率。
通俗理解:
就像"快递分拣中心"的"建设规划":
- SOURCES = "需要建设的部门":列出所有需要建设的"部门"(源文件)
- MULTIARCH_SOURCES = "重点优化部门":对"核心部门"(性能关键路径)进行"特殊优化"(多架构编译)
- INSTALL_HEADERS = "对外接口":安装"公共接口"(头文件),供其他模块使用
- LINK_FLAGS/LINK_LIBRARIES = "外部资源":链接"外部资源"(DPDK库、OpenSSL库)
2.6 本章总结
DPDK插件模块的文件组织特点:
-
分层清晰:按照功能分层,每层职责明确
- 入口层 → 数据定义层 → 功能实现层 → 核心功能层 → 接口层
-
模块化设计:每个文件负责特定功能,便于维护
- Input和Output分离,但共享数据结构和管理功能
-
依赖关系清晰:遵循依赖倒置原则,上层依赖下层
- 避免循环依赖,保持代码结构清晰
-
接口统一:通过公共头文件提供统一接口
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/ | 可选层 | 加密设备 | ⭐⭐ | ❌ |
文件组织优势:
- 可维护性:每个文件职责单一,便于理解和修改
- 可扩展性:新功能可以添加新文件,不影响现有代码
- 可测试性:每个模块可以独立测试
- 性能优化:关键路径文件支持多架构优化
后续章节预告:
- 第3章 :详细讲解核心数据结构(
dpdk_device_t、dpdk_rx_queue_t、dpdk_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_t(uint16_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发送数据包 - 统计查询 :读取
stats和xstats获取统计信息 - 流表管理 :使用
flow_entries和flow_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就像"快递分拣中心"的"工作人员工作台":
- 临时存放区 :
mbufs和buffers,用于存放批量接收的"包裹"(数据包) - 处理列表 :
next、etype、flags,记录了每个"包裹"的处理信息 - 标准模板 :
buffer_template,用于快速初始化"包裹"的"标签"
使用场景:
- 批量接收 :使用
mbufs数组批量接收数据包 - 批量处理 :使用
buffers、next、etype、flags数组批量处理数据包 - 性能优化:每个线程有独立的数据结构,避免数据竞争
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 - 通俗理解:就像"总档案室"的"正在操作"标志,表示有"设备"正在启动或停止
4. link_state_poll_interval:链路状态轮询间隔
- 作用:控制链路状态轮询的间隔时间
- 类型 :
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_default 和 log_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):包含"设备"的"配置信息"
- 收货窗口列表 (
设计优势:
- 层次清晰:数据结构层次分明,便于理解和维护
- 访问高效:通过索引直接访问,O(1)时间复杂度
- 内存局部性:相关数据集中存储,提高缓存命中率
- 扩展性好:动态数组支持动态扩展,适应不同规模的系统
3.8 本章总结
核心数据结构总结:
| 数据结构 | 作用 | 关键字段 | 重要性 |
|---|---|---|---|
| dpdk_device_t | 代表一个DPDK设备 | rx_queues、tx_queues、port_id、stats |
⭐⭐⭐⭐⭐ |
| dpdk_rx_queue_t | 代表一个接收队列 | buffer_pool_index、queue_index、efd |
⭐⭐⭐⭐ |
| dpdk_tx_queue_t | 代表一个发送队列 | lock、queue_index |
⭐⭐⭐⭐ |
| dpdk_per_thread_data_t | 每线程数据 | mbufs、buffers、next、etype、flags |
⭐⭐⭐⭐⭐ |
| dpdk_main_t | 全局主结构体 | devices、per_thread_data、conf |
⭐⭐⭐⭐⭐ |
| dpdk_flow_entry_t | 流表条目 | flow_index、mark、handle |
⭐⭐⭐ |
| dpdk_flow_lookup_entry_t | 流表查找条目 | flow_id、next_index、buffer_advance |
⭐⭐⭐ |
数据结构设计特点:
- 缓存行对齐 :使用
CLIB_CACHE_LINE_ALIGN_MARK避免false sharing - 动态数组 :使用VPP的
vec机制实现动态数组 - 内存池 :使用VPP的
pool机制实现内存池(流表条目) - 索引访问:通过索引直接访问,O(1)时间复杂度
- 线程安全:TX队列使用锁保护,RX队列通常单线程访问
后续章节预告:
- 第4章:详细分析模块初始化流程,讲解这些数据结构是如何创建和初始化的
- 第8章:详细讲解dpdk-input节点核心处理,讲解如何使用这些数据结构接收数据包
- 第15章:详细讲解dpdk-output节点核心处理,讲解如何使用这些数据结构发送数据包
相关源码文件:
src/plugins/dpdk/device/dpdk.h- 数据结构定义src/plugins/dpdk/device/dpdk_priv.h- 私有数据结构定义