简介:一份面向互联网与网络数据面开发者的应用指南,围绕 DPDK 自带 testpmd 示例程序展开;testpmd 既能用于包转发模式下的性能测试,也能访问 Flow Director 等 NIC 硬件特性,同时也是基于 DPDK SDK 构建更完整应用时的参考样板。压缩包内为单份 PDF 文档,大小仅 137KB,已有 599 人学习。文档先从编译流程讲起,说明如何用 make、gcc 等工具并配合 -m64 等选项指定目标平台与架构;随后介绍 EAL 与 testpmd 的命令行选项,帮助读者指定 NIC 接口、包转发模式和 Flow Director 等运行参数,并理解 DPDK 在数据包处理中的基本机制。运行时函数部分按功能模块梳理了帮助、控制、显示、配置、端口、链路绑定、寄存器与过滤器等常用操作,基本覆盖日常性能测试、NIC 特性验证与调试场景;内容预览还显示,文档包含 Introduction、Overview、Compiling、Running、Runtime Functions 等章节,结构清晰,便于按需查阅。对希望掌握 DPDK 包转发流程、NIC 配置方法并快速上手 testpmd 的读者来说,这份指南提供了从编译到运行的完整路径,能帮助理解 EAL、Packet Framework 与 NIC Poll Mode Driver 的协作方式,适合入门与进阶参考。
1. DPDK Testpmd 应用:先弄清它在真实开发里解决什么问题
在 DPDK 的中文技术社区里,Testpmd 可能是命令行被复制最多、解释最少的一个应用。捧着《DPDK Testpmd 应用》这类 PDF 从头翻到尾,你会发现它更像一本参数字典:一大堆命令行选项、转发模式、端口操作命令,却没有告诉你第一步该敲哪条命令。它的真实角色其实是网卡驱动和转发路径的“试金石”:验证 PMD 驱动能不能稳定收包,压一压单核能跑多少 Mpps,看看多队列 RSS 是否把流量均匀分发。适合做 DPDK 驱动开发、NFV 转发面、OVS-DPDK 调优的人,也适合刚把环境编译好、想确认硬件没有白买的初学者。接下来这篇实战笔记会从跑通最小命令一路讲到性能压测和踩坑,把 Testpmd 最有用的那部分落到键盘上。
2. 用最小命令跑通 Testpmd:先让它把包转起来
2.1 一条能转发报文的启动命令
第一次接触 Testpmd 的人,最好先忘掉“背参数”这件事,直接把下面这条命令粘到终端里。前提是你已经把网卡用dpdk-devbind.py绑到vfio-pci或igb_uio驱动,并且分配了足够的大页内存。
dpdk-testpmd -l 0-1 --huge-dir /dev/hugepages -n 2 \ -a 0000:01:00.0 \ -- -i --portmask=0x1 --rxd=1024 --txd=1024 --burst=64这条命令的意图是:用 CPU 0 和 CPU 1 两个逻辑核跑 Testpmd,从/dev/hugepages目录取大页内存,假定主板有 2 个内存通道,并只操作 PCI 地址为0000:01:00.0的那块网卡。--后面是 Testpmd 自己的参数,-i表示进入交互模式,这样你在启动后不会直接转发,而是先停在控制台前,输入start才真正开始收发包。
参数这里拆开说一下。-l 0-1建议你选两个不超频、不被打扰的核,必要时用-l指定核而不是让 DPDK 自己乱挑。-n 2是内存通道数,一般可以从 BIOS 或dmidecode查,填错了顶多性能差点,不会报错。--rxd和--txd是收发描述符环的大小,默认很多时候只有 256,对 10G 网卡来说容易丢包,设成 1024 或 2048 更稳。--burst=64是每次从网卡取包的最大数量,这个值决定了收包引擎分批次处理的粒度,64 是一个对延迟和吞吐都相对平衡的默认值。
2.2 端口、核和内存池的选型逻辑
Testpmd 启动时会创建若干个内存池,默认情况下一个收一个发。内存池的 block size 来自启动参数--mbuf-size,默认是 2048 字节,刚好覆盖一个标准 MTU 报文加头部和分片需要的空间。如果你们的报文带 VXLAN 或者更大的 metadata,建议改大,否则收包阶段就会因为 mbuf 不够放而丢包。
关于核的用法,-l指定的是 DPDK EAL 可以使用的逻辑核,不是让你一个核给收、一个核给发。Testpmd 的线程模型是“一个转发核同时跑收和发”,所以你通常会看到--nb-cores这个参数,而不是让用户手动把一对端口塞给某个线程。最常见做法是:
dpdk-testpmd -l 0-15 --huge-dir /dev/hugepages -n 4 \ -a 0000:01:00.0 -a 0000:01:00.1 \ -- -i --nb-cores=8 --rxq=8 --txq=8--nb-cores=8决定 Testpmd 启用多少个转发线程,每个线程挂在不同的核上。--rxq和--txq是端口队列数,队列数和转发线程数不强制一致,由 Testpmd 自动分配。这里我们分配 8 个线程、8 个队列,如果 RSS 配置正确,每个核各占一个队列,不会出现多个核抢同一个队列的锁竞争。
内存池数量也值得注意。Testpmd 内部经常出现allocation of mbuf pool失败的报错,原因往往是--mbuf-size改得太大,导致 sum of all requests 超过大页内存。我一般会保持默认--mbuf-cache=32,只在报文变大时调整--mbuf-size,而不是去动 cache。cache 太小会让每个核频繁回写全局 list,影响吞吐,太大又会多占内存。
2.3 为什么默认是 io forwarding,mac forwarding 又差在哪
默认转发模式是iofwd,它只做一件事:从某端口收,原封不动从另一个端口或同一个端口发出去,不改 MAC、不改 IP。这种模式非常适合测收发包引擎纯净吞吐。另一常见模式是macfwd,它会改写目的 MAC 再转发,模拟真实二层交换机的转发路径。两者在 Testpmd 命令行里几乎没有性能差,但macfwd要对每个报文做一次头部修改,开销略高一点点,做性能对标时必须写清楚用的是哪个模式。
如果你想模拟跨网段或者三层转发,还可以用flow模板,但生产环境里大家一般不会让 Testpmd 做复杂流表匹配测试,那些都交给硬件卸载或更高层的转发应用。Testpmd 的价值在于把底层报文的走向看得清清楚楚:
set fwd mac start show port stats all上面这组命令把转发模式切成 mac 转发并启动,接着打印所有端口的收发统计。统计里有rx-packets、tx-packets、rx-dropped和tx-dropped。测试互连时对端如果没收到包,先看rx-packets是在涨还是停在 0,这是最简单的一层定位。注意,set fwd mac只改转发引擎类型,不需要重启端口。
2.4 打通环回的三种做法
支持 loopback 的网卡,可以加--port-topology=loopback让单个端口自发自收,适合没有双端口设备的调试场景。命令如下:
dpdk-testpmd -l 0-1 -a 0000:01:00.0 \ -- -i --port-topology=loopback --forward-mode=io链路如果没起来,先执行set link up port 0,再看show port info 0里的link status。另一种场景是端口不支持硬件 loopback,那就必须用一根双口网卡把两个端口用光纤或网线环起来,--port-topology=chained对这种情况尤其好用,它允许一个转发核连续处理多个端口。区别在于 chained 拓扑下每个核依次服务所有端口,而 paired 是固定成对转发,吞吐性能差异在高端多口网卡上能看出来。
3. 用 Testpmd 做性能压测:吞吐、时延与丢包的正确读法
3.1 吞吐测试的典型步骤:外部打流加 iofwd
Testpmd 本身不是一个完整流量发生器,所以测吞吐最可靠的做法是:被测设备装 Testpmd,测试仪从外部向端口打流,Testpmd 转发,测试仪读回数据。这样得到的是整条路径有没有丢包。启动参数建议按前面推荐来:--rxd=2048 --txd=2048 --burst=64 --nb-cores=2 --rxq=2 --txq=2。
dpdk-testpmd -l 4-5 --huge-dir /dev/hugepages -n 2 \ -a 0000:af:00.0 -a 0000:af:00.1 \ -- -i --rxd=2048 --txd=2048 --burst=64 \ --rxq=2 --txq=2 --forward-mode=io进入交互界面后,按照测试计划先把端口状态拉起来,再启动转发:
port stop all set link up port 0 set link up port 1 start show port stats all外部打流的速率建议从线速的 50% 开始,逐步加到一个包长下刚好不丢的速率,再用 Testpmd 的set burst 128之类调整 batch 大小,看是否影响转发吞吐。官方文档里经常出现 Mpps 这个单位,我更习惯直接看 Testpmd 输出的rx-packets与tx-packets,两者之差就是被转丢的包数。丢包率不能拿测试仪一个端口的发包数去对,最好以 Testpmd 收到的包数为基准,这样能把入口丢包也算进去。
3.2 txonly 和 rxonly:用一块网卡也能做单方向压力
没有测试仪时,可以用 Testpmd 的txonly模式生成高速无意义 UDP 包,直接把数据从端口发出去。配合对端用rxonly或者另一台机器的 Testpmd 收包,就能测出单纯的发送能力。用法很简单:
set fwd txonly set txpkts 64 startset txpkts 64把生成的报文大小固定成 64 字节,这是以太网最小帧长,能把转发资源推到极限。测试完成后stop,再用show port stats all看tx-packets。注意txonly模式下rx-packets通常仍是 0,它不会处理入向流量,所以不要拿 rx 统计去判断发送吞吐。
反过来rxonly只负责从网卡收包并直接丢进 mbuf 池,不转发。它可以和外部打流设备配合,测出网卡驱动在纯接收模式下能收到多少包,同时不会因为转发环上的 CPU 瓶颈掩盖接收瓶颈。两次测试一个收一个发,也能定位性能瓶颈是出在收方向还是发方向。
3.3 多队列和 RSS:压测配置要和生产一致
在正式性能验证里,只开单队列跑 Testpmd 没有参考意义,因为生产环境的 OVS-DPDK 或 VPP 都是多队列加 RSS。Testpmd 支持运行时配置 RSS,我一般会这么操作:
port config rss all ipv4 show port rss all set fwd io start用port config rss all ipv4对所有端口开启 IPv4 哈希。测试前务必用show port rss all确认哈希类型真的写进去了,否则流量可能因为哈希函数默认只看外层 MAC 而全部押到一个队列。很多人压测时遇到“流量全部在核 0 上空转,其他核看着”,十有八九是 RSS 没开,或者哈希字段选错了。
观察各队列计数可以用show port xstats all,里面能看到rx_q0_packets、rx_q1_packets这样的登记项,确认是否均匀。不均匀的原因很多:测试流的五元组不够随机、哈希种子偏了、多队列下队列数量不是 2 的幂导致取模不均匀。这些都是踩过坑之后才会下意识去检查的项。
3.4 结果记录:参数要完整,结论才有可比性
Testpmd 的压测结果很难复现,最常翻车的原因是记录参数太随意。一个可交付报告里至少要包含:网卡型号和固件版本、驱动名称和 DPDK 版本、绑定模式(vfio-pci/igb_uio)、大页配置、CPU 型号和核绑定、内存通道数、端口数和拓扑、rxd/txd、burst、队列数、转发模式、报文长度、打流速率、持续时间、观察到的丢包率。用表格式记录会直观很多:
| 项目 | 测试配置 |
|---|---|
| DPDK 版本 | 22.11 |
| 网卡 | Intel E810 / Mellanox ConnectX-6 |
| 驱动绑定 | vfio-pci |
| 核 / 队列 | 8 核 / 8 队列 |
| rxq / txq 描述符 | 2048 / 2048 |
| 转发模式 | iofwd |
| 报文长度 | 64B / 1500B |
| 吞吐 | 14.1 Mpps(64B) |
原始日志要保留,不只看截图。Testpmd 退出时通常会在 stdout 打印一段简短的统计,用重定向存成文件,后续才能定位是哪个参数变了导致性能下降。
4. Testpmd 控制台交互:跑测试时这些命令能省一半时间
4.1 最有价值的 show 命令
Testpmd 的控制台比很多人想象的强得多。你不需要每改一个参数就重启进程,大量动作都在运行中完成。先记住这组show:
show port info all show port stats all show port xstats all show port rss allshow port info all看硬件状态和链路;show port stats all看累计收发和丢包;show port xstats all看每个队列单独的计数,是排查 RSS 是否均匀的关键;show port rss all看哈希配置。如果你在跑长时间稳定性测试,想确认进程没有卡死,连续执行两次show port stats all,计数在增长就说明数据路径还活着。
4.2 运行中调参的命令
转发过程中可以调整的参数分成三类:端口状态、转发引擎参数、队列配置。端口状态用port stop all/port start all控制,链路状态用set link up/down port N。转发引擎参数像set fwd切模式,set burst N调 batch,set txpkts改发包长度。队列配置比较麻烦,Testpmd 不允许运行中直接改已经启用的 rxq/txq 数量,常见做法是先停掉端口,再改队列数,最后重新启动:
port stop all port config rxq all 8 port config txq all 8 port config rss all ipv4 port start all start这段操作的意义是验证多队列切换后 RSS 仍然生效。注意port config rxq之后队列数变了,但转发核数量没有变,Testpmd 会自动重新分配队列到核,最好用show cfg看一眼分配结果,避免某个核背了多条队列而另一个核闲着。
4.3 用-f文件加大外层脚本组合成批处理
如果测试经常重复,手工敲命令就很浪费。Testpmd 支持用-f参数接一个命令文件,文件里每一行就是一条交互命令,启动后会顺序执行。先写一个只做配置和启动的文件tp_start.txt:
port stop all set fwd io set burst 64 port config rss all ipv4 port start all start再用 FIFO 配合 bash 控制时序:
mkfifo /tmp/tp_cmd dpdk-testpmd -l 0-1 --huge-dir /dev/hugepages -n 2 \ -a 0000:01:00.0 -- -i -f tp_start.txt < /tmp/tp_cmd & TP_PID=$! exec 3>/tmp/tp_cmd sleep 10 echo "show port stats all" >&3 echo "stop" >&3 echo "quit" >&3 wait $TP_PID-f负责把配置和启动动作固化,sleep 10控制实际转发时间,后续命令通过 FIFO 发给 Testpmd 的 stdin。这种方式比“人盯着终端回车”可靠,尤其适合放回 CI 里做冒烟测试。注意 FIFO 需要在写之前先打开读端,否则进程会卡在 open 上,这就是很多脚本“看起来没反应”的原因。
5. Testpmd 实战避坑:5 个反复出现的疑难问题
5.1 启动报 EAL 内存分配失败
- 现象:不管怎么调
--mbuf-size都出现EAL: No memory available或者hugepages not enough,进程直接退出。 - 原因:大页内存根本没准备好。多数情况下是
/dev/hugepages没有挂载,或者vm.nr_hugepages设置得比网卡需要的还少。 - 解决:先看
/proc/meminfo里的HugePages_Free,如果是 0,用sysctl vm.nr_hugepages=1024并挂载 hugetlbfs。再启动 Testpmd,指定--huge-dir /dev/hugepages。如果还不行,检查双路台机上每个 socket 是否都分配了大页,DPDK 默认优先在本 socket 用页,需要--socket-mem显式指定两个节点的内存大小。
5.2 绑定 vfio-pci 时提示操作不允许
- 现象:用
dpdk-devbind.py绑定网卡到vfio-pci时出现cannot open /dev/vfio/vfio或者operation not permitted。 - 原因:用户空间进程没有访问 vfio 设备的权限,或者是 IOMMU 被 BIOS 或内核参数关掉了。
- 解决:确认内核已经加载
vfio_pci模块,启动参数里有iommu=pt。把当前用户加入vfio组,或者临时用chmod 666 /dev/vfio/vfio验证权限问题。绑定完后再用dpdk-devbind.py -s看驱动列,确认已经从ixgbe/i40e换成vfio-pci,再启动 Testpmd。
5.3 Testpmd 收到了包,转发出去却是 0
- 现象:
show port stats all里rx-packets一直在涨,tx-packets停在 0,或者两个口数据对调。 - 原因:用的端口对和实际接线不一致,或者
forward-mode=io时端口编号让 Testpmd 认为两个端口不成对,常见于多端口网卡和 NUMA 节点不匹配。 - 解决:用
show port info all确认链路状态,再对照物理网口的 PCIe 接口编号,把命令里的--portmask改成实际要用的前端口。转发前建议先用set fwd mac,让 Testpmd 改写目的 MAC,这样对端如果也在同一个广播域里,能看到报文到底是从哪张卡出来的,方便手工定位。
5.4 多队列下流量全挤到一个核
- 现象:
show port xstats all里rx_q0_packets是大头,其他队列几乎为零,转发吞吐一直上不去。 - 原因:RSS 没有打开,或者哈希字段没有覆盖报文的五元组。还有一种可能是启动时没有设置
--rxq=8,Testpmd 默认只有 1 个队列,RSS 再开也只有 1 个队列可用。 - 解决:先看
show port rss all,执行port config rss all ipv4并确认哈希字段有值。再把队列数量改成核数:port stop all、port config rxq all 8、port start all。如果还是不平均,检查打流工具是不是只用了同一组源目 IP,五元组不随机,哈希当然均匀不了。
5.5 交互终端被刷屏,命令输不进去
- 现象:Testpmd 运行中每隔一秒打印一堆计数器,你在控制台敲的命令被刷得几乎看不见。
- 原因:启动参数里带了
--stats-period且设得很小,或者 Testpmd 内部启用了set verbose 2之类的调试输出。 - 解决:不想看刷屏就去掉
--stats-period,或者临时用set verbose 0关掉详细打印。需要周期统计时,把--stats-period设成 60 秒以上,并把日志重定向到文件,交互只保留一个干净的控制台。还有一点:如果用-f脚本,脚本里也别写太频繁的show port stats all,否则一样会有刷屏效果。
6. 把 Testpmd 用得更顺手:脚本化、结果校验和一劳永逸的日志习惯
做网卡驱动或转发面开发,最怕的就是换了一版代码,吞吐数字到底有没有变化说不清楚。我现在会把 Testpmd 装进一个带参数的封装脚本里,一次性跑多种报文长度,并把每次的原始输出留下来。
#!/bin/bash for pkt in 64 256 1024 1500; do log="result_${pkt}.log" cat > tp_cfg.txt <<EOF set fwd txonly set txpkts ${pkt} start EOF timeout 10 dpdk-testpmd -l 0-1 --huge-dir /dev/hugepages -n 2 \ -a 0000:01:00.0 -- -i -f tp_cfg.txt > "$log" 2>&1 grep -E "Tx-packets|Tx-pps|rx-packets" "$log" | tail -3 >> result_summary.txt donecat生成配置文件,timeout控制每个包长的运行时间,这样每次跑 10 秒后自动退出,统计输出全部落盘,不会因为 Ctrl+C 丢掉最后一段结果。脚本里写的txonly适合验证发送路径,如果要测完整转发,只需要把配置文件里的set fwd txonly换成set fwd io,再把打流器从外部送流量进来即可。
结果校验我习惯用“对账”方式。比如两个端口直接光纤环回,启动 Testpmd 后记录rx-packets和tx-packets,如果两边数字在一个合理的偏差范围内,说明驱动和队列配置没有吞包。真正的交叉验证是端到端:一个端口抓外部流量,另一个端口跑 Testpmd,在外部抓包工具里过滤源 MAC,看是否每条进来的数据都被原样发出。DPDK 进程绕过内核,所以 tcpdump 在 Testpmd 接管网卡上是抓不到包的,千万不要用在内核抓包的结果去推断 Testpmd 丢没丢。
日志习惯是我最想提醒你的一件事。每次跑 Testpmd 前,把命令行、DPDK 版本、网卡固件、大页配置先写进文件名或者日志第一行。不要只存一份结果截图,否则两周后对比数据时,你会连某个参数到底是谁改的都说不清。我吃过这个亏,现在所有测试机上都保留一份testpmd_env.sh,每次跑之前自动把环境信息拼进日志头。希望帮到你。
本文还有配套的精品资源,点击获取