news 2026/4/4 17:29:21

VPP中的DPDK插件源码详解第四篇:DPDK插件高级功能以及控制接口总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VPP中的DPDK插件源码详解第四篇:DPDK插件高级功能以及控制接口总结

目录

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

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

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

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

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

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

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

  • 第4章:模块初始化

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

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

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

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

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

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

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

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

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

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

    • 12.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_fnsrc/plugins/dpdk/device/flow.c:729

创建流程

  1. 分配流表条目:从xd->flow_entries池中分配一个dpdk_flow_entry_t结构
  2. 处理Mark动作:如果需要MARK/REDIRECT/BUFFER_ADVANCE动作,分配flow_lookup_entry
  3. 规则转换:调用dpdk_flow_add将VPP流规则转换为DPDK流规则
  4. 安装规则:调用DPDK的rte_flow_validaterte_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_itemrte_flow_action),支持多种流类型(IP4、IP6、VXLAN、GTP等),然后调用rte_flow_create创建DPDK流规则。


19.2 流表的删除(DEL_FLOW)

删除流程

  1. 查找流表条目:通过private_data获取流表条目索引
  2. 销毁DPDK规则:调用rte_flow_destroy销毁DPDK流规则
  3. 清理Lookup Entry:如果有mark,将lookup entry标记为无效并加入待回收列表
  4. 释放流表条目:将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_tsrc/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_tsrc/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 本章总结

关键要点

  1. 规则转换dpdk_flow_add将VPP流规则转换为DPDK流规则
  2. 延迟回收:删除流表时,lookup entry延迟回收,避免in-flight数据包访问已释放内存
  3. 动态启用:首次创建流规则时,动态启用设备的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_counterssrc/plugins/dpdk/device/dpdk_priv.h:100

更新流程

  1. 保存上次统计:将当前统计值保存到last_stats
  2. 获取最新统计:调用rte_eth_stats_get获取DPDK最新统计
  3. 计算增量:计算统计增量(当前值 - 上次值)
  4. 更新VPP计数器:调用vlib_increment_simple_counter更新VPP接口计数器
  5. 更新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_initsrc/plugins/dpdk/device/init.c:232

  • 调用rte_eth_xstats_get_names获取XSTATS名称列表
  • 为每个XSTATS创建统计条目和符号链接
  • 符号链接路径:/interfaces/<interface_name>/<xstat_name>

更新dpdk_get_xstatssrc/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 本章总结

关键要点

  1. 增量更新:只更新统计增量,减少计算开销
  2. 定期轮询:默认10秒轮询一次,可通过配置修改
  3. 扩展统计:支持设备特定的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);

主要用途

  1. 临时mbuf数组ptd->mbufs[]- 用于批量接收/发送时的mbuf指针数组
  2. 临时buffer数组ptd->buffers[]- 用于批量处理时的buffer索引数组
  3. 临时标志数组ptd->flags[]- 用于存储硬件卸载标志
  4. 临时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 本章总结

关键要点

  1. 每线程数据:每个线程有独立的数据结构,避免线程间竞争
  2. RX队列绑定:支持指定线程或任意线程处理
  3. TX队列共享:支持共享队列,多线程可共享同一个发送队列(见第18章)

相关源码文件

  • src/plugins/dpdk/device/node.c:358- 每线程数据获取
  • src/plugins/dpdk/device/init.c:550- 接收队列线程绑定

第22章:性能优化

本章概述

第22章讲解DPDK插件中的关键性能优化技术。这些优化技术在前面的章节中已经使用,本章做系统性的总结和说明。


22.1 批量处理优化(Burst Processing)

原理:一次处理多个数据包,减少函数调用开销和分支预测失败。

应用场景

  1. 批量接收rte_eth_rx_burst一次接收多个数据包(见第9章)
  2. 批量发送rte_eth_tx_burst一次发送多个数据包(见第16章)
  3. 批量处理:在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缓存,隐藏内存访问延迟。

应用场景

  1. 数据预取:在处理当前数据包时,预取下一个数据包的数据
  2. 指令预取:预取后续指令,减少指令缓存未命中

关键代码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核心访问同一缓存行的不同部分)。

应用场景

  1. 队列结构dpdk_tx_queue_tdpdk_rx_queue_t使用缓存行对齐
  2. 每线程数据:每线程数据结构使用缓存行对齐

