1. 为什么还要在 CentOS 上折腾 DPDK 19.11先把话说在前头DPDK 的版本迭代很快现在新版本已经出到 20 以后很多了但 19.11 这个版本在不少存量项目里依然是主力。原因很实在——它是 LTS 版本很多网卡厂商的驱动适配、不少自研数据面程序的接口都是照着这个版本写的。你如果接手一个跑了几年的转发程序贸然升级到新版本API 变动、依赖库版本冲突能让你调上一整天。所以在 CentOS 上把 19.11 编译跑通是一项很实用的基本功。这篇内容面向的是谁一类是刚接触 DPDK、需要在服务器上把环境搭起来跑通第一个 helloworld 或者 l2fwd 的同学另一类是老手手头有台 CentOS 机器想快速把 19.11 的编译链路重新捋一遍省得每次翻旧笔记。我会把整个流程拆成“环境准备、依赖安装、编译配置、大页内存、绑定网卡、跑测试程序”这几块每一块都讲清楚为什么这么做以及我踩过的坑。需要提前说明的是DPDK 是数据平面开发套件它绕过内核协议栈直接操作网卡所以对硬件、内核版本、内存配置都有要求。你在虚拟机里跑和物理机上跑体验完全不一样。我下面会以物理机 CentOS 7 为主虚拟机的情况单独提。2. 环境准备与依赖梳理2.1 系统版本与内核的匹配关系CentOS 7 自带的内核是 3.10 系列这个内核跑 DPDK 19.11 是没问题的官方文档里明确支持。但要注意一点如果你用的是 CentOS 8 或者 Stream内核是 4.18 起步编译本身没问题但有些老网卡的 igb_uio 驱动在 4.18 上编译会报错需要打补丁或者换用 vfio-pci。我个人的建议是如果只是学习验证CentOS 7.6 到 7.9 是最省心的选择内核不用动直接编译就能过。先确认一下系统信息cat /etc/redhat-release uname -r输出应该是类似CentOS Linux release 7.9.2009 (Core)和3.10.0-1160.el7.x86_64。如果你的内核是 3.10.0-1127 之前的版本建议先yum update一下因为早期 3.10 内核对大页内存和 vfio 的支持有些小问题。提示不要用 CentOS 6glibc 版本太低DPDK 19.11 的 meson 构建系统跑不起来。CentOS 6 的 gcc 是 4.4连 C11 标准都不完整支持。2.2 依赖包一次性装齐DPDK 19.11 同时支持 make 和 meson 两套构建系统。make 是老路子meson 是官方推荐的新方式。我建议两套都装上依赖因为有些老项目还在用 make你保不齐哪天就要切回去。用 yum 装依赖一条命令搞定yum install -y gcc gcc-c make meson ninja-build numactl-devel \ kernel-devel-$(uname -r) kernel-headers-$(uname -r) \ libpcap-devel openssl-devel zlib-devel \ python3 python3-pip git wget这里解释几个关键依赖的作用。numactl-devel是给 NUMA 架构用的多路服务器上 DPDK 要绑定内存到特定 NUMA 节点没这个库编译会报错。kernel-devel和kernel-headers必须和当前运行的内核版本完全一致否则编译 igb_uio 内核模块时会提示找不到头文件。libpcap-devel是给 pcap 驱动用的如果你要用 DPDK 抓包对比这个必须有。装完之后验证一下 meson 和 ninja 的版本meson --version ninja --versionCentOS 7 自带的 meson 版本可能偏低如果低于 0.47用 pip3 升级pip3 install --upgrade meson ninja注意pip 升级后的 meson 可能装在/usr/local/bin下而系统里/usr/bin/meson还是老版本。用which meson确认一下路径必要时把/usr/local/bin放到 PATH 前面。2.3 关闭 SELinux 和防火墙的干扰这一步不是必须的但能省掉很多莫名其妙的权限问题。SELinux 会拦截 DPDK 对大页内存和网卡设备的访问表现就是程序跑起来报Permission denied但你用 root 又看不出来哪里不对。临时关闭setenforce 0永久关闭就改/etc/selinux/config把SELINUXenforcing改成SELINUXdisabled然后重启。防火墙对 DPDK 本身没影响因为 DPDK 不走内核网络栈但如果你要跑 testpmd 做回环测试防火墙规则可能会干扰你判断结果建议也一并关掉。3. 获取源码与编译配置3.1 下载 DPDK 19.11 源码官方源码可以从 dpdk.org 的下载页拿但国内访问有时候慢。我一般直接用 git 克隆指定分支git clone -b v19.11 https://github.com/DPDK/dpdk.git dpdk-19.11 cd dpdk-19.11如果你网络环境不方便用 git也可以下载 tar.xz 包解压。注意 19.11 后面还有 19.11.1 到 19.11.14 这些稳定版更新建议直接用最新的 19.11.14bug 修复更全。分支名就是v19.11.14。下载完之后看一眼目录结构关键目录有这几个lib是核心库drivers是网卡驱动app是示例程序config是编译配置文件usertools是辅助脚本。后面编译配置主要跟config目录打交道。3.2 用 meson 构建还是用 make 构建这是很多人纠结的第一个点。我直接给结论新项目用 meson老项目维护用 make。meson 构建速度快依赖管理清晰而且 19.11 之后官方主推 meson。make 的好处是配置直观改config/defconfig_x86_64-native-linuxapp-gcc就行很多老教程都是这么写的。先讲 meson 的路子。在源码根目录建一个构建目录mkdir build cd build meson .. -Dexamplesl2fwd,l3fwd -Dplatformnative-Dexamples指定要编译哪些示例程序不指定的话默认不编译示例。-Dplatformnative让编译器针对当前 CPU 做优化生成的二进制性能更好但换到别的 CPU 上可能跑不了。如果你要跨机器部署改成-Dplatformgeneric。配置完成后编译ninja编译过程大概五到十分钟取决于机器性能。编译完成后库文件在build/lib下示例程序在build/examples下。再讲 make 的路子作为对照。先设置环境变量export RTE_SDK$(pwd) export RTE_TARGETx86_64-native-linuxapp-gcc make config T$RTE_TARGET make -j$(nproc)make config会在build目录下生成配置文件。-j$(nproc)用满所有 CPU 核心并行编译能快不少。make 构建的产物在$RTE_SDK/$RTE_TARGET目录下。实操心得如果你编译时报numa.h: No such file or directory说明numactl-devel没装或者装错了架构。用rpm -qa | grep numactl确认一下。另一个常见错误是cannot find -lrte_eal这通常是 RTE_TARGET 设错了检查一下是不是写成了x86_64-native-linux-gcc少了app。3.3 编译选项的取舍有几个编译选项值得单独说。第一个是CONFIG_RTE_BUILD_SHARED_LIB默认是静态库。如果你要自己写程序链接 DPDK静态库省事不用配LD_LIBRARY_PATH。但如果你要多个程序共享 DPDK 库或者做插件式加载就开共享库。第二个是CONFIG_RTE_LIBRTE_PMD_PCAP这个默认关闭。开了之后可以用 pcap 驱动把 DPDK 程序接到普通网卡甚至抓包文件上调试特别方便。我强烈建议打开编译时加-Dc_args-DRTE_LIBRTE_PMD_PCAP或者在 meson 配置里加-Denable_driversnet/pcap。第三个是CONFIG_RTE_MAX_MEMSEG默认值 256。如果你要处理大量小内存段或者跑很多个 DPDK 进程这个值可能不够会报Failed to allocate memory segment。改大到 1024 或者 2048 试试。4. 大页内存配置与网卡绑定4.1 大页内存的原理和配置DPDK 性能高的核心原因之一就是用了大页内存。普通内存页是 4KB大页是 2MB 或者 1GB。页越大TLB 命中率越高地址转换开销越小。你可以把 TLB 想象成一个地址簿4KB 页需要记很多条2MB 页条目少得多查起来快。配置 2MB 大页先看当前状态cat /proc/meminfo | grep Huge然后分配大页。假设你要给 DPDK 留 4GB 内存2MB 一页就是 2048 页echo 2048 /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages永久生效要改/etc/sysctl.conf加一行vm.nr_hugepages 2048然后sysctl -p。但更推荐用内核启动参数在/etc/default/grub的GRUB_CMDLINE_LINUX里加default_hugepagesz1G hugepagesz1G hugepages4这样开机就分配 1GB 大页。1GB 大页性能比 2MB 更好但需要 CPU 支持而且分配后不能动态调整。注意大页内存是预留的普通程序用不了。你分配了 4GB 大页free命令看到的内存会少 4GB这是正常的。如果分配失败检查一下是不是内存不够或者 NUMA 节点上内存分布不均。多路服务器上每个 NUMA 节点都要单独分配。挂载大页文件系统mkdir -p /mnt/huge mount -t hugetlbfs nodev /mnt/huge永久挂载就写进/etc/fstabnodev /mnt/huge hugetlbfs defaults 0 0。4.2 网卡绑定到 DPDK 驱动DPDK 要接管网卡必须把网卡从内核驱动解绑绑定到igb_uio或者vfio-pci。先看网卡列表cd dpdk-19.11/usertools ./dpdk-devbind.py --status输出会列出所有网络设备包括内核驱动和 DPDK 驱动的绑定情况。找到你要用的网卡记下 PCI 地址比如0000:03:00.0。加载 igb_uio 模块modprobe uio insmod /path/to/dpdk-19.11/build/kmod/igb_uio.ko如果你用 meson 构建igb_uio.ko 在build/kmod下用 make 构建就在$RTE_TARGET/kmod下。加载后绑定网卡./dpdk-devbind.py --bindigb_uio 0000:03:00.0再跑一次--status确认绑定成功。这时候内核就看不到这张网卡了ifconfig里也不会显示。实操心得绑定前一定要确认这张网卡没在用比如没配 IP、没跑业务。绑定后如果发现绑错了用--bindixgbe或者对应的内核驱动名绑回去。另外虚拟机里的网卡通常是 virtio 或者 e1000virtio 需要 vfio-pci 而不是 igb_uioe1000 可以用 igb_uio。如果你在 VMware 里跑网卡是 e1000e绑定 igb_uio 后可能收不到包这是驱动兼容性问题换 vfio-pci 试试。4.3 vfio-pci 的配置方法vfio-pci 比 igb_uio 更现代支持 IOMMU安全性更好。但配置稍微麻烦一点。先开 IOMMU在 grub 启动参数里加intel_iommuon iommupt重启。然后加载 vfio 模块modprobe vfio-pci绑定网卡./dpdk-devbind.py --bindvfio-pci 0000:03:00.0vfio-pci 需要网卡所在的 IOMMU 组里所有设备都绑定到 vfio否则会报错。用ls /sys/bus/pci/devices/0000:03:00.0/iommu_group/devices/看组里有哪些设备全部绑上。5. 跑通第一个 DPDK 程序5.1 用 helloworld 验证环境helloworld 是最简单的 DPDK 程序只初始化 EAL 然后打印每个核心的 hello。跑之前设置环境变量export RTE_SDK/path/to/dpdk-19.11 export RTE_TARGETx86_64-native-linuxapp-gcc然后运行./build/examples/helloworld -l 0-3 -n 4 --socket-mem 1024,0参数解释-l 0-3指定用 0 到 3 号逻辑核-n 4指定内存通道数--socket-mem 1024,0指定每个 NUMA 节点分配的内存第一个节点 1024MB第二个 0。如果你机器是单路写--socket-mem 1024就行。看到每个核心打印hello from core X就说明环境通了。如果报EAL: No free hugepages reported回去检查大页配置。如果报EAL: Cannot init memory可能是大页内存不够或者 NUMA 配置不对。5.2 用 l2fwd 做二层转发测试l2fwd 比 helloworld 复杂一点它从一块网卡收包从另一块网卡发出去做二层转发。跑之前需要至少两块绑定了 DPDK 驱动的网卡。./build/examples/l2fwd -l 0-1 -n 4 --socket-mem 1024 -- -p 0x3-p 0x3是端口掩码0x3 表示用端口 0 和端口 1。程序跑起来后会周期性打印统计信息包括收发包数量、丢包数量。你可以用另一台机器接上网线打流或者用 pcap 驱动从文件读包。提示l2fwd 默认不做 MAC 地址学习它只是简单地把从端口 0 收到的包从端口 1 发出去反之亦然。如果你发现包发出去了但对面收不到检查一下网线是不是交叉线或者交换机端口是不是配了隔离。5.3 testpmd 的交互式调试testpmd 是 DPDK 自带的瑞士军刀功能比 l2fwd 丰富得多。启动./build/app/testpmd -l 0-3 -n 4 --socket-mem 1024 -- -i --portmask0x3-i是交互模式进去之后可以敲命令。常用的有start开始转发stop停止show port stats all看统计set fwd mac切换转发模式。testpmd 支持 mac、io、rxonly、txonly 等多种转发模式调试网卡收发包问题特别有用。我一般用 testpmd 做两件事一是验证网卡绑定是否正确show port info 0能看到网卡的 MAC、速率、队列数二是做性能摸底start tx_first然后看show port stats all的 pps 和带宽。6. 常见问题与排查技巧实录6.1 编译阶段常见报错编译阶段最容易卡在依赖和内核头文件上。我整理了一个速查表报错信息原因解决方法numa.h: No such file缺 numactl-develyum install numactl-develcannot find -lrte_ealRTE_TARGET 设错检查是否写成x86_64-native-linuxapp-gcckernel headers not foundkernel-devel 版本不匹配装与uname -r一致的 kernel-develmeson version too oldmeson 版本低于 0.47pip3 install --upgrade mesonigb_uio.ko compile error内核 4.18 不兼容换 vfio-pci 或打补丁还有一个隐蔽的坑如果你之前编译过其他版本的 DPDK环境变量RTE_SDK和RTE_TARGET可能还指向旧路径。编译前用echo $RTE_SDK确认一下不对就重新 export。6.2 运行阶段常见报错运行阶段的报错更让人头疼因为往往没有明确提示。常见的有这几类第一类是内存相关。EAL: No free hugepages reported in hugepages-2048kB说明大页没分配或者分配的数量不够。EAL: Cannot get hugepage information通常是 hugetlbfs 没挂载。EAL: memzone reserve failed可能是内存段碎片太多试试加大CONFIG_RTE_MAX_MEMSEG。第二类是网卡相关。EAL: Error - exiting with code: 1后面跟着Cause: No Ethernet devices found说明网卡没绑定或者绑定失败。EAL: Driver cannot attach the device可能是驱动不匹配比如用 igb_uio 绑了 virtio 网卡。第三类是权限相关。EAL: Permission denied检查是不是没用 root 跑或者 SELinux 没关。EAL: Cannot open /dev/hugepages检查 hugetlbfs 挂载点的权限。实操心得遇到报错先看 EAL 的日志它会把初始化过程每一步都打出来。日志级别用--log-leveldebug调到 debug能看到更详细的信息。另外--sysloglocal0可以把日志写到 syslog方便集中排查。6.3 性能不达预期的排查思路程序跑通了但性能上不去这是另一个层面的问题。先确认几个基础点CPU 是不是绑定了正确的核心有没有开超线程NUMA 节点是不是和网卡在同一个 socket。用lscpu看 CPU 拓扑用cat /sys/bus/pci/devices/0000:03:00.0/numa_node看网卡在哪个 NUMA 节点。如果这些都没问题再看是不是内存带宽瓶颈。用dmidecode -t memory看内存频率和通道数DPDK 的-n参数要和实际内存通道数一致否则性能会打折。比如双通道内存你写-n 4反而可能因为交错访问变慢。还有一个容易被忽略的点BIOS 里的电源管理。如果 CPU 开了节能模式频率会动态调整DPDK 的轮询模式会让 CPU 一直跑在高频但 BIOS 可能限制功耗。进 BIOS 把电源模式改成 performance关掉 C-state 和 P-state。7. 从编译到部署的完整链路梳理把上面这些串起来一个完整的流程是这样的装依赖、下源码、meson 配置、ninja 编译、配大页、挂 hugetlbfs、加载 igb_uio、绑定网卡、跑 helloworld 验证、跑 l2fwd 或 testpmd 做功能测试、最后部署自己的程序。每一步都有坑但踩过一遍之后就有肌肉记忆了。我自己的习惯是写一个 setup 脚本把环境变量、大页配置、模块加载、网卡绑定都放进去换机器的时候直接跑脚本省得一条条敲。脚本里加一些检查比如大页数量对不对、网卡绑定成没成功失败就报错退出。这样能避免“以为配好了其实没配好”的情况。另外如果你要在容器里跑 DPDK情况会更复杂。容器需要 privileged 权限大页和网卡设备要挂载进去/dev/hugepages和/dev/uioX都要映射。我试过在 Docker 里跑 testpmd能跑通但性能有损耗生产环境还是建议直接跑在宿主机上。最后分享一个小技巧如果你只是想验证 DPDK 程序逻辑不想折腾网卡绑定可以用 pcap 驱动。编译时打开 pcap 支持运行时用--vdevnet_pcap0,rx_pcapinput.pcap,tx_pcapoutput.pcap这样 DPDK 程序就从 pcap 文件读包、往 pcap 文件写包完全不碰真实网卡。调试协议解析、转发逻辑的时候特别方便我在开发阶段基本都用这个方式。