目录
第一部分: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.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.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.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 设备错误恢复
第七部分:管理和接口
第24章:CLI和API接口
- 24.1 DPDK相关的CLI命令
- 24.2 设备配置命令
- 24.3 统计查询命令
- 24.4 调试命令
- 24.5 API接口(如果有)
第25章:加密设备支持(Cryptodev)
- 25.1 Cryptodev的概念和作用
- 25.2 Cryptodev的初始化
- 25.3 加密设备的管理
- 25.4 加密操作的数据路径
第八部分:总结
- 第26章:DPDK插件总结
- 26.1 DPDK插件的关键特点
- 26.2 在VPP数据包转发中的作用
- 26.3 性能优化要点
- 26.4 与其他模块的关系
- 26.5 最佳实践和注意事项
第19章:流表管理(Flow Offload)
本章概述:
第19章讲解Flow Offload流表的创建和删除机制。第12章已经详细讲解了流表匹配结果的处理(dpdk_process_flow_offload),本章重点讲解流表规则的创建、删除和VPP规则到DPDK规则的转换。流表管理通过dpdk_flow_ops_fn函数实现,支持ADD_FLOW和DEL_FLOW操作。
19.1 流表的创建(ADD_FLOW)
核心函数:dpdk_flow_ops_fn(src/plugins/dpdk/device/flow.c:729)
创建流程:
- 分配流表条目:从
xd->flow_entries池中分配一个dpdk_flow_entry_t结构 - 处理Mark动作:如果需要MARK/REDIRECT/BUFFER_ADVANCE动作,分配
flow_lookup_entry - 规则转换:调用
dpdk_flow_add将VPP流规则转换为DPDK流规则 - 安装规则:调用DPDK的
rte_flow_validate和rte_flow_create安装规则
关键代码(src/plugins/dpdk/device/flow.c:778):
/* 分配流表条目 */pool_get(xd->flow_entries,fe);fe->flow_index=flow->index;/* 如果需要Mark动作,分配lookup entry */if(flow->actions&(VNET_FLOW_ACTION_MARK|VNET_FLOW_ACTION_REDIRECT_TO_NODE|VNET_FLOW_ACTION_BUFFER_ADVANCE)){pool_get_aligned(xd->flow_lookup_entries,fle,CLIB_CACHE_LINE_BYTES);fe->mark=fle-xd->flow_lookup_entries;/* 初始化lookup entry */clib_memset(fle,-1,sizeof(*fle));if(flow->actions&VNET_FLOW_ACTION_MARK)fle->flow_id=flow->mark_flow_id;if(flow->actions&VNET_FLOW_ACTION_REDIRECT_TO_NODE)fle->next_index=flow->redirect_device_input_next_index;if(flow->actions&VNET_FLOW_ACTION_BUFFER_ADVANCE)fle->buffer_advance=flow->buffer_advance;}/* 启用Flow Offload功能(如果需要) */if((xd->flags&DPDK_DEVICE_FLAG_RX_FLOW_OFFLOAD)==0){xd->flags|=DPDK_DEVICE_FLAG_RX_FLOW_OFFLOAD;dpdk_device_setup(xd);// 重新配置设备以启用Flow Offload}/* 将VPP规则转换为DPDK规则并安装 */rv=dpdk_flow_add(xd,flow,fe);dpdk_flow_add函数:将VPP流规则转换为DPDK流规则(rte_flow_item和rte_flow_action),支持多种流类型(IP4、IP6、VXLAN、GTP等),然后调用rte_flow_create创建DPDK流规则。
19.2 流表的删除(DEL_FLOW)
删除流程:
- 查找流表条目:通过
private_data获取流表条目索引 - 销毁DPDK规则:调用
rte_flow_destroy销毁DPDK流规则 - 清理Lookup Entry:如果有mark,将lookup entry标记为无效并加入待回收列表
- 释放流表条目:将
dpdk_flow_entry_t返回到池中
关键代码(src/plugins/dpdk/device/flow.c:752):
if(op==VNET_FLOW_DEV_OP_DEL_FLOW){fe=vec_elt_at_index(xd->flow_entries,*private_data);/* 销毁DPDK流规则 */rv=rte_flow_destroy(xd->device_index,fe->handle,&xd->last_flow_error);if(fe->mark){/* 清理lookup entry:标记为无效,延迟回收(等待in-flight数据包处理完成) */fle=pool_elt_at_index(xd->flow_lookup_entries,fe->mark);clib_memset(fle,-1,sizeof(*fle));// 全部设置为0xFF,表示无效vec_add1(xd->parked_lookup_indexes,fe->mark);xd->parked_loop_count=vm->main_loop_count;}/* 释放流表条目 */clib_memset(fe,0,sizeof(*fe));pool_put(xd->flow_entries,fe);}延迟回收机制:删除流表时,lookup entry不会立即释放,而是延迟到主循环计数器增加后(确保in-flight数据包已处理)才回收。这样可以避免在处理中的数据包访问已释放的lookup entry。
19.3 流表条目的结构
dpdk_flow_entry_t(src/plugins/dpdk/device/dpdk.h:83):
typedefstruct{u32 flow_index;// VPP流规则索引u32 mark;// Lookup entry索引(如果为0表示不需要mark)structrte_flow*handle;// DPDK流规则句柄}dpdk_flow_entry_t;dpdk_flow_lookup_entry_t(src/plugins/dpdk/device/dpdk.h:90):
typedefstruct{u32 flow_id;// Flow ID(用于VNET_BUFFER_F_FLOW_REPORT)u16 next_index;// 下一跳节点索引(用于重定向)i16 buffer_advance;// Buffer前进字节数}dpdk_flow_lookup_entry_t;19.4 本章总结
关键要点:
- 规则转换:
dpdk_flow_add将VPP流规则转换为DPDK流规则 - 延迟回收:删除流表时,lookup entry延迟回收,避免in-flight数据包访问已释放内存
- 动态启用:首次创建流规则时,动态启用设备的Flow Offload功能
相关源码文件:
src/plugins/dpdk/device/flow.c:729-dpdk_flow_ops_fn函数src/plugins/dpdk/device/flow.c:182-dpdk_flow_add函数(规则转换)src/plugins/dpdk/device/dpdk.h:83- 流表条目结构定义
第20章:统计和计数
本章概述:
第20章讲解DPDK插件的统计和计数机制。统计信息包括:基础统计(接收/发送数据包和字节数)、错误统计、以及扩展统计(XSTATS)。统计更新通过dpdk_update_counters函数实现,在dpdk-process节点中定期轮询更新。
20.1 统计更新机制
核心函数:dpdk_update_counters(src/plugins/dpdk/device/dpdk_priv.h:100)
更新流程:
- 保存上次统计:将当前统计值保存到
last_stats - 获取最新统计:调用
rte_eth_stats_get获取DPDK最新统计 - 计算增量:计算统计增量(当前值 - 上次值)
- 更新VPP计数器:调用
vlib_increment_simple_counter更新VPP接口计数器 - 更新XSTATS:调用
dpdk_get_xstats更新扩展统计
关键代码(src/plugins/dpdk/device/dpdk_priv.h:100):
staticinlinevoiddpdk_update_counters(dpdk_device_t*xd,f64 now){vnet_main_t*vnm=vnet_get_main();u32 thread_index=vlib_get_thread_index();/* 保存上次统计值 */clib_memcpy_fast(&xd->last_stats,&xd->stats,sizeof(xd->last_stats));/* 获取最新统计 */rte_eth_stats_get(xd->port_id,&xd->stats);/* 更新接收错误统计 */DPDK_UPDATE_COUNTER(vnm,thread_index,xd,rx_nombuf,VNET_INTERFACE_COUNTER_RX_NO_BUF);DPDK_UPDATE_COUNTER(vnm,thread_index,xd,imissed,VNET_INTERFACE_COUNTER_RX_MISS);DPDK_UPDATE_COUNTER(vnm,thread_index,xd,ierrors,VNET_INTERFACE_COUNTER_RX_ERROR);/* 更新扩展统计 */dpdk_get_xstats(xd,thread_index);}DPDK_UPDATE_COUNTER宏:计算统计增量并更新VPP计数器,包含溢出检查(统计值不能减少)。
20.2 接收和发送统计
接收统计(src/plugins/dpdk/device/node.c:529):
/* 在dpdk_device_input节点中,每批接收后更新统计 */vlib_increment_combined_counter(im->combined_sw_if_counters+VNET_INTERFACE_COUNTER_RX,thread_index,xd->sw_if_index,n_rx_packets,n_rx_bytes);发送统计:在dpdk-output节点的发送函数中更新(见第16章)。
20.3 扩展统计(XSTATS)
初始化:dpdk_counters_xstats_init(src/plugins/dpdk/device/init.c:232)
- 调用
rte_eth_xstats_get_names获取XSTATS名称列表 - 为每个XSTATS创建统计条目和符号链接
- 符号链接路径:
/interfaces/<interface_name>/<xstat_name>
更新:dpdk_get_xstats(src/plugins/dpdk/device/dpdk_priv.h:52)
/* 获取XSTATS值并更新到统计系统 */ret=rte_eth_xstats_get(xd->port_id,xd->xstats,vec_len(xd->xstats));vec_foreach_index(i,xd->xstats){vlib_set_simple_counter(&xd->xstats_counters,thread_index,i,xd->xstats[i].value);}20.4 统计轮询
轮询机制(src/plugins/dpdk/device/init.c:1637):
dpdk-process节点定期轮询更新统计:
/* 默认轮询间隔:10秒 */#defineDPDK_STATS_POLL_INTERVAL(10.0)while(1){vec_foreach(xd,dm->devices){f64 now=vlib_time_now(vm);/* 检查是否到达轮询时间 */if((now-xd->time_last_stats_update)>=dm->stat_poll_interval)dpdk_update_counters(xd,now);}/* 等待下一次轮询 */vlib_process_wait_for_event_or_clock(vm,timeout);}20.5 本章总结
关键要点:
- 增量更新:只更新统计增量,减少计算开销
- 定期轮询:默认10秒轮询一次,可通过配置修改
- 扩展统计:支持设备特定的XSTATS统计,提供更详细的设备信息
相关源码文件:
src/plugins/dpdk/device/dpdk_priv.h:100-dpdk_update_counters函数src/plugins/dpdk/device/init.c:232-dpdk_counters_xstats_init函数src/plugins/dpdk/device/init.c:1637- 统计轮询机制
第21章:线程和队列管理
本章概述:
第21章讲解多线程支持和每线程数据结构。第18章已经详细讲解了发送队列管理,本章重点讲解每线程数据结构(per_thread_data)和接收队列的线程绑定。队列分配已在前面章节讲解,本章仅做简要回顾。
21.1 每线程数据结构
dpdk_per_thread_data_t(每线程数据):
/* 在dpdk_main_t中定义 */dpdk_per_thread_data_t*per_thread_data;// 每个线程一个/* 在节点中获取 */dpdk_per_thread_data_t*ptd=vec_elt_at_index(dm->per_thread_data,vm->thread_index);主要用途:
- 临时mbuf数组:
ptd->mbufs[]- 用于批量接收/发送时的mbuf指针数组 - 临时buffer数组:
ptd->buffers[]- 用于批量处理时的buffer索引数组 - 临时标志数组:
ptd->flags[]- 用于存储硬件卸载标志 - 临时next数组:
ptd->next[]- 用于存储下一跳索引
获取方式(src/plugins/dpdk/device/node.c:358):
dpdk_per_thread_data_t*ptd=vec_elt_at_index(dm->per_thread_data,vm->thread_index);21.2 接收队列的线程绑定
绑定机制(src/plugins/dpdk/device/init.c:550):
/* 如果指定了workers,按指定绑定 */if(devconf->workers){clib_bitmap_foreach(j,devconf->workers){dpdk_rx_queue_t*rxq=vec_elt_at_index(xd->rx_queues,q);rxq->queue_index=vnet_hw_if_register_rx_queue(vnm,xd->hw_if_index,q++,vdm->first_worker_thread_index+j);}}/* 否则,允许任意线程处理 */elsefor(q=0;q<xd->conf.n_rx_queues;q++){dpdk_rx_queue_t*rxq=vec_elt_at_index(xd->rx_queues,q);rxq->queue_index=vnet_hw_if_register_rx_queue(vnm,xd->hw_if_index,q,VNET_HW_IF_RXQ_THREAD_ANY);}关键差异:
- RX队列:支持指定线程绑定或任意线程处理(
VNET_HW_IF_RXQ_THREAD_ANY) - TX队列:默认轮询分配,支持共享队列(见第18章)
21.3 本章总结
关键要点:
- 每线程数据:每个线程有独立的数据结构,避免线程间竞争
- RX队列绑定:支持指定线程或任意线程处理
- TX队列共享:支持共享队列,多线程可共享同一个发送队列(见第18章)
相关源码文件:
src/plugins/dpdk/device/node.c:358- 每线程数据获取src/plugins/dpdk/device/init.c:550- 接收队列线程绑定
第22章:性能优化
本章概述:
第22章讲解DPDK插件中的关键性能优化技术。这些优化技术在前面的章节中已经使用,本章做系统性的总结和说明。
22.1 批量处理优化(Burst Processing)
原理:一次处理多个数据包,减少函数调用开销和分支预测失败。
应用场景:
- 批量接收:
rte_eth_rx_burst一次接收多个数据包(见第9章) - 批量发送:
rte_eth_tx_burst一次发送多个数据包(见第16章) - 批量处理:在
dpdk_process_rx_burst中批量转换和处理数据包
关键代码(src/plugins/dpdk/device/node.c:183):
/* 批量处理循环:一次处理4个或8个数据包 */while(n_left>=4){/* 处理4个数据包 *//* ... */n_left-=4;}22.2 预取优化(Prefetch)
原理:提前将数据加载到CPU缓存,隐藏内存访问延迟。
应用场景:
- 数据预取:在处理当前数据包时,预取下一个数据包的数据
- 指令预取:预取后续指令,减少指令缓存未命中
关键代码(src/plugins/dpdk/device/node.c:90):
static_always_inlinevoiddpdk_prefetch_buffer(vlib_main_t*vm,structrte_mbuf*mb){vlib_buffer_t*b=vlib_buffer_from_rte_mbuf(mb);CLIB_PREFETCH(b,CLIB_CACHE_LINE_BYTES,LOAD);CLIB_PREFETCH(b->data,CLIB_CACHE_LINE_BYTES,LOAD);}22.3 缓存行对齐
原理:将热点数据结构对齐到缓存行边界,避免false sharing(多个CPU核心访问同一缓存行的不同部分)。
应用场景:
- 队列结构:
dpdk_tx_queue_t和dpdk_rx_queue_t使用缓存行对齐 - 每线程数据:每线程数据结构使用缓存行对齐
关键代码(src/plugins/dpdk/device/dpdk.h:106):
typedefstruct{CLIB_CACHE_LINE_ALIGN_MARK(cacheline0);// 缓存行对齐标记clib_spinlock_tlock;u32 queue_index;}dpdk_tx_queue_t;22.4 分支预测优化
原理:使用PREDICT_TRUE和PREDICT_FALSE提示编译器优化分支预测。
应用场景:
/* 常见情况使用PREDICT_TRUE */if(PREDICT_TRUE(next_index==VNET_DEVICE_INPUT_NEXT_ETHERNET_INPUT)){/* 优化代码路径 */}/* 异常情况使用PREDICT_FALSE */if(PREDICT_FALSE(n_left)){/* 错误处理 */}22.5 零拷贝技术
原理:mbuf和vlib_buffer共享同一块内存,避免数据拷贝(见第9章)。
关键机制:
- 共享内存布局:
vlib_buffer_t紧跟在rte_mbuf之后 - 指针转换:通过宏快速转换指针,无需数据拷贝
22.6 本章总结
关键优化技术:
- 批量处理:减少函数调用开销
- 预取优化:隐藏内存访问延迟
- 缓存行对齐:避免false sharing
- 分支预测:优化热点路径
- 零拷贝:避免数据拷贝开销
相关源码文件:
src/plugins/dpdk/device/node.c:90- 预取函数src/plugins/dpdk/device/node.c:183- 批量处理循环src/plugins/dpdk/device/dpdk.h:106- 缓存行对齐
第23章:错误处理
本章概述:
第23章讲解DPDK插件的错误处理机制。错误处理包括:DPDK错误类型、错误统计、数据包丢弃原因和处理方式。部分错误处理已在前面章节讲解(如第18章的发送失败处理),本章做系统性总结。
23.1 DPDK错误类型
错误字符串定义(src/plugins/dpdk/device/node.c:33):
#defineforeach_dpdk_input_error\_(NONE,"no error")\_(UNKNOWN,"unknown")\_(NO_RX_BUFFER,"no rx buffer")\_(RX_ERROR,"rx error")#defineforeach_dpdk_output_error\_(NONE,"no error")\_(UNKNOWN,"unknown")\_(TX_FUNC_ERROR,"tx function error")\_(ERROR_PKT_DROP,"error packet drop")23.2 接收错误处理
错误统计:
- RX_NO_BUF:接收队列无可用缓冲区(
stats.rx_nombuf) - RX_MISS:接收队列满,数据包被丢弃(
stats.imissed) - RX_ERROR:接收错误(
stats.ierrors)
错误更新(见第20章统计更新):
DPDK_UPDATE_COUNTER(vnm,thread_index,xd,rx_nombuf,VNET_INTERFACE_COUNTER_RX_NO_BUF);DPDK_UPDATE_COUNTER(vnm,thread_index,xd,imissed,VNET_INTERFACE_COUNTER_RX_MISS);DPDK_UPDATE_COUNTER(vnm,thread_index,xd,ierrors,VNET_INTERFACE_COUNTER_RX_ERROR);23.3 发送错误处理
错误处理(见第18章):
- 检查发送结果:
rte_eth_tx_burst返回实际发送数量 - 更新错误统计:未发送的数据包计入
TX_ERROR计数器 - 释放mbuf:释放未发送的mbuf,避免资源泄漏
- 错误计数:记录节点级别的错误计数
关键代码(src/plugins/dpdk/device/device.c:426):
if(PREDICT_FALSE(n_left)){/* 更新错误统计 */vlib_increment_simple_counter(cm,thread_index,xd->sw_if_index,n_left);/* 记录错误计数 */vlib_error_count(vm,node->node_index,DPDK_TX_FUNC_ERROR_PKT_DROP,n_left);/* 释放mbuf */while(n_left--)rte_pktmbuf_free(ptd->mbufs[n_packets-n_left-1]);}23.4 设备错误恢复
设备状态检查:
- 链路状态:通过
dpdk_update_link_state定期检查链路状态 - 统计更新:通过
dpdk_update_counters定期更新统计,监控设备状态
错误恢复:
- 自动重试:发送失败时自动重试(最多16次,见第18章)
- 状态监控:通过定期轮询监控设备状态
23.5 本章总结
关键要点:
- 错误统计:所有错误都会更新到VPP统计系统
- 资源释放:错误时正确释放资源,避免资源泄漏
- 自动重试:发送失败时自动重试,提高可靠性
- 状态监控:定期监控设备状态,及时发现错误
相关源码文件:
src/plugins/dpdk/device/node.c:33- 错误字符串定义src/plugins/dpdk/device/device.c:426- 发送错误处理src/plugins/dpdk/device/dpdk_priv.h:82- 错误统计更新宏
第24章:CLI和API接口
本章概述:
第24章详细讲解DPDK插件的CLI命令和API接口。DPDK插件提供了多个CLI命令用于查看和配置DPDK设备,包括缓冲区管理、物理内存布局、设备描述符配置、版本信息等。此外,DPDK设备信息通过show hardware-interfaces命令显示,使用format_dpdk_device函数格式化输出。DPDK插件还支持Cryptodev(加密设备)的CLI命令。
24.1 DPDK CLI命令概览
DPDK插件提供的CLI命令如下:
| 命令 | 功能 | 源码位置 |
|---|---|---|
show dpdk buffer | 显示DPDK缓冲区(mempool)统计信息 | cli.c:43 |
show dpdk physmem | 显示DPDK物理内存布局 | cli.c:87 |
test dpdk buffer | 测试DPDK缓冲区的分配和释放 | cli.c:177 |
set dpdk interface descriptors | 设置DPDK接口的RX/TX描述符数量 | cli.c:263 |
show dpdk version | 显示DPDK版本和EAL初始化参数 | cli.c:346 |
show hardware-interfaces | 显示硬件接口信息(包括DPDK设备详细信息) | interface_cli.c |
Cryptodev CLI命令:
| 命令 | 功能 | 源码位置 |
|---|---|---|
show cryptodev assignment | 显示加密设备分配信息 | cryptodev.c:639 |
show cryptodev cache status | 显示加密设备缓存状态 | cryptodev.c:669 |
set cryptodev assignment | 设置加密设备到线程的分配 | cryptodev.c:752 |
24.2 CLI命令详解
24.2.1 show dpdk buffer
功能:显示所有DPDK内存池(mempool)的统计信息,包括可用缓冲区数量、已分配数量和总数量。
语法:
show dpdk buffer输出示例:
name="mbuf_pool_socket0" available = 15104 allocated = 1280 total = 16384实现代码(src/plugins/dpdk/device/cli.c:43):
staticclib_error_t*show_dpdk_buffer(vlib_main_t*vm,unformat_input_t*input,vlib_cli_command_t*cmd){vlib_buffer_main_t*bm=vm->buffer_main;vlib_buffer_pool_t*bp;vec_foreach(bp,bm->buffer_pools){structrte_mempool*rmp=dpdk_mempool_by_buffer_pool_index[bp->index];if(rmp){unsignedcount=rte_mempool_avail_count(rmp);// 可用数量unsignedfree_count=rte_mempool_in_use_count(rmp);// 已分配数量vlib_cli_output(vm,"name=\"%s\" available = %7d allocated = %7d total = %7d\n",rmp->name,(u32)count,(u32)free_count,(u32)(count+free_count));}}return0;}使用场景:
- 监控缓冲区使用情况
- 诊断缓冲区泄漏问题
- 验证缓冲区池配置
24.2.2 show dpdk physmem
功能:显示DPDK的物理内存布局信息,包括内存段、大页内存等信息。
语法:
show dpdk physmem实现代码(src/plugins/dpdk/device/cli.c:87):
staticclib_error_t*show_dpdk_physmem(vlib_main_t*vm,unformat_input_t*input,vlib_cli_command_t*cmd){// ... 设置管道和文件描述符 ...rte_dump_physmem_layout(f);// 调用DPDK函数dump内存布局fflush(f);// ... 读取并输出结果 ...vlib_cli_output(vm,"%v",s);return0;}使用场景:
- 查看DPDK内存使用情况
- 诊断内存相关问题
- 验证大页内存配置
24.2.3 test dpdk buffer
功能:测试DPDK缓冲区的分配和释放功能。可以分配指定数量的缓冲区,或释放之前分配的缓冲区。
语法:
test dpdk buffer [allocate <nn>] [free <nn>]参数说明:
allocate <nn>:分配nn个缓冲区free <nn>:释放nn个缓冲区(从最后分配的缓冲区开始释放)
示例:
# 显示当前分配的缓冲区数量vpp# test dpdk bufferCurrently0buffers allocated# 分配10个缓冲区vpp# test dpdk buffer allocate 10Currently10buffers allocated# 释放5个缓冲区vpp# test dpdk buffer free 5Currently5buffers allocated# 同时分配和释放(先执行free,后执行allocate)vpp# test dpdk buffer free 5 allocate 20Currently20buffers allocated实现代码(src/plugins/dpdk/device/cli.c:177):
staticclib_error_t*test_dpdk_buffer(vlib_main_t*vm,unformat_input_t*input,vlib_cli_command_t*cmd){staticu32*allocated_buffers;// 静态变量,保存已分配的缓冲区索引u32 n_alloc=0;u32 n_free=0;// 解析参数while(unformat_check_input(input)!=UNFORMAT_END_OF_INPUT){if(unformat(input,"allocate %d",&n_alloc));elseif(unformat(input,"free %d",&n_free));elsebreak;}// 先执行释放if(n_free){if(vec_len(allocated_buffers)<n_free)returnclib_error_return(0,"Can't free %d, only %d allocated",n_free,vec_len(allocated_buffers));first=vec_len(allocated_buffers)-n_free;vlib_buffer_free(vm,allocated_buffers+first,n_free);vec_set_len(allocated_buffers,first);}// 再执行分配if(n_alloc){first=vec_len(allocated_buffers);vec_validate(allocated_buffers,vec_len(allocated_buffers)+n_alloc-1);actual_alloc=vlib_buffer_alloc(vm,allocated_buffers+first,n_alloc);vec_set_len(allocated_buffers,first+actual_alloc);if(actual_alloc<n_alloc)vlib_cli_output(vm,"WARNING: only allocated %d buffers",actual_alloc);}vlib_cli_output(vm,"Currently %d buffers allocated",vec_len(allocated_buffers));return0;}使用场景:
- 测试缓冲区分配功能
- 验证缓冲区池是否有足够的可用缓冲区
- 调试缓冲区相关问题
24.2.4 set dpdk interface descriptors
功能:设置指定DPDK接口的RX和TX队列描述符数量。描述符数量决定了队列的大小,影响接口的性能和缓冲能力。
语法:
set dpdk interface descriptors <interface> [rx <nn>] [tx <nn>]参数说明:
<interface>:接口名称(如GigabitEthernet0/8/0)rx <nn>:RX队列描述符数量(可选)tx <nn>:TX队列描述符数量(可选)
示例:
# 设置RX和TX描述符数量为512vpp# set dpdk interface descriptors GigabitEthernet0/8/0 rx 512 tx 512# 只设置RX描述符数量vpp# set dpdk interface descriptors GigabitEthernet0/8/0 rx 1024# 只设置TX描述符数量vpp# set dpdk interface descriptors GigabitEthernet0/8/0 tx 1024实现代码(src/plugins/dpdk/device/cli.c:263):
staticclib_error_t*set_dpdk_if_desc(vlib_main_t*vm,unformat_input_t*input,vlib_cli_command_t*cmd){dpdk_main_t*dm=&dpdk_main;vnet_main_t*vnm=vnet_get_main();vnet_hw_interface_t*hw;dpdk_device_t*xd;u32 hw_if_index=(u32)~0;u32 nb_rx_desc=(u32)~0;u32 nb_tx_desc=(u32)~0;clib_error_t*error=NULL;// 解析接口名称和描述符数量while(unformat_check_input(line_input)!=UNFORMAT_END_OF_INPUT){if(unformat(line_input,"%U",unformat_vnet_hw_interface,vnm,&hw_if_index));elseif(unformat(line_input,"tx %d",&nb_tx_desc));elseif(unformat(line_input,"rx %d",&nb_rx_desc));}// 获取设备结构hw=vnet_get_hw_interface(vnm,hw_if_index);xd=vec_elt_at_index(dm->devices,hw->dev_instance);// 检查是否有变化if((nb_rx_desc==(u32)~0||nb_rx_desc==xd->conf.n_rx_desc)&&(nb_tx_desc==(u32)~0||nb_tx_desc==xd->conf.n_tx_desc)){error=clib_error_return(0,"nothing changed");gotodone;}// 更新配置if(nb_rx_desc!=(u32)~0)xd->conf.n_rx_desc=nb_rx_desc;if(nb_tx_desc!=(u32)~0)xd->conf.n_tx_desc=nb_tx_desc;// 重新配置设备dpdk_device_setup(xd);// 检查错误if(vec_len(xd->errors))returnclib_error_return(0,"%U",format_dpdk_device_errors,xd);done:returnerror;}注意事项:
- 描述符数量必须在设备支持的最小值和最大值之间
- 描述符数量必须是设备要求的对齐值的倍数
- 修改描述符数量会重新配置设备,可能导致接口短暂中断
使用场景:
- 优化接口性能(增加描述符数量可以提高吞吐量)
- 适应不同的流量模式
- 故障排除(如果队列溢出,可以增加描述符数量)
24.2.5 show dpdk version
功能:显示当前使用的DPDK版本和EAL(Environment Abstraction Layer)初始化参数。
语法:
show dpdk version输出示例:
DPDK Version: DPDK 22.11.0 DPDK EAL init args: --in-memory --no-telemetry --file-prefix vpp -w 0000:00:08.0 -w 0000:00:09.0实现代码(src/plugins/dpdk/device/cli.c:346):
staticclib_error_t*show_dpdk_version_command_fn(vlib_main_t*vm,unformat_input_t*input,vlib_cli_command_t*cmd){#define_(a,b,c)vlib_cli_output(vm,"%-25s "b,a":",c);_("DPDK Version","%s",rte_version());// 获取DPDK版本字符串_("DPDK EAL init args","%s",dpdk_config_main.eal_init_args_str);#undef_return0;}使用场景:
- 确认DPDK版本
- 查看EAL初始化参数(用于调试)
- 故障排除
24.3 show hardware-interfaces命令
功能:显示所有硬件接口的详细信息。对于DPDK设备,会调用format_dpdk_device函数显示详细的DPDK设备信息。
语法:
show hardware-interfaces [<interface>] [verbose [verbose]]参数说明:
<interface>:可选的接口名称,只显示指定接口的信息verbose:详细输出模式(可重复两次,verbose verbose表示最详细)
DPDK设备信息输出(通过format_dpdk_device函数,src/plugins/dpdk/device/format.c:411):
输出内容包括:
基本信息:
- 设备类型
- 链路状态(carrier up/down)
- 设备标志
队列配置:
- RX队列数量和描述符数量
- TX队列数量和描述符数量
- 队列描述符的限制(最小值、最大值、对齐值)
硬件信息:
- PCI设备信息(vendor ID、device ID、地址等)
- NUMA节点
- 交换机信息(如果适用)
功能特性:
- 混杂模式状态
- VLAN卸载配置
- RX卸载能力(可用和激活的)
- TX卸载能力(可用和激活的)
- RSS配置(可用和激活的)
- Burst模式信息
统计信息:
- 基础统计(接收/发送数据包和字节数、错误计数等)
- 扩展统计(XSTATS,如果启用verbose模式)
错误信息:
- 设备配置错误(如果有)
示例输出(部分):
GigabitEthernet0/8/0 carrier up flags: admin-up pmd rx: queues 1 (max 64), desc 1024 (min 64 max 4096 align 8) tx: queues 1 (max 64), desc 1024 (min 64 max 4096 align 8) pci: device 8086:1583 subsystem 8086:0001 address 0000:00:08.0 numa 0 max rx packet len: 9728 promiscuous: unicast off all-multicast off vlan offload: strip off filter off qinq off rx offload avail: cksum lro rx offload active: cksum tx offload avail: cksum tso tx offload active: cksum tso rss avail: ip4-tcp ip4-udp ip6-tcp ip6-udp rss active: ip4-tcp ip4-udp tx burst function: rte_eth_tx_burst rx burst function: rte_eth_rx_burst实现要点:
format_dpdk_device函数会:
- 更新统计信息(
dpdk_update_counters) - 更新链路状态(
dpdk_update_link_state) - 获取DPDK设备信息(
rte_eth_dev_info_get) - 格式化输出所有相关信息
24.4 Cryptodev CLI命令
24.4.1 show cryptodev assignment
功能:显示所有加密设备的分配信息,包括设备名称、队列ID和分配的线程。
语法:
show cryptodev assignment输出示例:
No. Name Queue-id Assigned-to 0 crypto_aesni_mb0 0 thread 1 1 crypto_aesni_mb1 0 thread 2使用场景:
- 查看加密设备配置
- 验证设备分配是否正确
24.4.2 show cryptodev cache status
功能:显示加密设备缓存状态,包括缓存的帧数、正在处理的帧数等详细信息。
语法:
show cryptodev cache status使用场景:
- 监控加密设备性能
- 诊断加密处理瓶颈
24.4.3 set cryptodev assignment
功能:将加密设备分配给指定的线程。
语法:
set cryptodev assignment thread <thread_index> resource <inst_index>参数说明:
<thread_index>:线程索引(不能是主线程0)<inst_index>:加密设备实例索引
示例:
# 将加密设备0分配给线程1vpp# set cryptodev assignment thread 1 resource 024.5 API接口
概述:DPDK插件本身没有提供专用的API接口,而是通过VPP的通用接口API来操作DPDK设备。DPDK设备信息通过format_dpdk_device函数在show hardware-interfaces的响应中返回。
相关API:
sw_interface_dump(
src/vnet/interface.api):- 用于获取软件接口信息
- 响应中包含硬件接口信息,对于DPDK设备会包含DPDK特定的信息
sw_interface_details:
- 返回接口详细信息
- 包含接口状态、MTU、链路速度等信息
API使用示例(Python):
fromvpp_papiimportVPP vpp=VPP(['/var/run/vpp/api.sock'])vpp.connect('my_app')# 获取所有接口信息interfaces=vpp.api.sw_interface_dump()forifaceininterfaces:print(f"Interface:{iface.interface_name}, "f"Index:{iface.sw_if_index}, "f"Flags:{iface.flags}")vpp.disconnect()注意事项:
- DPDK设备通过VPP的通用接口API管理
- 设备特定的信息通过格式化函数在CLI输出中显示
- 没有DPDK专用的API消息类型
24.6 本章总结
DPDK CLI命令总结:
| 命令 | 主要用途 | 关键特性 |
|---|---|---|
show dpdk buffer | 监控缓冲区使用 | 显示mempool统计 |
show dpdk physmem | 查看内存布局 | DPDK内存dump |
test dpdk buffer | 测试缓冲区分配 | 支持allocate/free |
set dpdk interface descriptors | 配置队列大小 | 动态修改描述符数量 |
show dpdk version | 查看版本信息 | 显示DPDK版本和EAL参数 |
show hardware-interfaces | 显示设备详细信息 | 包含DPDK设备完整信息 |
关键设计要点:
- CLI为主:DPDK插件主要通过CLI命令进行管理和监控
- 格式化函数:使用格式化函数统一输出格式
- 通用API:通过VPP通用接口API管理,无专用API
- 实时更新:
show hardware-interfaces命令会实时更新统计和状态
相关源码文件:
src/plugins/dpdk/device/cli.c- CLI命令实现src/plugins/dpdk/device/format.c- 格式化输出函数src/vnet/interface_cli.c- 硬件接口CLI命令src/plugins/dpdk/cryptodev/cryptodev.c- Cryptodev CLI命令
第25章:加密设备支持(Cryptodev)
本章概述:
第25章详细讲解DPDK插件中的Cryptodev(加密设备)支持。Cryptodev是DPDK提供的加密加速框架,允许VPP使用硬件加密设备或软件加密库来加速加密和解密操作。本章用通俗易懂的语言讲解Cryptodev的概念、工作原理、初始化过程和数据路径处理,帮助读者深入理解VPP中加密操作的硬件加速机制。
25.1 什么是Cryptodev?——用"快递加密中心"来理解
通俗理解:
想象一下"快递加密中心"的工作场景:
传统方式(软件加密):
- 快递员(CPU)自己用"密码本"(软件算法)给包裹加密
- 速度慢,占用CPU资源,影响其他工作
硬件加速方式(Cryptodev):
- 快递员把包裹交给"专业加密机器"(硬件加密设备)
- 加密机器专门负责加密,速度快,不占用CPU
- 快递员可以做其他事情,提高整体效率
专业定义:
**Cryptodev(Crypto Device)**是DPDK提供的加密设备抽象框架,它:
- 抽象硬件加密设备:将不同厂商的硬件加密设备(如Intel QAT、软件加密库等)统一抽象
- 提供统一接口:为VPP提供统一的加密操作接口
- 加速加密操作:利用硬件加速,大幅提升加密/解密性能
为什么需要Cryptodev?
- 性能需求:软件加密慢,硬件加密快(可能快10-100倍)
- CPU资源释放:让CPU专注于数据包转发,加密交给硬件
- 统一接口:不同硬件设备使用统一的接口,便于管理
25.2 Cryptodev的核心概念
25.2.1 加密设备(Cryptodev Device)
通俗理解:
就像"快递加密中心"有不同的"加密机器":
- Intel QAT(QuickAssist Technology):高性能硬件加密卡
- 软件加密库(如AES-NI):使用CPU的特殊指令加速加密
- 其他硬件加速卡:各种厂商的加密硬件
代码定义(src/plugins/dpdk/cryptodev/cryptodev.h):
typedefstruct{u32 dev_id;// DPDK设备IDu32 n_qp;// 队列对(Queue Pair)数量// ... 其他字段}cryptodev_inst_t;关键点:
- 一个Cryptodev设备可以有多个队列对(Queue Pair)
- 每个队列对可以独立处理加密操作
25.2.2 加密会话(Crypto Session)
通俗理解:
“加密会话"就像"加密机器"的"配置模板”:
会话包含:
- 加密算法(如AES-GCM、AES-CBC等)
- 加密密钥
- IV(初始化向量)长度
- AAD(Additional Authenticated Data)长度
为什么需要会话:
- 每次加密都需要这些参数,提前配置好可以重复使用
- 就像"快递加密机器"的"预设模式",选择后直接使用,不用每次都重新设置
代码定义:
typedefstruct{structrte_cryptodev_sym_session*sess[CRYPTODEV_N_OP_TYPES];// CRYPTODEV_N_OP_TYPES = 2 (加密和解密各一个会话)}cryptodev_session_t;会话创建流程:
准备加密参数(Xform):
// 准备AEAD加密参数prepare_aead_xform(xforms_enc,CRYPTODEV_OP_TYPE_ENCRYPT,key,aad_len);创建DPDK会话:
sessions[CRYPTODEV_OP_TYPE_ENCRYPT]=rte_cryptodev_sym_session_create(dev_id,xforms_enc,sess_pool);保存会话:会话保存在会话池(session pool)中,可以重复使用
25.2.3 加密操作(Crypto Operation)
通俗理解:
“加密操作"就是"一次具体的加密任务”:
- 包含信息:
- 要加密的数据
- 使用的会话(包含算法和密钥)
- 操作类型(加密或解密)
操作类型:
AEAD(Authenticated Encryption with Associated Data):
- 同时进行加密和认证
- 例如:AES-GCM、ChaCha20-Poly1305
- 就像"加密+验证签名"一次性完成
链接算法(Linked Algorithms):
- 先加密,后认证(或相反)
- 例如:AES-CBC + HMAC-SHA256
- 就像"先加密,再添加数字签名"
代码示例(src/plugins/dpdk/cryptodev/cryptodev_op_data_path.c):
/* 加密操作的结构(简化) */structrte_crypto_op{structrte_cryptodev_sym_session*sess;// 使用的会话structrte_crypto_sym_opsym[0];// 对称加密操作// ... 其他字段};25.2.4 队列对(Queue Pair)
通俗理解:
“队列对"就像"加密中心"的"工作窗口”:
每个队列对包含:
- 输入队列:待处理的加密操作(enqueue)
- 输出队列:处理完成的加密操作(dequeue)
多队列对的好处:
- 多个"窗口"可以并行工作
- 提高吞吐量,减少排队等待
代码定义:
// 队列对配置structrte_cryptodev_qp_conf{structrte_mempool*mp_session;// 会话内存池u32 nb_descriptors;// 描述符数量(队列大小)};// 配置队列对rte_cryptodev_queue_pair_setup(cryptodev_id,queue_id,&qp_cfg,numa_node);25.3 Cryptodev的初始化过程
通俗理解:
就像"快递加密中心"的"开业准备":
- 检查有哪些加密机器(设备探测)
- 给每台机器配置工作窗口(队列对设置)
- 准备加密模板库(会话池创建)
- 分配工作窗口给快递员(线程绑定)
初始化流程(src/plugins/dpdk/cryptodev/cryptodev.c:1268):
步骤1:设备探测(cryptodev_probe)
clib_error_t*dpdk_cryptodev_init(vlib_main_t*vm){cryptodev_main_t*cmt=&cryptodev_main;// 探测所有加密设备if(cryptodev_probe(vm,n_workers)<0)return0;// ...}通俗理解:
- 就像"检查仓库里有哪些加密机器可用"
- 可能是硬件加密卡,也可能是软件加密库
步骤2:设备配置(cryptodev_configure)
staticintcryptodev_configure(vlib_main_t*vm,u32 cryptodev_id){structrte_cryptodev_configcfg;structrte_cryptodev_infoinfo;// 获取设备信息rte_cryptodev_info_get(cryptodev_id,&info);// 配置设备cfg.socket_id=info.device->numa_node;// NUMA节点cfg.nb_queue_pairs=info.max_nb_queue_pairs;// 队列对数量rte_cryptodev_configure(cryptodev_id,&cfg);// 配置每个队列对for(i=0;i<info.max_nb_queue_pairs;i++){qp_cfg.nb_descriptors=CRYPTODEV_NB_CRYPTO_OPS;// 队列大小:1024rte_cryptodev_queue_pair_setup(cryptodev_id,i,&qp_cfg,numa_node);}// 启动设备rte_cryptodev_start(cryptodev_id);return0;}通俗理解:
- 给每台"加密机器"配置"工作窗口"(队列对)
- 每个窗口可以排队1024个任务
- 配置完成后,"启动机器"开始工作
步骤3:创建会话池(allocate_session_pools)
clib_error_t*allocate_session_pools(u32 numa_node,cryptodev_session_pool_t*sess_pools_elt,u32 len){// 创建会话池sess_pools_elt->sess_pool=rte_cryptodev_sym_session_pool_create((char*)name,// 池名称CRYPTODEV_NB_SESSION,// 会话数量:4096cmt->sess_sz,// 每个会话的大小0,0,numa_node);// NUMA节点return0;}通俗理解:
- 创建"加密模板库",可以存储4096个"加密配置模板"(会话)
- 每个模板包含算法、密钥等信息
- 需要加密时,直接使用模板,不用每次都重新配置
步骤4:线程绑定(cryptodev_assign_resource)
for(i=skip_master;i<tm->n_vlib_mains;i++){cet=cmt->per_thread_data+i;// 自动分配加密设备资源给线程cryptodev_assign_resource(cet,0,CRYPTODEV_RESOURCE_ASSIGN_AUTO);}通俗理解:
- 将"加密机器"的"工作窗口"分配给不同的"快递员"(线程)
- 每个线程可以使用自己的"专用窗口",避免竞争
25.4 加密操作的数据路径
通俗理解:
就像"快递加密中心"的"工作流程":
- 接收任务(Enqueue):快递员把需要加密的包裹放入"工作窗口"
- 机器处理(硬件加密):加密机器自动处理
- 取回结果(Dequeue):快递员从"完成窗口"取回加密好的包裹
25.4.1 入队(Enqueue)——提交加密任务
AEAD加密入队(src/plugins/dpdk/cryptodev/cryptodev_op_data_path.c:290):
static_always_inline u32cryptodev_aead_enqueue_internal(vlib_main_t*vm,vnet_crypto_async_frame_t*frame,cryptodev_op_type_top_type,u32 aad_len){cryptodev_engine_thread_t*cet=cryptodev_get_thread_data(vm);vlib_buffer_t*b;structrte_crypto_op*cop;u32 n_ops=0;// 遍历帧中的每个数据包for(i=0;i<frame->n_elts;i++){// 获取加密操作描述符cop=rte_crypto_op_alloc(cet->cop_pool,RTE_CRYPTO_OP_TYPE_SYMMETRIC);// 设置会话(使用预先创建的会话)cop->sym->session=frame->sessions[op_type][i];// 设置源数据(要加密的数据)cop->sym->m_src=rte_mbuf_from_vlib_buffer(b);// 设置目标数据(加密后的数据,通常与源相同)cop->sym->m_dst=cop->sym->m_src;// 设置IV(初始化向量)cop->sym->aead.iv.data=frame->ivs[i];cop->sym->aead.iv.length=12;// AES-GCM使用12字节IV// 设置AAD(Additional Authenticated Data)cop->sym->aead.aad.data=frame->aads[i];cop->sym->aead.aad.length=aad_len;// 添加到队列ops[n_ops++]=cop;}// 批量提交到加密设备n_enqueued=rte_cryptodev_enqueue_burst(cet->cryptodev_id,// 设备IDcet->cryptodev_q,// 队列IDops,// 操作数组n_ops);// 操作数量returnn_enqueued;}通俗理解:
准备包裹:
- 获取"加密任务单"(crypto op)
- 填写"加密模板编号"(session)
- 填写"要加密的数据"(source data)
- 填写"加密参数"(IV、AAD等)
提交任务:
- 把任务单放入"工作窗口"(队列)
- 加密机器会自动处理
关键点:
- 批量提交:一次提交多个任务,提高效率
- 零拷贝:通常源数据和目标数据是同一个buffer,避免数据拷贝
25.4.2 出队(Dequeue)——获取加密结果
出队处理(src/plugins/dpdk/cryptodev/cryptodev_op_data_path.c:464):
static_always_inline u32cryptodev_frame_dequeue_internal(vlib_main_t*vm,u32*enqueue_thread_idx){cryptodev_engine_thread_t*cet=cryptodev_get_thread_data(vm);structrte_crypto_op*ops[CRYPTODE_DEQ_MAX];u32 n_dequeued,i;// 从加密设备批量获取完成的操作n_dequeued=rte_cryptodev_dequeue_burst(cet->cryptodev_id,// 设备IDcet->cryptodev_q,// 队列IDops,// 操作数组CRYPTODE_DEQ_MAX);// 最大数量:64// 处理每个完成的操作for(i=0;i<n_dequeued;i++){cop=ops[i];// 检查操作状态if(cop->status==RTE_CRYPTO_OP_STATUS_SUCCESS){// 加密成功,更新帧状态frame->frame_index=cop->user_data;frame->elts[cop->user_data].status=VNET_CRYPTO_FRAME_ELT_STATUS_SUCCESS;}else{// 加密失败,标记错误frame->elts[cop->user_data].status=VNET_CRYPTO_FRAME_ELT_STATUS_FAILED;}// 释放操作描述符rte_crypto_op_free(cop);}returnn_dequeued;}通俗理解:
检查完成窗口:
- 从"完成窗口"批量取回处理完的任务(最多64个)
检查结果:
- 如果成功:标记为成功,继续后续处理
- 如果失败:标记为失败,可能需要重试或丢弃
回收资源:
- 释放"任务单",可以重复使用
关键点:
- 批量处理:一次处理多个结果,提高效率
- 状态检查:每个操作都有状态,可以判断成功或失败
25.5 缓存队列(Cache Queue)机制
通俗理解:
就像"快递加密中心"的"中转站":
- 为什么需要缓存队列?
- 加密设备可能很忙,不能立即处理
- 先把任务放在"中转站",等有空时再批量提交
缓存队列结构(简化):
typedefstruct{u16 head;// 队头(写入位置)u16 tail;// 队尾(读取位置)cryptodev_cache_ring_elt_tframes[CRYPTODEV_CACHE_QUEUE_SIZE];// 队列大小:256}cryptodev_cache_ring_t;工作流程:
接收任务:
- 加密任务先放入缓存队列(如果设备忙)
批量提交:
- 当缓存队列积累了一定数量的任务,批量提交到加密设备
处理结果:
- 从设备取回结果,继续后续处理
好处:
- 减少系统调用:批量提交减少与加密设备的交互次数
- 提高吞吐量:设备可以更高效地处理批量任务
- 平滑流量:缓存可以平滑突发流量
25.6 与VPP加密框架的集成
通俗理解:
就像"快递加密中心"与"快递公司"的"工作对接":
- VPP加密框架:定义了"加密服务"的标准接口
- Cryptodev插件:实现了具体的"加密服务提供商"
注册流程(src/plugins/dpdk/cryptodev/cryptodev.c:1338):
clib_error_t*dpdk_cryptodev_init(vlib_main_t*vm){// ... 初始化设备 ...// 注册加密引擎到VPPeidx=vnet_crypto_register_engine(vm,"dpdk_cryptodev",// 引擎名称100,// 优先级(越高越优先)cryptodev_key_handler,// 密钥处理函数cryptodev_sess_handler);// 会话处理函数// 注册入队/出队函数if(cmt->is_raw_api){// 使用Raw API(新版本DPDK)vnet_crypto_register_handlers(vm,eidx,cryptodev_raw_enqueue_handlers,cryptodev_raw_dequeue);}else{// 使用传统API(旧版本DPDK)vnet_crypto_register_handlers(vm,eidx,cryptodev_enqueue_handlers,cryptodev_dequeue);}return0;}通俗理解:
注册服务:
- 告诉VPP:“我是加密服务提供商,名字叫dpdk_cryptodev”
- 设置优先级:100(数字越大,优先级越高)
提供服务:
- 提供密钥管理服务(key_handler)
- 提供会话管理服务(sess_handler)
- 提供加密操作服务(enqueue/dequeue handlers)
自动选择:
- 当VPP需要加密时,会根据优先级自动选择使用哪个加密引擎
- 如果Cryptodev可用且优先级高,就会使用它
25.7 支持的加密算法
AEAD算法(同时加密和认证):
AES-GCM:
- AES-128-GCM、AES-192-GCM、AES-256-GCM
- 广泛使用,性能好
ChaCha20-Poly1305:
- 新兴算法,在某些场景下性能更好
链接算法(加密+认证分开):
- AES-CBC + HMAC-SHA:
- AES-CBC-HMAC-SHA256、AES-CBC-HMAC-SHA512等
算法检查(src/plugins/dpdk/cryptodev/cryptodev.c:247):
static_always_inlineintcryptodev_check_supported_vnet_alg(vnet_crypto_key_t*key){// 检查设备是否支持该算法if(key->is_link){// 链接算法:检查加密算法和认证算法check_cipher_support(cipher_algo,key_size)&&check_auth_support(auth_algo,digest_size);}else{// AEAD算法:检查AEAD算法check_aead_support(aead_algo,key_size,digest_size,aad_size);}return1;// 支持}通俗理解:
- 就像"加密机器"有"能力清单"
- 使用前先检查:“这台机器能不能用AES-GCM?”
- 如果支持,就使用;不支持,就用软件加密或其他引擎
25.8 性能优化要点
25.8.1 批量处理
通俗理解:
- 一次提交多个任务,比一个一个提交快得多
- 就像"批量打包"比"单个打包"效率高
代码实现:
// 批量提交:一次最多提交64个操作n_enqueued=rte_cryptodev_enqueue_burst(dev_id,queue_id,ops,64);// 批量取回:一次最多取回64个结果n_dequeued=rte_cryptodev_dequeue_burst(dev_id,queue_id,ops,64);25.8.2 零拷贝
通俗理解:
- 加密前后使用同一个buffer,避免数据拷贝
- 就像"在原包裹上直接贴加密标签",不用重新打包
代码实现:
// 源数据和目标数据是同一个cop->sym->m_src=rte_mbuf_from_vlib_buffer(b);cop->sym->m_dst=cop->sym->m_src;// 零拷贝25.8.3 会话复用
通俗理解:
- 同一个"加密配置"可以重复使用
- 就像"预设模板"可以反复使用,不用每次都重新配置
代码实现:
// 会话创建一次,可以重复使用structrte_cryptodev_sym_session*sess=rte_cryptodev_sym_session_create(dev_id,xform,sess_pool);// 每次加密时直接使用cop->sym->session=sess;// 复用会话25.9 实际应用场景
场景1:IPSec VPN
通俗理解:
- 当数据包需要IPSec加密时,使用Cryptodev加速
- 就像"通过VPN发送的包裹"都需要加密
工作流程:
- 数据包进入IPSec处理节点
- 需要加密,调用VPP加密框架
- 加密框架选择Cryptodev引擎
- Cryptodev使用硬件加速加密
- 加密完成,继续转发
场景2:TLS/SSL加速
通俗理解:
- HTTPS流量需要TLS加密,使用Cryptodev加速
- 就像"网购的包裹"需要加密保护
25.10 本章总结
核心概念总结:
| 概念 | 通俗理解 | 作用 |
|---|---|---|
| Cryptodev设备 | 加密机器 | 提供硬件加密能力 |
| 加密会话 | 加密模板 | 预先配置加密参数 |
| 加密操作 | 加密任务 | 一次具体的加密请求 |
| 队列对 | 工作窗口 | 提交任务和取回结果 |
| 缓存队列 | 中转站 | 批量提交,提高效率 |
关键流程:
- 初始化:探测设备 → 配置队列 → 创建会话池 → 绑定线程
- 加密流程:准备操作 → 入队提交 → 硬件处理 → 出队获取结果
- 性能优化:批量处理 + 零拷贝 + 会话复用
性能优势:
- 速度:硬件加密比软件加密快10-100倍
- CPU释放:CPU可以专注于数据包转发
- 吞吐量:支持高并发加密处理
相关源码文件:
src/plugins/dpdk/cryptodev/cryptodev.c- 主要实现文件src/plugins/dpdk/cryptodev/cryptodev.h- 头文件定义src/plugins/dpdk/cryptodev/cryptodev_op_data_path.c- 传统API数据路径src/plugins/dpdk/cryptodev/cryptodev_raw_data_path.c- Raw API数据路径
进一步学习:
- DPDK Cryptodev API文档
- VPP加密框架文档
- Intel QAT文档(如果使用QAT硬件)
第26章:DPDK插件总结
本章概述:
本章对整个文档进行总结,概括DPDK插件的核心特点、在VPP中的作用、性能优化要点、与其他模块的关系以及最佳实践。
26.1 DPDK插件核心特点
1. 统一的数据路径
- 零拷贝设计:
rte_mbuf和vlib_buffer_t共享内存,避免数据拷贝 - 批量处理:批量接收/发送,提高吞吐量
- 硬件卸载:支持校验和、VLAN、RSS等硬件加速
2. 高性能架构
- 多队列支持:每个设备支持多个RX/TX队列
- 多线程优化:每线程独立数据结构,避免竞争
- 缓存行对齐:避免false sharing
3. 灵活的配置
- 动态配置:支持运行时修改队列描述符数量
- 流表管理:支持硬件Flow Offload
- 硬件卸载:可配置启用/禁用各种卸载功能
26.2 在VPP数据包转发中的作用
接收路径(Input):
网络接口 → dpdk-input节点 → 硬件卸载处理 → 多段包处理 → 流表匹配 → LRO处理 → 下一跳选择 → ethernet-input发送路径(Output):
interface-output → dpdk-output节点 → TX硬件卸载 → mbuf验证 → rte_eth_tx_burst → 网络接口关键作用:
- 数据入口:dpdk-input节点是DPDK设备数据包进入VPP的第一站
- 数据出口:dpdk-output节点是数据包离开VPP的最后一步
- 性能基础:提供高性能的数据包I/O能力,支撑整个转发系统
26.3 性能优化要点
1. 批量处理优化
- 批量接收:
rte_eth_rx_burst一次接收多个数据包 - 批量发送:
rte_eth_tx_burst一次发送多个数据包 - 批量处理循环:在节点中使用4/8路循环处理
2. 内存优化
- 零拷贝:mbuf和buffer共享内存
- 缓冲区模板:快速初始化buffer元数据
- 预取优化:提前加载数据到CPU缓存
3. 硬件加速
- 校验和卸载:IP和L4校验和由硬件处理
- TSO/LRO:大包分段和合并由硬件处理
- RSS:硬件负载均衡
- Flow Offload:硬件流表匹配
4. 线程优化
- 每线程数据:避免共享数据竞争
- 队列绑定:合理分配队列到线程
- 条件锁定:只在共享队列时加锁
26.4 与其他模块的关系
与VPP核心模块:
- VLIB框架:作为VLIB节点实现,参与图调度
- VNET接口系统:通过
vnet_device_class注册为设备类 - 缓冲区系统:使用VPP的buffer pool机制
与加密模块:
- Cryptodev插件:DPDK插件的子模块,提供硬件加密加速
与IPSec模块:
- 加密框架:通过VPP加密框架为IPSec提供硬件加速
与其他输入/输出模块:
- 互补关系:与其他设备驱动(virtio、vhost等)并存
- 统一接口:都通过VNET设备类接口管理
26.5 关键设计模式
1. 设备类模式
VNET_DEVICE_CLASS(dpdk_device_class)={.name="dpdk",.tx_function=dpdk_device_class_tx_fn,.format_device=format_dpdk_device,// ...};2. 节点模式
VLIB_REGISTER_NODE(dpdk_input_node)={.function=dpdk_input,.type=VLIB_NODE_TYPE_INPUT,// ...};3. 每线程数据模式
dpdk_per_thread_data_t*ptd=vec_elt_at_index(dm->per_thread_data,vm->thread_index);26.6 最佳实践
1. 队列配置
- RX队列数:建议等于或大于工作线程数
- TX队列数:根据负载均衡需求配置
- 描述符数量:根据流量特征调整(默认1024)
2. 硬件卸载
- 启用校验和卸载:减少CPU开销
- 启用RSS:提高多线程性能
- 按需启用TSO/LRO:根据应用场景选择
3. 性能调优
- NUMA亲和性:队列绑定到正确的NUMA节点
- 线程绑定:合理分配队列到线程
- 缓冲区大小:根据MTU配置合适的buffer大小
4. 故障排除
- 查看统计:使用
show hardware-interfaces查看设备统计 - 检查队列:使用
show dpdk buffer检查缓冲区使用 - 监控错误:关注错误计数器
26.7 文档结构回顾
第一部分:作用和意义(第1章)
- DPDK插件的定位和作用
第二部分:整体架构(第2-3章)
- 文件组织、核心数据结构
第三部分:初始化和管理(第4-7章)
- 模块初始化、设备管理、驱动管理、缓冲区管理
第四部分:数据包接收(第8-14章)
- dpdk-input节点、接收处理、硬件卸载、多段包、流表、LRO、下一跳选择
第五部分:数据包发送(第15-17章)
- dpdk-output节点、发送处理、TX硬件卸载
第六部分:队列和优化(第18、21-23章)
- 发送队列管理、线程管理、性能优化、错误处理
第七部分:高级功能(第19-20、24-25章)
- 流表管理、统计计数、CLI/API、Cryptodev
26.8 关键技术要点总结
| 技术点 | 核心机制 | 性能影响 |
|---|---|---|
| 零拷贝 | mbuf与buffer共享内存 | ⭐⭐⭐⭐⭐ 极高 |
| 批量处理 | 批量接收/发送 | ⭐⭐⭐⭐⭐ 极高 |
| 硬件卸载 | 校验和、TSO/LRO等 | ⭐⭐⭐⭐ 很高 |
| 多队列 | 多RX/TX队列并行 | ⭐⭐⭐⭐ 很高 |
| 每线程数据 | 避免数据竞争 | ⭐⭐⭐ 高 |
| 预取优化 | 提前加载数据 | ⭐⭐⭐ 高 |
| 缓存对齐 | 避免false sharing | ⭐⭐ 中等 |
26.9 总结
DPDK插件是VPP高性能数据包转发的核心基础设施,通过以下方式实现高性能:
- 硬件加速:充分利用网卡硬件卸载能力
- 零拷贝:最小化数据拷贝开销
- 批量处理:提高处理效率
- 多队列多线程:充分利用多核CPU
核心价值:
- 高性能:支撑VPP的高吞吐量转发能力
- 灵活性:支持多种硬件卸载和配置选项
- 可扩展性:支持多设备、多队列、多线程
适用场景:
- 高性能网络转发
- 大流量处理
- 低延迟应用
- 需要硬件加速的场景
文档说明:
本文档基于VPP源码分析,将DPDK插件的Input和Output功能统一讲解,避免割裂。章节结构按照:作用和意义 → 整体架构 → 分散讲解 → 总结的模式组织。所有技术细节均来自VPP源码,确保准确性和实用性。