关键代码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_TRUEPREDICT_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 本章总结

关键优化技术

  1. 批量处理:减少函数调用开销
  2. 预取优化:隐藏内存访问延迟
  3. 缓存行对齐:避免false sharing
  4. 分支预测:优化热点路径
  5. 零拷贝:避免数据拷贝开销

相关源码文件

  • 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章):

  1. 检查发送结果rte_eth_tx_burst返回实际发送数量
  2. 更新错误统计:未发送的数据包计入TX_ERROR计数器
  3. 释放mbuf:释放未发送的mbuf,避免资源泄漏
  4. 错误计数:记录节点级别的错误计数

关键代码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 本章总结

关键要点

  1. 错误统计:所有错误都会更新到VPP统计系统
  2. 资源释放:错误时正确释放资源,避免资源泄漏
  3. 自动重试:发送失败时自动重试,提高可靠性
  4. 状态监控:定期监控设备状态,及时发现错误

相关源码文件

  • 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):

输出内容包括:

  1. 基本信息

    • 设备类型
    • 链路状态(carrier up/down)
    • 设备标志
  2. 队列配置

    • RX队列数量和描述符数量
    • TX队列数量和描述符数量
    • 队列描述符的限制(最小值、最大值、对齐值)
  3. 硬件信息

    • PCI设备信息(vendor ID、device ID、地址等)
    • NUMA节点
    • 交换机信息(如果适用)
  4. 功能特性

    • 混杂模式状态
    • VLAN卸载配置
    • RX卸载能力(可用和激活的)
    • TX卸载能力(可用和激活的)
    • RSS配置(可用和激活的)
    • Burst模式信息
  5. 统计信息

    • 基础统计(接收/发送数据包和字节数、错误计数等)
    • 扩展统计(XSTATS,如果启用verbose模式)
  6. 错误信息

    • 设备配置错误(如果有)

