DPDK实践之(1)dpdk基础使用
Author: Once Day Date: 2024年5月19日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文档可参考专栏:Linux基础知识_Once-Day的博客-CSDN博客
参考文章:
- DPDK总结(网卡初始化)_rte_eth_dev_count_hz5034的博客-CSDN博客
- DPDK之网卡收包流程 (qq.com)
- Git DPDK资料下载
- DPDK download
- Getting Started Guide for Linux — documentation (dpdk.org)
- Sample Applications User Guides — documentation (dpdk.org)
文章目录
- DPDK实践之(1)dpdk基础使用
- 1. 概述
- 1.1 linux编译方式
- 1.2 运行系统要求
- 1.3 二级进程支持
- 2. 源码编译
- 2.1 编译方式
- 2.2 编译步骤
- 3. 运行dpdk基础程序
- 3.1 Linux用户空间驱动
- 3.2 dpdk程序运行标准步骤
- 3.3 运行helloworld程序
- 3.4 DPDK程序支持valgrind
- 4. DPDK EAL参数
- 4.1 CPU核心相关参数介绍
- 4.2 网卡相关参数
- 4.3 内存相关参数
- 4.4 debug相关参数
- 4.5 杂项选项
- 4.6 Linux相关参数
- 5. 附录
- 5.1 DPDK运行CPU
- 5.2 DPDK运行内存
- 5.3 dpdk遥测功能
1. 概述
DPDK(Data Plane Development Kit)是一个由 Intel 开发并开源的软件库和驱动集合,用于在 commodity hardware(通用硬件)上进行快速封包处理。DPDK 可以极大提高数据包处理速度和吞吐量,使得网络应用能够高效地在 CPU 上运行,而无需依赖昂贵的专有硬件或专门的网络处理设备。
DPDK 的主要组成部分包括:
-
Core Libraries:提供了一组用于处理数据包的核心库,包括内存、队列、缓冲池、端口和流量管理等功能。
-
Poll Mode Drivers (PMDs):这些是为各种网络接口卡(NICs)编写的轮询模式驱动程序,可以绕过内核,直接在用户空间处理数据包。
-
Additional Libraries:除了核心库和 PMDs,DPDK 还提供了一些附加的库,用于实现更高级的网络功能,如安全、流量管理和服务质量(QoS)等。
DPDK 也支持各种类型的处理器和操作系统,包括 Linux、FreeBSD 和 Windows,可以在 x86、Power 和 ARM 等架构上运行。
DPDK 官网文档地址是:http://www.dpdk.org/doc。您可以在此找到如何安装和使用 DPDK 的详细指南,以及各种参考文档和教程。
1.1 linux编译方式
对于大多数平台,使用基本的DPDK功能不需要特殊的BIOS设置。在x86上可能有一些BIOS设置先决条件:
- HPET计时器:如果你的应用程序需要使用高精度事件计时器(HPET),可能需要在BIOS中启用此功能。
- 功率管理:某些DPDK应用程序可能会受益于更精细的功率管理。例如,可以在BIOS中启用或禁用CPU的某些节能特性。
- 小数据包性能:对于一些需要处理大量小数据包的应用,可能需要在BIOS中调整某些设置以优化性能。例如,可能需要禁用CPU的某些节能特性,或者更改内存访问策略。
对于编译期,需要支持C11标准(包含atomic实现),以及一些配套的编译工具(gcc/clang/pkg-config/pkgconf等等)。以下是在不同的Linux发行版中安装这些开发工具的命令:
对于RHEL/Fedora系统:
sudo dnf groupinstall "Development Tools"
对于Ubuntu/Debian系统:
sudo apt update
sudo apt install build-essential
对于Alpine Linux:
apk add alpine-sdk bsd-compat-headers
pkg-config
是一个脚本,它的主要目的是帮助你在编译过程中添加正确的编译器和链接器的选项。比如,如果你的程序需要一个库,需要知道这个库的头文件和库文件的位置。pkg-config
可以帮你查询这些信息。
在Debian或Ubuntu等基于Debian的系统中,可以使用以下命令来安装pkg-config
:
sudo apt update
sudo apt install pkg-config
然后,你可以使用pkg-config
命令来查询库的信息。例如,下面的命令可以查询libpng
库的版本信息:
pkg-config --modversion libpng
pkgconf
是pkg-config
的一个替代品,提供了相似的功能,并且有一些额外的改进。例如,pkgconf
可以更好地处理库之间的依赖关系。
在Debian或Ubuntu等基于Debian的系统中,你可以使用以下命令来安装pkgconf
:
sudo apt update
sudo apt install pkgconf
然后,你可以像使用pkg-config
一样使用pkgconf
命令。
此外也需要以下依赖工具:
- Python 3.6 or later.
- Meson (version 0.53.2+) and ninja。
Meson
是一个开源的构建系统,旨在提供一个高效、用户友好的构建环境。Ninja
是一个小型的构建系统,专注于速度,常常被用作Meson
和其他构建系统(如CMake
)的后端。 pyelftools
(version 0.22+)。pyelftools
是一个Python库,用于解析和操作ELF (Executable and Linkable Format) 和 DWARF (Debug With Arbitrary Record Format) 格式的文件。这些格式被广泛用于可执行文件、目标文件、共享库和核心转储。- Library for handling NUMA (Non Uniform Memory Access).
libnuma-dev
in Debian/Ubuntu。
一些工具和库是可选的,可以用来增强DPDK的功能或者使其能支持更多的硬件特性。
-
Intel® C++ Compiler (icc):这是Intel公司的C++编译器,可以充分利用Intel处理器的特性来优化代码。你可以在Intel官网找到安装指南和文档。在安装icc时,可能需要安装一些额外的库。
-
IBM® Advance ToolChain for Powerlinux:这是一个开源的开发工具集和运行时库,可以让用户在Linux系统上充分利用IBM最新的POWER硬件特性。你可以在IBM官网找到安装文档。
对于DPDK的一些组件,如库和poll-mode drivers (PMDs),可能需要额外的依赖。在构建DPDK时,这些依赖会被自动检测,并且会相应地启用或禁用相关的组件。
在所有情况下,都需要相应的库开发包(-devel或-dev)来构建DPDK组件。
例如,这些额外的依赖包括:
- libarchive:一些单元测试使用tar获取它们的资源。
- libelf:编译和使用bpf库。
对于每个poll-mode driver,其额外的依赖可以在相关的DPDK指南文档中找到:
- Network Interface Controller Drivers — documentation (dpdk.org)
1.2 运行系统要求
参考文档:2. System Requirements — Data Plane Development Kit 24.03.0 documentation (dpdk.org)
下面是运行DPDK应用的基本系统需求:
- Kernel version >= 4.14,Linux系统内核版本至少4.14之上。
- glibc >= 2.7 (for features related to cpuset),Glibc版本至少在2.7之上。
- Linux内核配置要求HUGETLBFS、PROC_PAGE_MONITOR support。
- 如果HPET使能,则Linux内核配置要求HPET和HPET_MMAP也使能,可见High Precision Event Timer (HPET) Functionality。
Hugepages是一个在内核级别支持的特性,它可以使系统使用大于默认的4KB的内存页大小。使用Hugepages可以增加性能,因为需要的页数更少,因此需要更少的Translation Lookaside Buffers (TLBs)。TLB是一种高速的地址翻译缓存,它能减少从虚拟页地址到物理页地址的转换时间。如果不使用Hugepages,标准的4KB页大小会导致高TLB miss比率,进而降低性能。
在Linux系统中,可以通过下面的步骤启用和配置Hugepages:
-
检查内核支持:首先,需要确认Linux内核支持Hugepages。可以通过查看
/proc/meminfo
文件来确认这一点。在这个文件中,如果看到像HugePages_Total
、HugePages_Free
和Hugepagesize
这样的行,那么内核支持Hugepages。 -
配置Hugepages:可以通过
sysctl
命令或直接修改/etc/sysctl.conf
文件来配置Hugepages的数量。例如,如果想设置Hugepages的数量为1024,可以将下面的行添加到/etc/sysctl.conf
文件中:vm.nr_hugepages = 1024
然后,可以运行
sudo sysctl -p
命令来应用这个更改。 -
挂载Hugepages文件系统:最后需要挂载一个Hugepages文件系统。可以通过
mount
命令来完成这个操作,如下:sudo mkdir -p /mnt/huge sudo mount -t hugetlbfs nodev /mnt/huge
也可以将上面的行添加到
/etc/fstab
文件中,以便在系统启动时自动挂载Hugepages文件系统。
在运行时以及启动时预留hugepages。在许多情况下,预留hugepages可以优化高性能计算和网络应用的性能。
-
在运行时预留hugepages:可以通过向
/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
文件写入需要的hugepages数量来在系统运行时预留hugepages。这对于单节点系统来说很简单。在多NUMA系统中,可以如下为NUMA节点单独预留hugepages。# 预留1024 * 2 MB的大页内存 echo 1024 > /sys/devices/system/node/nodeX/hugepages/hugepages-2048kB/nr_hugepages
-
在启动时预留hugepages:在某些情况下,可能需要在系统启动时预留hugepages。这可以通过在内核命令行中添加参数来完成。例如,对于2MB的页,可以使用
hugepages=1024
参数。对于1GB的页,需要显式指定大小,如:default_hugepagesz=1G hugepagesz=1G hugepages=4
一些内核不允许在运行时收集1G大小的内存页,因此需要在启动时预留,此时另外一个好处是内存物理地址也能更加连续。
-
DPDK Hugepages管理工具:DPDK提供了一个工具
dpdk-hugepages.py
,可以用来管理hugepages。 -
Hugepages大小的支持:支持的hugepages大小取决于CPU的架构。在Intel架构中,如果CPU标志中存在
pse
,则支持2MB的hugepages;如果存在pdpe1gb
,则支持1GB的hugepages。在IBM Power架构中,支持的hugepages大小为16MB和16GB。 -
64位应用的建议:对于64位应用,如果平台支持,推荐使用1GB的hugepages。
-
NUMA系统中的分配:在双插槽NUMA系统中,启动时预留的hugepages数量通常在两个插槽之间平均分配(假设两个插槽上都有足够的内存)。
对于更多的内核选项,可以在Linux源代码树的 Documentation/admin-guide/kernel-parameters.txt
文件中查看。
1.3 二级进程支持
参考文档:2. System Requirements — Data Plane Development Kit 24.03.0 documentation (dpdk.org)
在DPDK(数据平面开发套件)中,二级进程(Secondary Process)是一种特殊的进程,它可以与主进程(Primary Process)共享某些资源,例如内存,队列等。在很多场景下,主进程负责初始化和启动一些服务,例如内存管理,设备管理等。然后,二级进程可以利用这些已经初始化的资源进行一些特定的任务。
这种设计使得在一个系统中可以运行多个进程,而不需要每个进程都进行一次完整的初始化。这样就可以降低启动时间,减少资源使用,提高整体性能。
在DPDK中,二级进程通常通过使用rte_eal_init
函数的特定参数启动,这些参数使得二级进程能够找到并连接到主进程已经初始化的资源。
如果不需要二级进程支持,DPDK可以通过使用“in-memory”模式无需任何配置即可使用大页。如果需要二级进程支持,需要创建大页的挂载点。在现代Linux发行版上,系统提供了一个默认的大页挂载点,位于/dev/hugepages。这个挂载点将使用上述内核参数设置的默认大页大小。
然而,为了使用默认以外的大页大小,需要手动为这些大页大小(例如1GB页)创建挂载点。为了使大小为1GB的大页可供DPDK使用,必须执行以下步骤:
mkdir /mnt/huge
mount -t hugetlbfs pagesize=1GB /mnt/huge
可以通过在/etc/fstab文件中添加以下行,使挂载点在重启后保持永久:
nodev /mnt/huge hugetlbfs pagesize=1GB 0 0
2. 源码编译
2.1 编译方式
有多种编译方式,这里以Linux平台X86本地编译为主,各种编译方式链接如下:
- 3. Compiling the DPDK Target from Source — Data Plane Development Kit 23.11.0-rc1 documentation
- 4. Cross compiling DPDK for aarch64 and aarch32 — Data Plane Development Kit 23.11.0-rc1 documentation
- 5. Cross compiling DPDK for LoongArch — Data Plane Development Kit 23.11.0-rc1 documentation
- 6. Cross compiling DPDK for RISC-V — Data Plane Development Kit 23.11.0-rc1 documentation
2.2 编译步骤
首先在官网下载源码文件(也可以在gitlab下载),选择合适的版本,然后进行解压:
onceday@book2023:~/dpdk-compile$ tar -xJf dpdk-22.11.3.tar.xz
onceday@book2023:~/dpdk-compile$ cd dpdk-stable-22.11.3/
以下是这些目录更详细的解释:
doc
目录,包含了DPDK的官方文档,如安装指南、程序员手册、API文档等。license
目录,包含了DPDK的许可证信息。lib
目录,包含了构成DPDK的各种库的源代码,例如内存管理、缓冲池、队列、以太网控制器等。drivers
目录,包含了DPDK的轮询模式驱动程序的源代码,这些驱动程序主要用于网络接口卡。app
目录,包含了DPDK的应用程序的源代码,这些应用程序主要用于自动测试DPDK的各种功能。examples
目录,包含了一些DPDK应用程序的示例源代码,这些示例可以帮助开发者更好地理解和使用DPDK。config
和buildtools
目录,包含了一些与构建和配置DPDK相关的脚本和配置文件。usertools
目录,包含了一些脚本,这些脚本可以帮助DPDK的最终用户更方便地使用DPDK应用程序。devtools
目录,包含了一些脚本,这些脚本主要供DPDK的开发者在开发过程中使用。kernel
目录,包含了一些内核模块的源代码,这些内核模块在某些操作系统上是必需的,例如Linux。
要配置DPDK构建,可以使用:
meson setup <options> build
其中,“build”是所需的输出构建目录,<options>
可以为空或是meson或DPDK特定的构建选项之一。
配置好之后,要构建并安装DPDK到系统中,使用:
cd build
ninja
sudo meson install
sudo ldconfig
上面的最后两个命令通常需要以root身份运行,meson install
步骤将构建的对象复制到最终的系统范围的位置,最后一步使动态加载器ld.so
更新其缓存以考虑新的对象。
在某些Linux发行版中,例如Fedora或Redhat,/usr/local
中的路径不在加载器的默认路径中。因此,在这些发行版上,应在运行ldconfig
之前将/usr/local/lib
和/usr/local/lib64
添加到/etc/ld.so.conf.d/
目录中的一个文件里。你可以通过以下命令来做到这一点:
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/local_lib.conf
echo "/usr/local/lib64" | sudo tee -a /etc/ld.so.conf.d/local_lib.conf
sudo ldconfig
这样,加载器就可以在/usr/local/lib
和/usr/local/lib64
中查找动态链接库了。
3. 运行dpdk基础程序
3.1 Linux用户空间驱动
参考文档:7. Linux Drivers — Data Plane Development Kit 24.03.0 documentation (dpdk.org)
DPDK 支持多种网卡驱动,包括 Intel IXGBE、Intel I40E、Intel ICE、Mellanox MLX4、Mellanox MLX5、Cisco enic 等。DPDK使用 UIO (Userspace I/O) 驱动或 VFIO (Virtual Function I/O) 驱动来实现用户态驱动,绕过内核网络协议栈,以提高数据包处理性能。
- UIO 驱动包括
igb_uio
和uio_pci_generic
,它们是内核模块,用于将网卡设备绑定到用户空间。 - VFIO 驱动是一种更新的用户态驱动方式,它利用 IOMMU 实现设备直接分配给用户空间,提供更好的性能和安全性。
使用 dpdk-devbind.py
构建 DPDK 驱动环境:
dpdk-devbind.py
是 DPDK 提供的一个工具,用于管理网卡设备的驱动绑定。- 首先,确保已经加载了所需的内核模块,如
uio
、igb_uio
或vfio-pci
。 - 使用
dpdk-devbind.py --status
命令查看当前系统中的网卡设备及其绑定的驱动。 - 使用
dpdk-devbind.py --bind
命令将网卡设备绑定到 DPDK 支持的用户态驱动,例如:
这将把sudo dpdk-devbind.py --bind=uio_pci_generic eth0
eth0
网卡绑定到uio_pci_generic
驱动。 - 如果需要将网卡设备恢复到内核驱动,可以使用
--unbind
选项:sudo dpdk-devbind.py --unbind eth0
- 绑定完成后,可以再次使用
dpdk-devbind.py --status
命令确认网卡设备已经成功绑定到了目标驱动。
在使用 DPDK 时,绑定的网卡设备将无法被内核网络协议栈使用,因此在配置时需要谨慎选择要绑定的网卡设备,以免影响系统的正常网络功能。
下面是一台虚拟云服务器的驱动查询情况:
ubuntu->build:$ sudo dpdk-devbind.py --statusNetwork devices using kernel driver
===================================
0000:00:05.0 'Virtio network device 1000' if=eth0 drv=virtio-pci unused=vfio-pci *Active*
eth0是一个虚拟接口,驱动是virtio-pci,可以更换为(危险操作)vfio-pci,然后dpdk就可以使用该接口。但是这里不建议这么操作,除非在本地设备上创建虚拟机完成这个操作。
一般的家用PC网卡驱动,dpdk都不支持用户空间驱动,因此后面用dpdk自行创建的虚拟接口完成测试,如下:
(1) TAP (Terminal Access Point) 设备是一种虚拟网络接口,模拟了一个以太网设备,可以与物理网络设备类似地进行数据包的发送和接收。
-
在 DPDK 中,可以使用
net_tap
PMD (Poll Mode Driver) 创建 TAP 设备。通过--vdev
参数指定net_tap
设备,并提供相关的配置选项,例如:sudo testpmd -c 0x3 -n 4 --vdev 'net_tap0,iface=tap0' -- -i
这个命令会创建一个名为
net_tap0
的虚拟接口,并将其与tap0
这个 TAP 设备关联。 -
TAP 设备创建后,可以像物理网卡一样进行配置,例如设置 IP 地址、子网掩码等。
-
DPDK 应用程序可以通过
rte_eth_rx_burst
和rte_eth_tx_burst
等 API 函数来接收和发送 TAP 设备上的数据包。 -
TAP 设备的优点是配置简单,容易与现有的网络环境集成。它可以与其他虚拟化技术(如 QEMU、VirtualBox 等)配合使用,实现虚拟机与 DPDK 应用程序之间的网络通信。
(2) virtio 接口,virtio 是一种用于虚拟化环境的通用 I/O 框架,它提供了一组标准的接口和协议,用于在虚拟机和宿主机之间高效地传输数据。
- DPDK 支持使用
virtio-user
后端创建基于 virtio 的虚拟接口。通过--vdev
参数指定net_virtio_user
设备,并提供相关的配置选项,例如:
这个命令会创建一个名为sudo testpmd -c 0x3 -n 4 --vdev 'net_virtio_user0,path=/tmp/vhost-sock0' -- -i
net_virtio_user0
的虚拟接口,并将其与/tmp/vhost-sock0
这个 Unix 域套接字关联。 - virtio 接口的数据传输是基于共享内存的,通过在虚拟机和宿主机之间共享内存区域来实现高效的数据传输。
- DPDK 应用程序可以通过
rte_eth_rx_burst
和rte_eth_tx_burst
等 API 函数来接收和发送 virtio 接口上的数据包。 - virtio 接口的优点是性能高,可以利用虚拟化技术提供的硬件加速功能,如 SR-IOV、IOMMU 等,实现近乎裸金属的性能。
- virtio 接口常用于 DPDK 应用程序与虚拟机的高性能网络通信场景,例如在 NFV (网络功能虚拟化)中,通过 virtio 接口将虚拟机中的网络流量高效地转发给 DPDK 应用程序进行处理。
TAP 设备配置简单,易于与现有网络环境集成,而 virtio 接口则提供了更高的性能,适用于对网络性能要求较高的场景。
3.2 dpdk程序运行标准步骤
DPDK应用的启动通常涉及以下几个步骤:
-
环境设置:在运行DPDK应用之前,需要设置一些环境变量,例如,对Hugepages的配置。这可以通过在启动脚本中设置
nr_hugepages
参数来完成。echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
-
绑定驱动:DPDK应用需要直接访问网络设备。这需要将网络设备的驱动从常规的Linux网络驱动(例如
e1000
)更改为DPDK支持的用户空间驱动,如uio_pci_generic
、igb_uio
或vfio
。首先,加载适当的UIO模块,例如
igb_uio
:sudo modprobe uio sudo insmod kmod/igb_uio.ko
然后,找出要绑定到DPDK的网络设备的PCI地址。你可以使用
lspci
命令来查找,例如:lspci | grep Ethernet
设备的PCI地址应该类似于
01:00.0
或02:00.0
。最后,使用
dpdk-devbind.py
工具将设备绑定到igb_uio
驱动:sudo python3 usertools/dpdk-devbind.py --bind=igb_uio 01:00.0
-
启动应用:使用
sudo
启动应用时,将PCI设备的地址作为参数传递给应用。sudo ./build/l2fwd -l 0-3 -n 4 -- -q 8 -p 0x1
3.3 运行helloworld程序
默认的dpdk编译里没有包含helloworld这个示例程序,因此这里先添加helloworld编译选项,如下:
在dpdk源码界面输入以下命令:
sudo meson configure build -Dexamples=helloworld
cd build
sudo ninja
然后就可以在examples目录下面看到编译出来的可执行程序:
ubuntu->build:$ ll examples/
-rwxr-xr-x 1 root root 32900088 May 19 17:12 dpdk-helloworld*
先尝试直接运行,会出现错误:
ubuntu->build:$ ./examples/dpdk-helloworld
EAL: Detected CPU lcores: 4
EAL: Detected NUMA nodes: 1
EAL: Detected static linkage of DPDK
EAL: Multi-process socket /run/user/1000/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'VA'
EAL: No free 2048 kB hugepages reported on node 0
EAL: FATAL: Cannot get hugepage information.
EAL: Cannot get hugepage information.
PANIC in main():
Cannot init EAL
6: [./examples/dpdk-helloworld(_start+0x25) [0x55b1030fcda5]]
5: [/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f818377ae40]]
4: [/lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f818377ad90]]
3: [./examples/dpdk-helloworld(+0x1f130c) [0x55b1023e630c]]
2: [./examples/dpdk-helloworld(__rte_panic+0xcd) [0x55b102402293]]
1: [./examples/dpdk-helloworld(rte_dump_stack+0x32) [0x55b10329cab2]]
Aborted
这里提示没有找到可用的大页内存,所以使用3.2节里的方法来保留大页内存,如下(需要root权限):
onceday->~:# echo 128 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
然后使用管理员权限运行,如下:
ubuntu->build:$ sudo ./examples/dpdk-helloworld
EAL: Detected CPU lcores: 4
EAL: Detected NUMA nodes: 1
EAL: Detected static linkage of DPDK
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: VFIO support initialized
EAL: Probe PCI driver: net_virtio (1af4:1000) device: 0000:00:05.0 (socket 0)
eth_virtio_pci_init(): Failed to init PCI device
EAL: Requested device 0000:00:05.0 cannot be used
TELEMETRY: No legacy callbacks, legacy socket not created
hello from core 1
hello from core 2
hello from core 3
hello from core 0
可以看到,默认在所有核上运行一个线程,每个线程都输出一个hello,这就是dpdk的per-core处理,能够在每个核上绑定一个线程。此外,这里提示PCI驱动绑定失败,这是因为运行之前没有替换网卡驱动为DPDK支持的用户空间驱动。
3.4 DPDK程序支持valgrind
在X86上,会因为AVX512指令集导致valgrind支持异常,根据stack-overflow回答,需要关闭dpdk编译对avx512指令集的支持情况,可参考文档:gcc - Disabling all AVX512 extensions - Stack Overflow。
这个avx512指令集通过一个python脚本检测,直接修改代码即可(直接返回错误):
#buildtools/binutils-avx512-check.py
import subprocess
import sys
import tempfileobjdump, *cc = sys.argv[1:]
with tempfile.NamedTemporaryFile() as obj:# On Windows, the file is opened exclusively and is not writable.sys.exit(1) # 这里直接返回错误obj.close()# from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90028gather_params = '0x8(,%ymm1,1),%ymm0{%k2}'src = '__asm__("vpgatherqq {}");'.format(gather_params).encode('utf-8')subprocess.run(cc + ['-c', '-xc', '-o', obj.name, '-'], input=src, check=True)asm = subprocess.run([objdump, '-d', '--no-show-raw-insn', obj.name],stdout=subprocess.PIPE, check=True).stdout.decode('utf-8')if gather_params not in asm:print('vpgatherqq displacement error with as')sys.exit(1)
然后使用meson setup
后,生成的rte_build_config.h文件里就不会包含相关的指令集,即:
#define RTE_COMPILE_TIME_CPUFLAGS RTE_CPUFLAG_SSE,RTE_CPUFLAG_SSE2,RTE_CPUFLAG_SSE3,RTE_CPUFLAG_SSSE3,RTE_CPUFLAG_SSE4_1,RTE_CPUFLAG_SSE4_2,RTE_CPUFLAG_AES,RTE_CPUFLAG_AVX,RTE_CPUFLAG_AVX2,RTE_CPUFLAG_PCLMULQDQ,RTE_CPUFLAG_RDRAND,RTE_CPUFLAG_RDSEED
使用valgrind来检测dpdk-helloworld的内存使用情况,如下:
ubuntu->build:$ sudo valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes ./examples/dpdk-helloworld
==2444979== Memcheck, a memory error detector
==2444979== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==2444979== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==2444979== Command: ./examples/dpdk-helloworld
==2444979==
EAL: Detected CPU lcores: 4
EAL: Detected NUMA nodes: 1
EAL: Detected static linkage of DPDK
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
......(省略大量内容)......
==2444979== LEAK SUMMARY:
==2444979== definitely lost: 0 bytes in 0 blocks
==2444979== indirectly lost: 0 bytes in 0 blocks
==2444979== possibly lost: 1,600 bytes in 5 blocks
==2444979== still reachable: 43,254 bytes in 445 blocks
==2444979== suppressed: 0 bytes in 0 blocks
==2444979==
==2444979== For lists of detected and suppressed errors, rerun with: -s
==2444979== ERROR SUMMARY: 19 errors from 14 contexts (suppressed: 0 from 0)
从运行结果来看,有很多泄露地方,这是因为dpdk库并未考虑对一些持续存在的内存进行释放,而且由于申请了太多的虚拟地址,导致valgrind的执行结果较久,效率较低。
如果想将valgrind完全用于dpdk,还需要进一步优化dpdk相关的函数,来减少这类误报和执行效率问题。
4. DPDK EAL参数
参考文档:9. EAL parameters — Data Plane Development Kit 24.03.0 documentation (dpdk.org):
4.1 CPU核心相关参数介绍
-c COREMASK/-l CORELIST
,用于指定应用程序使用的逻辑核。COREMASK是一个十六进制位掩码,CORELIST是一个逗号分隔的逻辑核列表。例如,--lcores 0-3
表示将逻辑核心0-3映射到物理核心0-3。
--lcores <core map>
,该参数用于将逻辑核心集合映射到物理CPU核心集合。参数格式为:
<lcores[@cpus]>[<,lcores[@cpus]>...]
其中,lcores表示逻辑核心列表,cpus表示物理CPU核心列表。多个映射关系可以用逗号",“分隔。在每个映射关系中,可以用括号”()“将lcores和cpus括起来表示一个组。连字符”-“用于指定连续的核心编号范围,逗号”,“用于分隔单个核心编号。如果一个组只有一个元素,可以省略括号。如果lcores和cpus的值相同,可以省略”@"符号。
例如,--lcores 0-3@(0-3),4@(4)
表示将逻辑核心0-3映射到物理核心0-3,将逻辑核心4映射到物理核心4。
--main-lcore <core ID>
,该参数用于指定主核心的ID。在DPDK应用程序中,通常会有一个主线程(main thread)用于执行初始化、配置和控制任务,而其他线程则用于处理数据包。–main-lcore参数就是用来指定运行主线程的核心ID。
-s <service core mask>
,该参数用于设置服务核心的十六进制掩码。服务核心是DPDK应用程序中专门用于处理服务任务的核心,如定时器、警报等。这些任务通常与数据包处理无关,但是对于应用程序的正常运行非常重要。
4.2 网卡相关参数
-b, --block <[domain:]bus:devid.func
>,该参数用于阻止EAL(Environment Abstraction Layer)使用指定的PCI设备。通过指定设备的域(domain)、总线(bus)、设备ID(devid)和功能(func),可以跳过对该设备的探测,从而防止EAL使用它。可以使用多个-b
选项来阻止多个设备。
例如,"-b 0000:01:00.0"表示阻止EAL使用域0、总线1、设备ID为0、功能为0的PCI设备。
-a, --allow <[domain:]bus:devid.func
>,该参数用于将指定的PCI设备添加到EAL的探测列表中。与-b
选项类似,需要指定设备的域、总线、设备ID和功能。
例如,"-a 0000:02:00.0"表示将域0、总线2、设备ID为0、功能为0的PCI设备添加到EAL的探测列表中。
需要注意,-a选项不能与-b(阻止列表)选项同时使用。
--vdev <device arguments>
,该参数用于添加虚拟设备。虚拟设备是指不是物理存在的设备,而是由DPDK模拟的设备。–vdev参数的格式为<driver><id>[,key=val, ...]
,其中,driver是虚拟设备的驱动名称,id是设备的唯一标识符,key=val是可选的参数。
例如,--vdev 'net_pcap0,rx_pcap=input.pcap,tx_pcap=output.pcap
表示添加一个名为net_pcap0的虚拟设备,该设备使用PCAP驱动,并指定了rx_pcap和tx_pcap两个参数,分别表示接收和发送数据包的PCAP文件。
-d <path to shared object or directory>
,该参数用于加载外部驱动程序。可以指定单个共享对象文件的路径,也可以指定包含多个驱动程序共享对象的目录路径。可以使用多个-d选项来加载多个驱动程序。
例如,-d /usr/local/lib/dpdk/drivers
表示加载/usr/local/lib/dpdk/drivers
目录下的所有驱动程序。
--no-pci
,该参数用于禁用PCI总线。使用该参数后,EAL将不会探测和使用任何PCI设备。
4.3 内存相关参数
-n <number of channels>
,该参数用于设置内存通道的数量。在多通道内存系统中,可以通过增加通道数来提高内存带宽和性能。EAL将根据指定的通道数对内存进行初始化和分配。
例如,-n 4
表示使用4个内存通道。
-r <number of ranks>
,该参数用于设置内存条(rank)的数量。内存条是指物理内存模块,一个内存通道可以有多个内存条。EAL默认会自动检测内存条的数量,但也可以通过-r参数手动指定。
例如,-r 2
表示每个内存通道有2个内存条。
-m <megabytes>
,该参数用于指定在启动时预先分配的内存大小,单位为兆字节(MB)。EAL将在应用程序启动时一次性分配指定大小的内存,以避免运行时频繁的内存分配和释放操作,提高性能。
例如,-m 1024
表示预先分配1024MB(即1GB)的内存。
--in-memory
,该参数用于指定EAL完全在内存中运行,不创建任何共享数据结构。这意味着EAL不会使用共享内存、共享文件等跨进程通信的机制,而是将所有数据都保存在进程的内存空间中。使用该参数可以提高性能,但也会增加内存消耗。
需要注意的是,--in-memory
参数隐含了--no-shconf
(不创建共享配置)和--huge-unlink
(如果适用,不创建大页面文件)参数的效果。
--iova-mode <pa|va>
,该参数用于强制指定IOVA(I/O Virtual Address)模式。IOVA是用于进行DMA(直接内存访问)操作的虚拟地址空间。EAL支持两种IOVA模式:物理地址(pa)和虚拟地址(va)。
例如,--iova-mode pa
表示强制使用物理地址作为IOVA。
--huge-worker-stack[=size]
,该参数用于从大页面内存中分配工作线程的栈空间。默认情况下,EAL使用系统的pthread栈大小作为工作线程的栈大小。使用--huge-worker-stack
参数可以将栈分配在大页面内存中,以提高性能。可以通过可选的size参数指定栈的大小,单位为KB。
例如,--huge-worker-stack
表示使用大页面内存分配工作线程栈,栈大小为默认的pthread栈大小。而"–huge-worker-stack=4096"表示使用大页面内存分配工作线程栈,栈大小为4MB。
4.4 debug相关参数
--no-shconf
,该参数用于指定EAL不创建任何共享文件。这意味着EAL将不支持辅助进程(secondary process),因为辅助进程依赖于与主进程共享的配置文件和内存。使用该参数可以简化EAL的配置和管理,但也会限制应用程序的功能。
--no-huge
,该参数用于指定EAL使用匿名内存而不是大页面(hugepages)。与--no-shconf
参数类似,使用--no-huge
参数也会导致EAL不支持辅助进程,因为辅助进程通常与主进程共享大页面内存。使用匿名内存可以简化内存管理,但也可能影响性能。
--log-level <type:val>
,该参数用于为特定组件指定日志级别。可以多次使用该参数来设置不同组件的日志级别。参数格式为:--log-level <组件>:<级别>
,其中,<组件>
是EAL中的组件名称,如lib.eal表示EAL库;<级别>
是日志级别,如debug表示调试级别。例如,--log-level lib.eal:debug
表示将EAL库的日志级别设置为调试级别。
--trace=<regex-match>
,该参数用于根据正则表达式匹配的跟踪名称启用跟踪功能。默认情况下,跟踪功能是禁用的,需要通过--trace
参数显式启用。可以多次使用该参数,最多可以指定32个跟踪配置。
例如,--trace=eal
表示启用EAL组件的全局跟踪配置,--trace=.*
表示启用所有组件的全局跟踪配置。
--trace-dir=<directory path>
,该参数用于指定跟踪输出的目录路径。默认情况下,跟踪输出文件会创建在用户的主目录中。可以通过--trace-dir
参数指定其他目录。该参数只能指定一次。
例如,--trace-dir=/tmp
表示将/tmp目录配置为跟踪输出目录。
--trace-bufsz=<val>
,该参数用于指定每个线程的跟踪输出文件的最大大小。可以使用B、K、M等单位分别表示字节、千字节、兆字节。默认情况下,跟踪输出文件的大小为1MB。该参数只能指定一次。
例如,--trace-bufsz=2M
表示将跟踪输出文件的最大大小配置为2MB。
--trace-mode=<o[verwrite] | d[iscard]>
,该参数用于指定跟踪输出文件的更新模式。当文件大小达到最大限制时,可以选择覆盖(overwrite)或丢弃(discard)新的跟踪更新。默认模式为覆盖。该参数只能指定一次。
例如,“–trace-mode=d"或”–trace-mode=discard"表示当跟踪输出文件达到最大大小时,丢弃新的跟踪更新。
4.5 杂项选项
-h
, --help
,显示帮助信息,列出所有可用的 EAL (Environment Abstraction Layer) 参数。
-v
,在应用程序启动时显示版本信息。这对于确认您正在运行的 DPDK 版本很有帮助。
--mbuf-pool-ops-name
,指定 mbuf (内存缓冲区) 使用的池操作名称。DPDK 使用 mbuf 来管理数据包缓冲区,不同的池操作可能针对不同的使用场景进行了优化。
--telemetry
,启用遥测功能(默认启用)。DPDK 的遥测功能可以收集运行时的性能指标和事件,帮助进行性能分析和优化。
--no-telemetry
,禁用遥测功能。
--force-max-simd-bitwidth=<val>
,指定处理器支持的最大 SIMD (Single Instruction, Multiple Data) 位宽。这将限制程序使用的向量指令集。例如:
--force-max-simd-bitwidth=512
: 允许使用最大 512 位的 SIMD 指令,即 AVX-512。--force-max-simd-bitwidth=64
: 将 SIMD 位宽限制为 64 位,实际上禁用了所有向量化代码。--force-max-simd-bitwidth=0
: 禁用 SIMD 位宽限制。
4.6 Linux相关参数
--create-uio-dev
,为绑定到 igb_uio 内核驱动程序的设备创建 /dev/uioX
文件。通常由 igb_uio 驱动程序自行完成。例如,如果有两个网卡绑定到了 igb_uio 驱动,那么会自动创建 /dev/uio0
和 /dev/uio1
文件。
--vmware-tsc-map
,使用 VMware TSC 映射而不是原生 RDTSC。在 VMware 虚拟机中运行 DPDK 应用时,使用该选项可以提高时间戳计数器的精确度。
--no-hpet
,不使用高精度事件计时器(HPET)。在某些系统上,禁用 HPET 可能有助于提高性能。
--vfio-intr <legacy|msi|msix>
,为绑定到 VFIO 内核驱动程序的设备使用指定的中断模式。例如,--vfio-intr msix
表示使用 MSI-X 中断模式,相比于旧的中断模式,它可以支持更多的中断向量。
--vfio-vf-token <uuid>
, 为绑定到 VFIO 内核驱动程序的设备使用指定的 VF 令牌。这在使用 SR-IOV 虚拟功能时很有用,可以确保不同的 DPDK 进程使用不同的虚拟功能。
--file-prefix <prefix name>
,为 DPDK 进程使用不同的共享数据文件前缀。这允许在不同前缀下运行多个独立的 DPDK 主/从进程。例如,--file-prefix foo
将创建以 foo
开头的共享数据文件,而不是默认的 rte
前缀。
--legacy-mem
,使用传统的 DPDK 内存分配模式。在较新版本的 DPDK 中,默认使用更高效的内存分配方式。
--socket-mem <amounts of memory per socket>
,预分配每个 socket 指定数量的内存。例如,--socket-mem 1024,2048
将在 socket 0 上分配 1GB 内存,在 socket 1 上分配 2GB 内存。
--socket-limit <amounts of memory per socket>
,对每个 socket 的内存使用设置上限(仅限非传统内存模式)。例如,--socket-limit 2048,4096
将 socket 0 的内存使用限制为 2GB,socket 1 限制为 4GB。值为 0 表示对特定 socket 禁用限制。
--single-file-segments
,在 hugetlbfs 中创建更少的文件(仅限非传统模式)。这有助于减少 hugetlbfs 中的文件数量,并提高性能。
--huge-dir <path to hugetlbfs directory>
,使用指定的 hugetlbfs 目录,而不是自动检测的目录。例如,--huge-dir /mnt/huge2M
表示使用 /mnt/huge2M
作为 hugetlbfs 目录。
--huge-unlink[=existing|always|never]
,控制如何删除 hugepage 文件。默认行为是删除并重新创建现有的 hugepage 文件。--huge-unlink=always
表示总是删除 hugepage 文件。--huge-unlink=never
表示从不删除,而是重新映射,允许重用 hugepage。
--match-allocations
,按照原始分配的方式将 hugepage 释放回系统。这有助于避免内存碎片问题。
--proc-type <primary|secondary|auto>
,设置当前进程的类型。primary 表示主进程,secondary 表示从进程,auto 表示自动检测。
--base-virtaddr <address>
,尝试为主 DPDK 进程的所有内存映射使用不同的起始地址。当从进程由于地址映射冲突而无法启动时,这个选项很有帮助。例如,--base-virtaddr 0x7f000000000
将起始虚拟地址设置为 0x7f000000000。
5. 附录
5.1 DPDK运行CPU
DPDK应用程序通过EAL(Environment Abstraction Layer)管理和配置逻辑核心。coremask (-c 0x0f)
或corelist (-l 0-3)
参数用于指定应用程序使用的逻辑核,每个参数位对应一个逻辑核号。在应用程序初始化时,EAL会显示使用的逻辑核及其所在的物理CPU socket。了解逻辑核与物理CPU之间的映射关系,有助于优化NUMA架构下的性能。
DPDK应用程序采用轮询而非中断模式处理数据包,因此需要专门的worker逻辑核。除worker核外,EAL还使用一个master逻辑核做管理任务,master核通常与worker核分开,以免影响性能。
在高性能场景下,如OVS-DPDK,通常会将整个逻辑核专门绑定给DPDK使用,而不与其他程序共享,以最小化上下文切换开销,实现最优数据面性能。这种使用独占逻辑核的方式在生产中非常普遍。
5.2 DPDK运行内存
DPDK应用程序在启动时可以通过-m
或--socket-mem
参数来指定使用的内存大小。如果没有显式传递这些参数,DPDK会自动使用与hugepage分配的内存大小相同的内存。
如果显式传递了-m
或--socket-mem
参数,但请求的内存大小超过了预留的hugepage内存,应用程序会启动失败。另一方面,如果请求的内存小于预留的hugepage内存,尤其是使用-m
选项时,应用程序也可能会失败。假设系统在socket 0
和socket 1
上分别预留了1024个2MB的hugepage。如果用户请求128MB内存,这64个页面可能无法满足以下约束:
-
内核可能只在socket 1上给应用程序分配hugepage内存。在这种情况下,如果应用程序尝试在socket 0上创建对象(如ring或memory pool),就会失败。
-
这些页面可以位于物理内存中的任何位置,尽管DPDK EAL会尝试以连续块的方式分配内存,但这些页面可能不是连续的。在这种情况下,应用程序无法分配大的内存池。
为了避免上述问题,建议使用--socket-mem
选项而不是-m
选项。–socket-mem选项可用于为特定的socket请求指定数量的内存。
- 例如,
--socket-mem=0,512
表示仅为socket 1保留512MB内存。 - 在四个socket的系统上,要在socket 0和socket 2上各分配1GB内存,可以使用参数
--socket-mem=1024,0,1024
。未显式引用的CPU socket(如本例中的socket 3)不会预留任何内存。
如果DPDK无法在每个socket上分配足够的内存,EAL初始化将失败。
5.3 dpdk遥测功能
DPDK 的遥测功能允许收集应用程序运行时的各种指标和事件,以便进行性能分析和优化。要使用遥测功能,需要执行以下步骤:
-
配置遥测,在应用程序启动时,DPDK 会自动启用遥测功能。可以通过环境变量或配置文件来调整遥测的行为,例如指定输出的目标(如文件或套接字)、采样频率等。
-
编写遥测点,在 DPDK 应用程序代码中,可以使用 DPDK 提供的 API 函数来记录遥测点。
例如使用
rte_telemetry_register_callback()
函数注册一个回调函数,当特定的遥测事件发生时,该函数将被调用。 -
运行应用程序,启动 DPDK 应用程序,遥测功能将在后台收集指标和事件。
-
收集和分析遥测数据,DPDK 提供了工具来帮助收集和可视化遥测数据。例如,
dpdk-telemetry-client
工具可以连接到正在运行的 DPDK 应用程序,并实时显示遥测指标。还可以将遥测数据导出到文件或其他工具进行进一步分析。
Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!
(。◕‿◕。)感谢您的阅读与支持~~~