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 6,glibc 版本太低,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,把SELINUX=enforcing改成SELINUX=disabled,然后重启。防火墙对 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.14,bug 修复更全。分支名就是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 .. -Dexamples=l2fwd,l3fwd -Dplatform=native-Dexamples指定要编译哪些示例程序,不指定的话默认不编译示例。-Dplatform=native让编译器针对当前 CPU 做优化,生成的二进制性能更好,但换到别的 CPU 上可能跑不了。如果你要跨机器部署,改成-Dplatform=generic。
配置完成后编译:
ninja编译过程大概五到十分钟,取决于机器性能。编译完成后,库文件在build/lib下,示例程序在build/examples下。
再讲 make 的路子,作为对照。先设置环境变量:
export RTE_SDK=$(pwd) export RTE_TARGET=x86_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_drivers=net/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_hugepagesz=1G hugepagesz=1G hugepages=4,这样开机就分配 1GB 大页。1GB 大页性能比 2MB 更好,但需要 CPU 支持,而且分配后不能动态调整。
注意:大页内存是预留的,普通程序用不了。你分配了 4GB 大页,
free命令看到的内存会少 4GB,这是正常的。如果分配失败,检查一下是不是内存不够,或者 NUMA 节点上内存分布不均。多路服务器上,每个 NUMA 节点都要单独分配。
挂载大页文件系统:
mkdir -p /mnt/huge mount -t hugetlbfs nodev /mnt/huge永久挂载就写进/etc/fstab:nodev /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 --bind=igb_uio 0000:03:00.0再跑一次--status确认绑定成功。这时候内核就看不到这张网卡了,ifconfig里也不会显示。
实操心得:绑定前一定要确认这张网卡没在用,比如没配 IP、没跑业务。绑定后如果发现绑错了,用
--bind=ixgbe或者对应的内核驱动名绑回去。另外,虚拟机里的网卡通常是 virtio 或者 e1000,virtio 需要 vfio-pci 而不是 igb_uio,e1000 可以用 igb_uio。如果你在 VMware 里跑,网卡是 e1000e,绑定 igb_uio 后可能收不到包,这是驱动兼容性问题,换 vfio-pci 试试。
4.3 vfio-pci 的配置方法
vfio-pci 比 igb_uio 更现代,支持 IOMMU,安全性更好。但配置稍微麻烦一点。先开 IOMMU,在 grub 启动参数里加intel_iommu=on iommu=pt,重启。然后加载 vfio 模块:
modprobe vfio-pci绑定网卡:
./dpdk-devbind.py --bind=vfio-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_TARGET=x86_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 --portmask=0x3-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-devel | yum install numactl-devel |
cannot find -lrte_eal | RTE_TARGET 设错 | 检查是否写成x86_64-native-linuxapp-gcc |
kernel headers not found | kernel-devel 版本不匹配 | 装与uname -r一致的 kernel-devel |
meson version too old | meson 版本低于 0.47 | pip3 install --upgrade meson |
igb_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-level=debug调到 debug,能看到更详细的信息。另外,--syslog=local0可以把日志写到 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 支持,运行时用--vdev=net_pcap0,rx_pcap=input.pcap,tx_pcap=output.pcap,这样 DPDK 程序就从 pcap 文件读包、往 pcap 文件写包,完全不碰真实网卡。调试协议解析、转发逻辑的时候特别方便,我在开发阶段基本都用这个方式。