示例输出(部分):

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函数会:

  1. 更新统计信息(dpdk_update_counters
  2. 更新链路状态(dpdk_update_link_state
  3. 获取DPDK设备信息(rte_eth_dev_info_get
  4. 格式化输出所有相关信息

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 0

24.5 API接口

概述:DPDK插件本身没有提供专用的API接口,而是通过VPP的通用接口API来操作DPDK设备。DPDK设备信息通过format_dpdk_device函数在show hardware-interfaces的响应中返回。

相关API

  1. sw_interface_dumpsrc/vnet/interface.api):

    • 用于获取软件接口信息
    • 响应中包含硬件接口信息,对于DPDK设备会包含DPDK特定的信息
  2. 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设备完整信息

关键设计要点

  1. CLI为主:DPDK插件主要通过CLI命令进行管理和监控
  2. 格式化函数:使用格式化函数统一输出格式
  3. 通用API:通过VPP通用接口API管理,无专用API
  4. 实时更新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?——用"快递加密中心"来理解

通俗理解

想象一下"快递加密中心"的工作场景:

  1. 传统方式(软件加密)

    • 快递员(CPU)自己用"密码本"(软件算法)给包裹加密
    • 速度慢,占用CPU资源,影响其他工作
  2. 硬件加速方式(Cryptodev)

    • 快递员把包裹交给"专业加密机器"(硬件加密设备)
    • 加密机器专门负责加密,速度快,不占用CPU
    • 快递员可以做其他事情,提高整体效率

专业定义

**Cryptodev(Crypto Device)**是DPDK提供的加密设备抽象框架,它:

  • 抽象硬件加密设备:将不同厂商的硬件加密设备(如Intel QAT、软件加密库等)统一抽象
  • 提供统一接口:为VPP提供统一的加密操作接口
  • 加速加密操作:利用硬件加速,大幅提升加密/解密性能

为什么需要Cryptodev?

  1. 性能需求:软件加密慢,硬件加密快(可能快10-100倍)
  2. CPU资源释放:让CPU专注于数据包转发,加密交给硬件
  3. 统一接口:不同硬件设备使用统一的接口,便于管理

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;

会话创建流程

  1. 准备加密参数(Xform)

    // 准备AEAD加密参数prepare_aead_xform(xforms_enc,CRYPTODEV_OP_TYPE_ENCRYPT,key,aad_len);
  2. 创建DPDK会话

    sessions[CRYPTODEV_OP_TYPE_ENCRYPT]=rte_cryptodev_sym_session_create(dev_id,xforms_enc,sess_pool);
  3. 保存会话:会话保存在会话池(session pool)中,可以重复使用


25.2.3 加密操作(Crypto Operation)

通俗理解

“加密操作"就是"一次具体的加密任务”:

  • 包含信息
    • 要加密的数据
    • 使用的会话(包含算法和密钥)
    • 操作类型(加密或解密)

操作类型

  1. AEAD(Authenticated Encryption with Associated Data)

    • 同时进行加密和认证
    • 例如:AES-GCM、ChaCha20-Poly1305
    • 就像"加密+验证签名"一次性完成
  2. 链接算法(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的初始化过程

通俗理解

就像"快递加密中心"的"开业准备":

  1. 检查有哪些加密机器(设备探测)
  2. 给每台机器配置工作窗口(队列对设置)
  3. 准备加密模板库(会话池创建)
  4. 分配工作窗口给快递员(线程绑定)

初始化流程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 加密操作的数据路径

通俗理解

就像"快递加密中心"的"工作流程":

  1. 接收任务(Enqueue):快递员把需要加密的包裹放入"工作窗口"
  2. 机器处理(硬件加密):加密机器自动处理
  3. 取回结果(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;}

通俗理解

  1. 准备包裹

    • 获取"加密任务单"(crypto op)
    • 填写"加密模板编号"(session)
    • 填写"要加密的数据"(source data)
    • 填写"加密参数"(IV、AAD等)
  2. 提交任务

    • 把任务单放入"工作窗口"(队列)
    • 加密机器会自动处理

关键点

  • 批量提交:一次提交多个任务,提高效率
  • 零拷贝:通常源数据和目标数据是同一个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;}

通俗理解

  1. 检查完成窗口

    • 从"完成窗口"批量取回处理完的任务(最多64个)
  2. 检查结果

    • 如果成功:标记为成功,继续后续处理
    • 如果失败:标记为失败,可能需要重试或丢弃
  3. 回收资源

    • 释放"任务单",可以重复使用

关键点

  • 批量处理:一次处理多个结果,提高效率
  • 状态检查:每个操作都有状态,可以判断成功或失败

25.5 缓存队列(Cache Queue)机制

通俗理解

就像"快递加密中心"的"中转站":

  • 为什么需要缓存队列
    • 加密设备可能很忙,不能立即处理
    • 先把任务放在"中转站",等有空时再批量提交

缓存队列结构(简化):

typedefstruct{u16 head;// 队头(写入位置)u16 tail;// 队尾(读取位置)cryptodev_cache_ring_elt_tframes[CRYPTODEV_CACHE_QUEUE_SIZE];// 队列大小:256}cryptodev_cache_ring_t;

工作流程

  1. 接收任务

    • 加密任务先放入缓存队列(如果设备忙)
  2. 批量提交

    • 当缓存队列积累了一定数量的任务,批量提交到加密设备
  3. 处理结果

    • 从设备取回结果,继续后续处理

好处

  • 减少系统调用:批量提交减少与加密设备的交互次数
  • 提高吞吐量:设备可以更高效地处理批量任务
  • 平滑流量:缓存可以平滑突发流量

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;}

通俗理解

  1. 注册服务

    • 告诉VPP:“我是加密服务提供商,名字叫dpdk_cryptodev”
    • 设置优先级:100(数字越大,优先级越高)
  2. 提供服务

    • 提供密钥管理服务(key_handler)
    • 提供会话管理服务(sess_handler)
    • 提供加密操作服务(enqueue/dequeue handlers)
  3. 自动选择

    • 当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发送的包裹"都需要加密

工作流程

  1. 数据包进入IPSec处理节点
  2. 需要加密,调用VPP加密框架
  3. 加密框架选择Cryptodev引擎
  4. Cryptodev使用硬件加速加密
  5. 加密完成,继续转发
场景2:TLS/SSL加速

通俗理解

  • HTTPS流量需要TLS加密,使用Cryptodev加速
  • 就像"网购的包裹"需要加密保护

25.10 本章总结

核心概念总结

概念通俗理解作用
Cryptodev设备加密机器提供硬件加密能力
加密会话加密模板预先配置加密参数
加密操作加密任务一次具体的加密请求
队列对工作窗口提交任务和取回结果
缓存队列中转站批量提交,提高效率

关键流程

  1. 初始化:探测设备 → 配置队列 → 创建会话池 → 绑定线程
  2. 加密流程:准备操作 → 入队提交 → 硬件处理 → 出队获取结果
  3. 性能优化:批量处理 + 零拷贝 + 会话复用

性能优势

  • 速度:硬件加密比软件加密快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_mbufvlib_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高性能数据包转发的核心基础设施,通过以下方式实现高性能:

  1. 硬件加速:充分利用网卡硬件卸载能力
  2. 零拷贝:最小化数据拷贝开销
  3. 批量处理:提高处理效率
  4. 多队列多线程:充分利用多核CPU

核心价值

  • 高性能:支撑VPP的高吞吐量转发能力
  • 灵活性:支持多种硬件卸载和配置选项
  • 可扩展性:支持多设备、多队列、多线程

适用场景

  • 高性能网络转发
  • 大流量处理
  • 低延迟应用
  • 需要硬件加速的场景

文档说明
本文档基于VPP源码分析,将DPDK插件的Input和Output功能统一讲解,避免割裂。章节结构按照:作用和意义 → 整体架构 → 分散讲解 → 总结的模式组织。所有技术细节均来自VPP源码,确保准确性和实用性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/23 17:34:12

nodeppt演讲者模式深度解析:打造专业级演讲体验

nodeppt演讲者模式深度解析&#xff1a;打造专业级演讲体验 【免费下载链接】nodeppt This is probably the best web presentation tool so far! 项目地址: https://gitcode.com/gh_mirrors/no/nodeppt 还在为演讲时手忙脚乱而烦恼吗&#xff1f;nodeppt的演讲者模式正…

作者头像 李华
网站建设 2026/3/17 0:31:43

终极指南:YouTube Music桌面版如何打造专属音乐空间

终极指南&#xff1a;YouTube Music桌面版如何打造专属音乐空间 【免费下载链接】ytmdesktop A Desktop App for YouTube Music 项目地址: https://gitcode.com/gh_mirrors/yt/ytmdesktop YouTube Music桌面版是一款功能强大的开源音乐播放器&#xff0c;为用户提供超越…

作者头像 李华
网站建设 2026/3/23 6:34:24

Wan2.2-T2V-A14B实现高质量运动过渡的算法机制揭秘

Wan2.2-T2V-A14B 实现高质量运动过渡的算法机制揭秘在短视频日均播放量突破百亿的时代&#xff0c;内容创作者早已不满足于“能出画面”——大家真正想要的是一段会呼吸的视频&#xff1a;人物动作自然流畅、场景转换丝滑无痕、风吹发梢都带着情绪。&#x1f3af; 可现实呢&…

作者头像 李华
网站建设 2026/3/14 6:14:16

VSCode连接量子设备全攻略(从零到专家级配置方案)

第一章&#xff1a;VSCode 的量子硬件连接配置 在现代量子计算开发中&#xff0c;Visual Studio Code&#xff08;VSCode&#xff09;已成为主流集成开发环境之一。通过扩展插件与底层API的结合&#xff0c;开发者可直接在VSCode中编写量子电路并连接真实量子硬件进行执行。 安…

作者头像 李华
网站建设 2026/3/31 15:40:13

VSCode遇上量子模拟:3个关键功能让你的编码速度翻倍

第一章&#xff1a;量子模拟器的 VSCode 扩展开发在现代量子计算研究中&#xff0c;开发者需要高效的工具链来编写、调试和模拟量子算法。Visual Studio Code 作为主流代码编辑器&#xff0c;其强大的扩展生态为集成量子模拟功能提供了理想平台。通过开发专用的 VSCode 扩展&am…

作者头像 李华
网站建设 2026/4/1 3:21:56

【加密PDF文档解析终极方案】:Dify平台深度集成技巧与实战秘籍

第一章&#xff1a;加密PDF文档解析的核心挑战在处理现代电子文档时&#xff0c;加密PDF文件的解析成为许多自动化系统与数据提取流程中的关键瓶颈。由于PDF格式本身支持多种加密机制&#xff08;如基于密码的40位或128位RC4加密、AES-256加密以及公钥加密&#xff09;&#xf…

作者头像 李华