PORT HOTPLUG FRAMEWORK
端口热插拔框架为DPDK应用程序提供在运行时附加和分离端口的能力。由于该框架依赖于PMD实现,PMD无法处理的端口超出了该框架的范围。此外,在从DPDK应用程序分离端口后,该框架不提供从系统中移除设备的方法。对于由物理网卡支持的端口,内核需要支持PCI热插拔功能。
26.1 概述
端口热插拔框架的基本要求包括:
- 使用端口热插拔框架的DPDK应用程序必须管理自己的端口。端口热插拔框架的实现允许DPDK应用程序管理端口。例如,当DPDK应用程序调用端口附加函数时,将返回已附加的端口号。DPDK应用程序还可以通过端口号分离端口。
- 附加或分离物理设备端口需要内核支持。要附加新的物理设备端口,首先设备将在内核中被用户空间驱动I/O框架识别。然后DPDK应用程序可以调用端口热插拔函数来附加端口。对于分离,步骤相反。
- 分离之前,端口必须停止和关闭。DPDK应用程序在分离端口之前必须调用
rte_eth_dev_stop()
和rte_eth_dev_close()
API。这些函数将启动PMD的最终化序列。 - 该框架不影响传统的DPDK应用程序行为。如果未调用端口热插拔函数,则所有传统的DPDK应用程序仍可在不进行修改的情况下正常工作。
26.2 端口热插拔API概述
- 附加端口
rte_eth_dev_attach()
API将端口附加到DPDK应用程序,并返回已附加的端口号。在调用API之前,设备应该被用户空间驱动I/O框架识别。该API接收类似“0000:01:00.0”这样的PCI地址或者类似“eth_pcap0,iface=eth0”这样的虚拟设备名称。对于虚拟设备名称,格式与DPDK的一般“–vdev”选项相同。 - 分离端口
rte_eth_dev_detach()
API从DPDK应用程序分离端口,并返回分离设备的PCI地址或设备的虚拟设备名称。
26.3 参考
“testpmd”支持端口热插拔框架。
26.4 限制
- 端口热插拔API不具备线程安全性。
- 该框架仅在Linux下可用,不支持BSD。
- 要分离端口,端口应由igb_uio管理的设备支持。不支持VFIO。
- 并非所有PMD都支持分离功能。要了解PMD是否支持分离,请搜索PMD实现中的“RTE_PCI_DRV_DETACHABLE”标志。如果PMD中定义了该标志,则支持分离功能。
SOURCE ORGANIZATION
此部分描述了DPDK框架中源代码的组织结构。
27.1 Makefiles 和 Config
注意:在下面的描述中,RTE_SDK 是一个环境变量,指向解压缩tar包的基本目录。请参阅构建系统提供的有用变量的描述,了解其他变量的说明。
DPDK库和应用程序提供的 Makefiles 位于 $(RTE_SDK)/mk。
Config 模板位于 $(RTE_SDK)/config。这些模板描述了每个目标启用的选项。配置文件还包含许多DPDK库的可启用和禁用项,包括调试选项。用户应查看配置文件,并熟悉其中的选项。配置文件还用于创建一个头文件,该头文件将位于新的构建目录中。
27.2 Libraries
库位于 $(RTE_SDK)/lib 的子目录中。按照惯例,我们将提供应用程序接口(API)的代码称为库。通常,它会生成一个存档文件(.a),但内核模块也应该放在同一个目录下。
lib 目录包含:
lib
+-- librte_cmdline # command line interface helper
+-- librte_distributor # packet distributor
+-- librte_eal # environment abstraction layer
+-- librte_ether # generic interface to poll mode driver
+-- librte_hash # hash library
+-- librte_ip_frag # IP fragmentation library
+-- librte_ivshmem # QEMU IVSHMEM library
+-- librte_kni # kernel NIC interface
+-- librte_kvargs # argument parsing library
+-- librte_lpm # longest prefix match library
+-- librte_malloc # malloc-like functions
+-- librte_mbuf # packet and control mbuf manipulation library
+-- librte_mempool # memory pool manager (fixedsized objects)
+-- librte_meter # QoS metering library
+-- librte_net # various IP-related headers
+-- librte_pmd_bond # bonding poll mode driver
+-- librte_pmd_e1000 # 1GbE poll mode drivers (igb and em)
+-- librte_pmd_fm10k # Host interface PMD driver for FM10000 Series
+-- librte_pmd_ixgbe # 10GbE poll mode driver
+-- librte_pmd_i40e # 40GbE poll mode driver
+-- librte_pmd_mlx4 # Mellanox ConnectX-3 poll mode driver
+-- librte_pmd_pcap # PCAP poll mode driver
+-- librte_pmd_ring # ring poll mode driver
+-- librte_pmd_virtio # virtio poll mode driver
+-- librte_pmd_vmxnet3 # VMXNET3 poll mode driver
+-- librte_pmd_xenvirt # Xen virtio poll mode driver
+-- librte_power # power management library
+-- librte_ring # software rings (act as lockless FIFOs)
+-- librte_sched # QoS scheduler and dropper library
+-- librte_timer # timer library
27.3 Applications
应用程序是包含 main() 函数的源代码。它们位于 $(RTE_SDK)/app 和 $(RTE_SDK)/examples 目录中。
app 目录包含用于测试DPDK的示例应用程序(自动测试)。
examples 目录包含展示如何使用库的示例应用程序。
app
+-- chkincs # test prog to check include depends
+-- test # autotests, to validate DPDK features
+-- test-pmd # test and bench poll mode driver examples
examples
+-- cmdline # Example of using cmdline library
+-- dpdk_qat # Example showing integration with Intel QuickAssist
+-- exception_path # Sending packets to and from Linux Ethernet device (TAP)
+-- helloworld # Helloworld basic example
+-- ip_reassembly # Example showing IP Reassembly
+-- ip_fragmentation # Example showing IPv4 Fragmentation
+-- ipv4_multicast # Example showing IPv4 Multicast
+-- kni # Kernel NIC Interface example
+-- l2fwd # L2 Forwarding example with and without SR-IOV
+-- l3fwd # L3 Forwarding example
+-- l3fwd-power # L3 Forwarding example with power management
+-- l3fwd-vf # L3 Forwarding example with SR-IOV
+-- link_status_interrupt # Link status change interrupt example
+-- load_balancer # Load balancing across multiple cores/sockets
+-- multi_process # Example applications with multiple DPDK processes
+-- qos_meter # QoS metering example
+-- qos_sched # QoS scheduler and dropper example
+-- timer # Example of using librte_timer library
+-- vmdq_dcb # Intel 82599 Ethernet Controller VMDQ and DCB receiving
+-- vmdq # Example of VMDQ receiving
+-- vhost # Example of userspace vhost and switch
注意:实际的 examples 目录可能包含除上述示例之外的其他示例应用程序。请查看最新的DPDK源文件以了解详情。
DEVELOPMENT KIT BUILD SYSTEM
DPDK需要一个构建系统来进行编译等活动。本部分描述了DPDK框架中使用的约束和机制。
该框架有两个用例:
• 编译DPDK库和示例应用程序;该框架生成特定的二进制库、包含文件和示例应用程序。
• 使用已安装的二进制DPDK,编译外部应用程序或库。
28.1 构建开发套件二进制
以下提供了如何构建DPDK二进制的详细信息。
28.1.1 构建目录概念
在安装后,会创建一个构建目录结构。每个构建目录包含包含文件、库和应用程序:
~/DPDK$ ls
app MAINTAINERS
config Makefile
COPYRIGHT mk
doc scripts
examples lib
tools x86_64-native-linuxapp-gcc
x86_64-native-linuxapp-icc i686-native-linuxapp-gcc
i686-native-linuxapp-icc
...
~/DEV/DPDK$ ls i686-native-linuxapp-gcc
app build hostapp include kmod lib Makefile
~/DEV/DPDK$ ls i686-native-linuxapp-gcc/app/
cmdline_test dump_cfg test testpmd
cmdline_test.map dump_cfg.map test.map
testpmd.map
~/DEV/DPDK$ ls i686-native-linuxapp-gcc/lib/
libethdev.a librte_hash.a librte_mbuf.a librte_pmd_ixgbe.a
librte_cmdline.a librte_lpm.a librte_mempool.a librte_ring.a
librte_eal.a librte_malloc.a librte_pmd_e1000.a librte_timer.a
~/DEV/DPDK$ ls i686-native-linuxapp-gcc/include/
arch rte_cpuflags.h rte_memcpy.h
cmdline_cirbuf.h rte_cycles.h rte_memory.h
cmdline.h rte_debug.h rte_mempool.h
cmdline_parse_etheraddr.h rte_eal.h rte_memzone.h
cmdline_parse.h rte_errno.h rte_pci_dev_ids.h
cmdline_parse_ipaddr.h rte_ethdev.h rte_pci.h
cmdline_parse_num.h rte_ether.h rte_per_lcore.h
cmdline_parse_portlist.h rte_fbk_hash.h rte_prefetch.h
cmdline_parse_string.h rte_hash_crc.h rte_random.h
cmdline_rdline.h rte_hash.h rte_ring.h
cmdline_socket.h rte_interrupts.h rte_rwlock.h
cmdline_vt100.h rte_ip.h rte_sctp.h
exec-env rte_jhash.h rte_spinlock.h
rte_alarm.h rte_launch.h rte_string_fns.h
rte_atomic.h rte_lcore.h rte_tailq.h
rte_branch_prediction.h rte_log.h rte_tcp.h
rte_byteorder.h rte_lpm.h rte_timer.h
rte_common.h rte_malloc.h rte_udp.h
rte_config.h rte_mbuf.h
构建目录特定于配置,包括架构 + 执行环境 + 工具链。可以有多个构建目录共享相同的源文件,但具有不同的配置。
例如,要使用默认配置模板config/defconfig_x86_64-linuxapp创建一个名为my_sdk_build_dir的新构建目录,可以执行以下操作:
cd ${RTE_SDK}
make config T=x86_64-native-linuxapp-gcc O=my_sdk_build_dir
这将创建一个新的 my_sdk_build_dir 目录。 之后,我们可以通过执行以下操作进行编译:
cd my_sdk_build_dir
make
这相当于:
make O=my_sdk_build_dir
my_sdk_build_dir 的内容是:
28.2 编译外部应用程序
由于DPDK本质上是一个开发套件,最终用户的首要目标将是使用此SDK创建应用程序。要编译一个应用程序,用户必须设置 RTE_SDK 和 RTE_TARGET 环境变量。
export RTE_SDK=/opt/DPDK
export RTE_TARGET=x86_64-native-linuxapp-gcc
cd /path/to/my_app
对于新应用程序,用户必须创建自己的Makefile,其中包含一些.mk文件,如 ${RTE_SDK}/mk/rte.vars.mk 和 ${RTE_SDK}/mk/rte.app.mk。这在"构建您自己的应用程序"中有描述。
根据在Makefile中定义的或作为环境变量定义的选择的目标(架构、机器、执行环境、工具链),应用程序和库将使用适当的.h文件进行编译,并链接适当的.a文件。这些文件位于 ${RTE_SDK}/arch-machine-execenv-toolchain 中,内部由 ${RTE_BIN_SDK} 引用。
要编译他们的应用程序,用户只需调用 make。编译结果将位于 /path/to/my_app/build 目录中。
示例应用程序位于 examples 目录中。
28.3 Makefile描述
28.3.1 DPDK Makefile 的通用规则
在DPDK中,Makefiles 总是遵循相同的方案:
- 在开头包含 $(RTE_SDK)/mk/rte.vars.mk。
- 定义 RTE 构建系统的特定变量。
- 包含特定的 $(RTE_SDK)/mk/rte.XYZ.mk,其中 XYZ 可以是 app、lib、extapp、extlib、obj、gnuconfigure 等,取决于您想构建的对象类型。请参见下面的Makefile类型。
- 包含用户定义的规则和变量。
以下是一个非常简单的外部应用程序Makefile示例:
include $(RTE_SDK)/mk/rte.vars.mk
# binary name
APP = helloworld
# all source are stored in SRCS-y
SRCS-y := main.c
CFLAGS += -O3
CFLAGS += $(WERROR_FLAGS)
include $(RTE_SDK)/mk/rte.extapp.mk
28.3.2 Makefile 类型
根据用户Makefile末尾包含的 .mk 文件不同,Makefile 将具有不同的作用。请注意,不可能在同一个Makefile中构建库和应用程序。对此,用户必须创建两个单独的Makefile,可能位于两个不同的目录中。
无论如何,用户Makefile中必须尽快包含 rte.vars.mk 文件。
应用程序
这些Makefiles生成二进制应用程序。
• rte.app.mk:在开发套件框架中的应用程序
• rte.extapp.mk:外部应用程序
• rte.hostapp.mk:开发套件框架中的主机应用程序
库
生成 .a 库。
• rte.lib.mk:开发套件框架中的库
• rte.extlib.mk:外部库
• rte.hostlib.mk:开发套件框架中的主机库
安装
• rte.install.mk:不构建任何内容,仅用于在安装目录中创建链接或复制文件。这对包含开发套件框架中的文件很有用。
内核模块
• rte.module.mk:在开发套件框架中构建内核模块。
对象
• rte.obj.mk:对象聚合(将多个 .o 合并为一个)在开发套件框架中。
• rte.extobj.mk:对象聚合(将多个 .o 合并为一个)在开发套件框架外部。
其他
• rte.doc.mk:开发套件框架中的文档
• rte.gnuconfigure.mk:构建基于 configure 的应用程序。
• rte.subdir.mk:在开发套件框架中构建多个目录。
28.3.3 构建系统提供的有用变量
• RTE_SDK:DPDK源文件的绝对路径。在编译开发套件时,该变量由框架自动设置。如果编译外部应用程序,则必须由用户定义为环境变量。
• RTE_SRCDIR:源文件根目录的路径。在编译开发套件时,RTE_SRCDIR = RTE_SDK。在编译外部应用程序时,该变量指向外部应用程序源文件的根目录。
• RTE_OUTPUT:写入输出文件的路径。通常是 $(RTE_SRCDIR)/build,但可以通过 make 命令行中的 O= 选项进行覆盖。
• RTE_TARGET:标识正在构建的目标的字符串。格式为架构-机器-执行环境-工具链。在编译SDK时,目标由配置文件(.config)的构建系统推导出来。在构建外部应用程序时,用户必须在Makefile中或作为环境变量指定。
• RTE_SDK_BIN:引用
(
R
T
E
S
D
K
)
/
(RTE_SDK)/
(RTESDK)/(RTE_TARGET)。
• RTE_ARCH:定义架构(i686、x86_64)。与 CONFIG_RTE_ARCH 相同,但字符串周围没有双引号。
• RTE_MACHINE:定义机器。与 CONFIG_RTE_MACHINE 相同,但字符串周围没有双引号。
• RTE_TOOLCHAIN:定义工具链(gcc、icc)。与 CONFIG_RTE_TOOLCHAIN 相同,但字符串周围没有双引号。
• RTE_EXEC_ENV:定义执行环境(linuxapp)。与 CONFIG_RTE_EXEC_ENV 相同,但字符串周围没有双引号。
• RTE_KERNELDIR:该变量包含用于编译内核模块的内核源文件的绝对路径。内核头文件必须与将在目标机器(运行应用程序的机器)上使用的头文件相同。默认情况下,该变量设置为 /lib/modules/$(shell uname -r)/build,这在目标机器也是构建机器时是正确的。
28.3.4 只能在Makefile中设置/覆盖的变量
- VPATH: 构建系统将搜索源文件的路径列表。默认情况下,RTE_SRCDIR 将包含在 VPATH 中。
- CFLAGS: 用于C编译的标志。用户应该使用 += 来追加此变量中的数据。
- LDFLAGS: 用于链接的标志。用户应该使用 += 来追加此变量中的数据。
- ASFLAGS: 用于汇编的标志。用户应该使用 += 来追加此变量中的数据。
- CPPFLAGS: 用于给C预处理器传递标志(仅在汇编 .S 文件时有用)。用户应该使用 += 来追加此变量中的数据。
- LDLIBS: 在应用程序中,要链接的库列表(例如,-L /path/to/libfoo -lfoo)。用户应该使用 += 来追加此变量中的数据。
- SRC-y: 应用程序、库或对象Makefile中源文件的列表(.c、.S 或 .o,如果源文件是二进制文件)。这些源文件必须从 VPATH 中获取。
- INSTALL-y-$(INSTPATH): 要安装到 $(INSTPATH) 的文件列表。这些文件必须从 VPATH 中获取,并将被复制到 ( R T E O U T P U T ) / (RTE_OUTPUT)/ (RTEOUTPUT)/(INSTPATH)。几乎可以在任何 RTE Makefile 中使用。
- SYMLINK-y-$(INSTPATH): 要安装到 $(INSTPATH) 的文件列表。这些文件必须从 VPATH 中获取,并将以符号链接的方式链接到 ( R T E O U T P U T ) / (RTE_OUTPUT)/ (RTEOUTPUT)/(INSTPATH)。几乎可以在任何 DPDK Makefile 中使用。
- PREBUILD: 在主要构建之前执行的先决条件操作的列表。用户应该使用 += 来追加此变量中的数据。
- POSTBUILD: 在主要构建之后执行的操作的列表。用户应该使用 += 来追加此变量中的数据。
- PREINSTALL: 在安装之前执行的先决条件操作的列表。用户应该使用 += 来追加此变量中的数据。
- POSTINSTALL: 在安装之后执行的操作的列表。用户应该使用 += 来追加此变量中的数据。
- PRECLEAN: 在清理之前执行的先决条件操作的列表。用户应该使用 += 来追加此变量中的数据。
- POSTCLEAN: 在清理之后执行的操作的列表。用户应该使用 += 来追加此变量中的数据。
- DEPDIR-y: 仅在开发套件框架中使用,指定当前目录的构建是否依赖于另一个目录的构建。这是为了正确支持并行构建。
28.3.5 只能由用户在命令行中设置/覆盖的变量
一些变量可用于配置构建系统的行为。这些变量在“Development Kit Root Makefile Help”和“External Application/Library Makefile Help”中有文档记录。
- WERROR_CFLAGS: 默认情况下,此变量设置为取决于编译器的特定值。鼓励用户按照以下方式使用此变量:
这避免了根据编译器(icc 或 gcc)使用不同情况。此外,此变量可以从命令行覆盖,以便在测试目的时绕过标志。CFLAGS += $(WERROR_CFLAGS)
28.3.6 可在Makefile或命令行中设置/覆盖的变量
- CFLAGS_my_file.o: 用于 my_file.c 的 C 编译的特定标志。
- LDFLAGS_my_app: 在链接 my_app 时添加的特定标志。
- NO_AUTOLIBS: 如果设置,框架提供的库将不会自动包含在 LDLIBS 变量中。
- EXTRA_CFLAGS: 在编译时将此变量的内容追加到 CFLAGS 之后。
- EXTRA_LDFLAGS: 在链接时将此变量的内容追加到 LDFLAGS 之后。
- EXTRA_ASFLAGS: 在汇编时将此变量的内容追加到 ASFLAGS 之后。
- EXTRA_CPPFLAGS: 在汇编文件上使用 C 预处理器时将此变量的内容追加到 CPPFLAGS 之后。
29.1 配置目标
配置目标需要指定目标的名称,使用 T=mytarget 参数是必需的。可用的目标列表位于 $(RTE_SDK)/config(去掉 defconfig_ 前缀)。
配置目标还支持指定输出目录的名称,使用 O=mybuilddir。这是一个可选参数,默认输出目录是 build。
- Config
创建一个构建目录,并从模板生成配置。在新的构建目录中也创建一个 Makefile。
示例:make config O=mybuild T=x86_64-native-linuxapp-gcc
29.2 构建目标
构建目标支持指定输出目录的名称,使用 O=mybuilddir。默认输出目录是 build。
-
all, build 或仅 make
在之前由 make config 创建的输出目录中构建 DPDK。
示例:make O=mybuild
-
clean
清除使用 make build 创建的所有对象。
示例:make clean O=mybuild
-
%_sub
仅构建一个子目录,而不管理其他目录的依赖关系。
示例:make lib/librte_eal_sub O=mybuild
-
%_clean
仅清除一个子目录。
示例:make lib/librte_eal_clean O=mybuild
29.3 安装目标
-
Install
构建 DPDK 二进制文件。实际上,这会将每个支持的目标构建到单独的目录中。每个目录的名称是目标的名称。可以选择性地使用 T=mytarget 指定要安装的目标名称。目标名称可以包含通配符 * 字符。可用的目标列表位于 $(RTE_SDK)/config(删除 defconfig_ 前缀)。
示例:make install T=x86_64-*
-
Uninstall
移除已安装的目标目录。
29.4 测试目标
-
test
启动指定使用 O=mybuilddir 的构建目录的自动测试。这是可选的,默认输出目录是 build。
示例:make test O=mybuild
-
testall
启动所有已安装目标目录(在 make install 之后)的自动测试。可以选择性地使用 T=mytarget 指定要测试的目标名称。目标名称可以包含通配符 * 字符。可用的目标列表位于 $(RTE_SDK)/config(删除 defconfig_ 前缀)。
示例:make testall, make testall T=x86_64-*
29.5 文档目标
- doc
生成 Doxygen 文档(API、html 和 pdf)。 - doc-api-html
生成 html 格式的 Doxygen API 文档。 - doc-guides-html
生成 html 格式的指南文档。 - doc-guides-pdf
生成 pdf 格式的指南文档。
29.6 依赖目标
-
depdirs
此目标在 make config 时隐式调用。通常情况下,除非在 Makefiles 中更新了 DEPDIRS-y 变量,否则无需用户调用它。它将生成文件 $(RTE_OUTPUT)/.depdirs。
示例:make depdirs O=mybuild
-
depgraph
此命令生成依赖关系的 dot 图。它可以用于调试循环依赖问题,或者只是了解依赖关系。
示例:make depgraph O=mybuild > /tmp/graph.dot && dotty /tmp/graph.dot
29.7 其他目标
- help
显示此帮助信息。
29.8 其他有用的命令行变量
以下变量可以在命令行上指定:
- V=
启用详细构建(显示完整的编译命令行和一些中间命令)。 - D=
启用依赖项调试。这提供了一些有关为何构建或不构建目标的有用信息。 - EXTRA_CFLAGS=, EXTRA_LDFLAGS=, EXTRA_ASFLAGS=, EXTRA_CPPFLAGS=
追加特定的编译、链接或汇编标志。 - CROSS=
指定一个交叉工具链头部,它将作为前缀用于所有 gcc/binutils 应用程序。这仅在使用 gcc 时有效。
29.9 在构建目录中使用 Make
在 SDK 根目录 $(RTE_SDK) 中调用了上述所有目标。可以在构建目录内部运行相同的 Makefile 目标。例如,以下命令:
cd $(RTE_SDK)
make config O=mybuild T=x86_64-native-linuxapp-gcc
make O=mybuild
等同于:
cd $(RTE_SDK)
make config O=mybuild T=x86_64-native-linuxapp-gcc
cd mybuild
# 现在不需要再指定 O= now
make
29.10 编译调试版本
要在 DPDK 和示例应用程序中包含调试信息并将优化级别设置为 0 进行编译,应在编译之前设置 EXTRA_CFLAGS 环境变量,如下所示:
export EXTRA_CFLAGS='-O0 -g'
然后可以以通常的方式编译 DPDK 以及任何用户或示例应用程序。例如:
make install T=x86_64-native-linuxapp-gcc
make -C examples/<theapp>
扩展 DPDK
本章描述了开发人员如何扩展 DPDK 以提供新的库、新的目标或支持新的目标。
30.1 示例:添加一个新库 libfoo
要向 DPDK 中添加新库,请按照以下步骤进行:
- 添加新的配置选项:
for f in config/*; do \
echo CONFIG_RTE_LIBFOO=y >> $f; done
1、创建带有源代码的新目录:
mkdir ${RTE_SDK}/lib/libfoo
touch ${RTE_SDK}/lib/libfoo/foo.c
touch ${RTE_SDK}/lib/libfoo/foo.h
2、在 libfoo 中添加 foo() 函数:
void foo(void)
{
}
在 foo.h 中声明:
extern void foo(void);
3、更新 lib/Makefile:
vi ${RTE_SDK}/lib/Makefile
添加:
DIRS-$(CONFIG_RTE_LIBFOO) += libfoo
4、为此库创建新的 Makefile(从 mempool Makefile 派生):
cp ${RTE_SDK}/lib/librte_mempool/Makefile ${RTE_SDK}/lib/libfoo/
vi ${RTE_SDK}/lib/libfoo/Makefile
替换:
librte_mempool -> libfoo
rte_mempool -> foo
5、更新 mk/DPDK.app.mk:
如果启用了该选项,则在 LDLIBS 变量中添加 -lfoo。这将在链接 DPDK 应用程序时自动包含此标志。
6、使用新库构建 DPDK:
cd ${RTE_SDK}
make config T=x86_64-native-linuxapp-gcc
make
7、检查库的安装情况:
ls build/lib
ls build/include
30.1.1 示例:在测试应用程序中使用 libfoo
测试应用程序用于验证 DPDK 的所有功能。一旦您添加了一个库,就应该在测试应用程序中添加一个新的测试用例。
- 应添加一个新的 test_foo.c 文件,其中包括 foo.h 并从 test_foo() 中调用 foo() 函数。当测试通过时,test_foo() 函数应返回 0。
- Makefile、test.h 和 commands.c 也必须进行更新,以处理新的测试用例。
- 测试报告生成:autotest.py 是一个用于生成测试报告的脚本,位于 ${RTE_SDK}/doc/rst/test_report/autotests 目录中。还需要更新此脚本。如果 libfoo 属于新的测试系列,则必须更新 ${RTE_SDK}/doc/rst/test_report/test_report.rst 中的链接。
- 使用更新后的测试应用程序构建 DPDK(以下仅显示特定目标):
cd ${RTE_SDK}
make config T=x86_64-native-linuxapp-gcc
make
构建你自己的应用程序
31.1 在开发工具包目录中编译示例应用程序
当编译示例应用程序(例如,hello world)时,必须导出以下变量:RTE_SDK 和 RTE_TARGET。
~/DPDK$ cd examples/helloworld/
~/DPDK/examples/helloworld$ export RTE_SDK=/home/user/DPDK
~/DPDK/examples/helloworld$ export RTE_TARGET=x86_64-native-linuxapp-gcc
~/DPDK/examples/helloworld$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
默认情况下,二进制文件生成在 build 目录下:
~/DPDK/examples/helloworld$ ls build/app
helloworld helloworld.map
31.2 在开发工具包之外构建你自己的应用程序
将示例应用程序(Hello World)复制到一个新目录,作为开发的起点:
~$ cp -r DPDK/examples/helloworld my_rte_app
~$ cd my_rte_app/
~/my_rte_app$ export RTE_SDK=/home/user/DPDK
~/my_rte_app$ export RTE_TARGET=x86_64-native-linuxapp-gcc
~/my_rte_app$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
自定义 Makefile
31.3.1 应用程序 Makefile
提供的 Hello World 示例应用程序的默认 Makefile 是一个很好的起点。
它包含以下内容:
在开头包含了 $(RTE_SDK)/mk/rte.vars.mk
在结尾包含了 $(RTE_SDK)/mk/rte.extapp.mk
用户需要定义一些变量:
APP:包含应用程序的名称。
SRCS-y:源文件列表(.c,.S)。
31.3.2 库 Makefile
同样也可以以相同的方式构建一个库:
在开头包含 $(RTE_SDK)/mk/rte.vars.mk。
在结尾包含 $(RTE_SDK)/mk/rte.extlib.mk。
唯一的区别是 APP 应该替换为 LIB,其中包含库的名称。例如,libfoo.a。
31.3.3 自定义 Makefile 操作
一些变量可以被定义来自定义 Makefile 操作。以下是最常见的变量。有关详细信息,请参阅“开发工具包构建系统”章节中的“Makefile 描述”部分。
- VPATH:构建系统搜索源文件的路径列表。默认情况下,RTE_SRCDIR 将包含在 VPATH 中。
- CFLAGS_my_file.o:对 my_file.c 进行 C 编译的特定标志。
- CFLAGS:用于 C 编译的标志。
- LDFLAGS:用于链接的标志。
- CPPFLAGS:用于向 C 预处理器提供标志的标志(仅在汇编 .S 文件时有用)。
- LDLIBS:要链接的库列表(例如,-L /path/to/libfoo - lfoo)。
- NO_AUTOLIBS:如果设置,则框架提供的库不会自动包含在 LDLIBS 变量中。
请根据需要自行添加翻译或额外说明。
外部应用程序/库的 Makefile 帮助
外部应用程序或库应包含来自 RTE_SDK 中的特定 Makefile,位于 mk 目录中。这些 Makefile 是:
- ${RTE_SDK}/mk/rte.extapp.mk:构建应用程序
- ${RTE_SDK}/mk/rte.extlib.mk:构建静态库
- ${RTE_SDK}/mk/rte.extobj.mk:构建对象(.o)
32.1 先决条件
必须定义以下变量:
- ${RTE_SDK}:指向 DPDK 的根目录。
- ${RTE_TARGET}:引用用于编译的目标(例如,x86_64-native-linuxapp-gcc)。
32.2 构建目标
构建目标支持指定输出目录的名称,使用 O=mybuilddir。这是可选的;默认输出目录为 build。
- all,“nothing”(仅表示 make)
在指定的输出目录中构建应用程序或库。
示例:
make O=mybuild
- clean
清理使用 make build 创建的所有对象。
示例:
make clean O=mybuild
32.3 帮助目标
- help
显示此帮助信息。
32.4 其他有用的命令行变量
以下变量可以在命令行指定:
- S=
指定源文件所在的目录。默认情况下,它是当前目录。 - M=
指定一旦创建输出目录后要调用的 Makefile。默认情况下,它使用 $(S)/Makefile。 - V=
启用详细构建(显示完整的编译命令行和一些中间命令)。 - D=
启用依赖关系调试。这提供了一些关于为什么需要重建目标的有用信息。
EXTRA_CFLAGS=、EXTRA_LDFLAGS=、EXTRA_ASFLAGS=、EXTRA_CPPFLAGS=
追加特定的编译、链接或汇编标志。 - CROSS=
指定一个用于所有 gcc/binutils 应用程序的交叉工具链标头。这仅在使用 gcc 时有效。
32.5 从其他目录运行 Make
可以通过指定输出目录和源目录来从另一个目录运行 Makefile。例如:
export RTE_SDK=/path/to/DPDK
export RTE_TARGET=x86_64-native-linuxapp-icc
make -f /path/to/my_app/Makefile S=/path/to/my_app O=/path/to/build_dir
33.1 引言
以下部分描述了 DPDK 中使用的优化以及新应用程序应考虑的优化方法。
它们还强调了在使用 DPDK 开发应用程序时应该和不应该使用的对性能影响较大的编码技术。
最后,它们介绍了使用英特尔的性能分析器进行应用程序分析以优化软件的方法。
编写高效代码
本章提供了一些使用 DPDK 开发高效代码的技巧。欲获取更多通用信息,请参考英特尔® 64 和 IA-32 架构优化参考手册,这是编写高效代码的宝贵参考资料。
34.1 内存
本节描述了在 DPDK 环境中开发应用程序时的一些关键内存注意事项。
34.1.1 内存拷贝:在数据平面不要使用libc
在 DPDK 中通过 Linux* 应用环境可以使用许多 libc 函数,这可以简化应用程序的移植和配置平面的开发。然而,许多这些函数并非为性能而设计。像 memcpy() 或 strcpy() 这样的函数不应该在数据平面中使用。对于拷贝小结构体,最好使用编译器可以优化的更简单的技术。建议参考英特尔出版的 VTune™ Performance Analyzer Essentials 出版物获取建议。
对于频繁调用的特定函数,最好提供一个自制的经过优化的函数,应该声明为 static inline。
DPDK API 提供了一个经过优化的 rte_memcpy() 函数。
34.1.2 内存分配
libc 的其他函数,如 malloc(),提供了一种灵活的方式来分配和释放内存。在某些情况下,使用动态分配是必要的,但不建议在数据平面中使用类似 malloc 的函数,因为管理碎片化的堆可能成本高昂,而且分配器可能没有针对并行分配进行优化。
如果确实需要在数据平面中进行动态分配,最好使用固定大小对象的内存池。这个 API 由 librte_mempool 提供。这个数据结构提供了一些增加性能的服务,如对象的内存对齐、无锁访问对象、NUMA 感知、批量获取/放置和每个 lcore 的缓存。rte_malloc() 函数使用了与内存池类似的概念。
34.1.3 并发访问相同的内存区域
多个 lcore 对同一内存区域的读写访问操作可能会产生大量数据缓存未命中,这是非常昂贵的。通常可以使用每个 lcore 变量,例如统计数据。针对此问题至少有两种解决方案:
- 使用 RTE_PER_LCORE 变量。请注意,在这种情况下,lcore X 上的数据对于 lcore Y 不可用。
- 使用结构体表(每个 lcore 一个)。在这种情况下,每个结构体必须缓存对齐。
如果同一缓存行中没有读写变量,那么只读变量可以在多个 lcore 之间共享而不会造成性能损失。
34.1.4 NUMA
在 NUMA 系统上,最好访问本地内存,因为远程内存访问速度较慢。在 DPDK 中,memzone、ring、rte_malloc 和 mempool API 提供了在特定套接字上创建池的方法。
有时,复制数据以优化速度可能是个好主意。对于经常访问的只读变量,把它们仅保留在一个套接字中应该不是问题,因为数据将存在于缓存中。
34.1.5 跨内存通道分配
现代内存控制器具有多个内存通道,可以并行加载或存储数据。根据内存控制器及其配置,内存分布在通道上的方式各不相同。每个通道都有带宽限制,这意味着如果所有内存访问操作都只在第一个通道上进行,可能会存在潜在的瓶颈。
默认情况下,内存池库将对象的地址分布在内存通道之间。
34.2 lcore 之间的通信
为了在 lcore 之间提供基于消息的通信,建议使用 DPDK ring API,它提供了无锁环实现。
这个环支持批量和突发访问,意味着可以使用一个昂贵的原子操作从环中读取多个元素(参见第 5 章“环库”)。使用批量访问操作可以极大地提高性能。
出队消息的代码算法可能类似于以下内容:
#define MAX_BULK 32
while (1) {
/* Process as many elements as can be dequeued. */
count = rte_ring_dequeue_burst(ring, obj_table, MAX_BULK);
if (unlikely(count == 0))
continue;
my_process_bulk(obj_table, count);
}
34.3 PMD 驱动程序
DPDK Poll Mode Driver(PMD)也能以批量/突发模式工作,允许在发送或接收函数的每次调用中对一些代码进行因式分解。
避免部分写入。当 PCI 设备通过 DMA 写入系统内存时,如果写操作是在完整的缓存行上而不是部分缓存行上,成本就会较低。在 PMD 代码中,已经采取了尽可能避免部分写入的措施。
34.3.1 降低数据包延迟
传统上,吞吐量和延迟之间存在权衡。一个应用程序可以调整以实现高吞吐量,但平均数据包的端到端延迟通常会增加。同样地,该应用程序可以调整以实现平均较低的端到端延迟,但代价是较低的吞吐量。
为了实现更高的吞吐量,DPDK 试图通过批量处理每个数据包的成本来聚合。以 testpmd 应用程序为例,可以在命令行上将批量大小设置为 16(也是默认值)。这允许应用程序一次从 PMD 请求 16 个数据包。然后,testpmd 应用程序立即尝试传输所有接收到的数据包,即在本例中为所有 16 个数据包。
在网络端口的相应 TX 队列的尾指针更新之前,数据包不会被传输。在调整以实现高吞吐量时,这种行为是可取的,因为对 RX 和 TX 队列的尾指针更新的成本可以分摊到 16 个数据包上,有效地隐藏了相对较慢的 MMIO 写入 PCIe* 设备的成本。然而,当调整以实现低延迟时,这并不理想,因为第一个接收到的数据包必须等待其他 15 个数据包也被接收。在所有 16 个数据包都被传输进行处理之前,它无法被传输,因为网卡在 TX 尾指针更新之前不知道传输数据包。
为了在系统负载较重的情况下始终实现低延迟,应用程序开发人员应避免批量处理数据包。testpmd 应用程序可以从命令行配置为使用批量值为 1。这将允许一次处理一个数据包,提供较低的延迟,但代价是较低的吞吐量。
34.4 锁和原子操作
原子操作意味着在指令之前添加一个锁前缀,导致处理器在执行下一条指令期间断言 LOCK# 信号。在多核环境中,这对性能有很大影响。
通过避免在数据平面中使用锁机制可以提高性能。通常可以用其他解决方案代替,比如每个 lcore 变量。此外,某些锁定技术比其他技术更高效。例如,读-拷贝-更新(RCU)算法经常可以取代简单的读写锁。
34.5 编码注意事项
34.5.1 内联函数
小函数可以在头文件中声明为 static inline。这避免了调用指令的成本(以及相关的上下文保存)。然而,这种技术并不总是高效的;它取决于许多因素,包括编译器。
34.5.2 分支预测
英特尔® C/C++ 编译器(icc)/gcc 内置的辅助函数 likely() 和 unlikely() 允许开发人员指示代码分支可能被执行或不会被执行。例如:
if (likely(x > 1))
do_stuff();
34.6 设置目标CPU类型
DPDK通过DPDK配置文件中的CONFIG_RTE_MACHINE选项支持针对CPU微架构的特定优化。优化程度取决于编译器针对特定微架构的优化能力,因此尽可能使用最新的编译器版本是可取的。
如果编译器版本不支持特定的功能集(例如Intel® AVX指令集),则构建过程会优雅地降级到编译器支持的最新功能集。
由于构建和运行时目标可能不相同,因此生成的二进制文件还包含一个在main()函数之前运行的平台检查,检查当前机器是否适合运行该二进制文件。
除了编译器优化之外,一组预处理器定义会自动添加到构建过程中(不考虑编译器版本)。这些定义对应于目标CPU应该能够支持的指令集。例如,为任何支持SSE4.2的处理器编译的二进制文件将定义RTE_MACHINE_CPUFLAG_SSE4_2,从而为不同平台启用编译时代码路径选择。
对应用程序进行性能分析
英特尔处理器提供性能计数器来监视事件。英特尔提供的一些工具可用于对应用程序进行性能分析和基准测试。参阅英特尔出版的《VTune性能分析器Essentials》获取更多信息。
对于DPDK应用程序,这仅限于Linux*应用程序环境中进行。
通过事件计数器应该监控的主要情况包括:
- 缓存未命中
- 分支错误预测
- DTLB未命中
- 长延迟指令和异常
有关应用程序分析的详细信息,请参阅英特尔性能分析指南。
术语表
- ACL: 访问控制列表 (Access Control List)
- API: 应用程序编程接口 (Application Programming Interface)
- ASLR: Linux* 内核地址空间布局随机化 (Linux* kernel Address-Space Layout Randomization)
- BSD: 伯克利软件发行版 (Berkeley Software Distribution)
- Clr: 清除 (Clear)
- CIDR: 无类域间路由 (Classless Inter-Domain Routing)
- Control Plane: 控制平面,涉及数据包路由和提供起点或终点。
- Core: 核心,如果处理器支持超线程,则一个核心可能包含多个逻辑核心或线程。
- Core Components: DPDK 提供的一组库,包括 eal、ring、mempool、mbuf、timers 等。
- CPU: 中央处理器 (Central Processing Unit)
- CRC: 循环冗余校验 (Cyclic Redundancy Check)
- ctrlmbuf: 携带控制数据的 mbuf。
- Data Plane: 与控制平面相对应,在网络架构中用于转发数据包的层。这些层必须高度优化以获得良好性能。
- DIMM: 双列直插内存模块 (Dual In-line Memory Module)
- Doxygen: DPDK 中用于生成 API 参考的文档生成器。
- DPDK: 数据平面开发工具包 (Data Plane Development Kit)
- DRAM: 动态随机存取存储器 (Dynamic Random Access Memory)
- EAL: 环境抽象层 (Environment Abstraction Layer),为应用程序和库隐藏环境细节的通用接口。提供的服务包括:开发工具包加载和启动、核心亲和/分配过程、系统内存分配/描述、PCI 总线访问、分区间通信。
- FIFO: 先进先出 (First In First Out)
- FPGA: 现场可编程门阵列 (Field Programmable Gate Array)
- GbE: 千兆以太网 (Gigabit Ethernet)
- HW: 硬件 (Hardware)
- HPET: 高精度事件定时器 (High Precision Event Timer),在 x86 平台上提供精确的时间参考。
- ID: 标识符 (Identifier)
- IOCTL: 输入/输出控制 (Input/Output Control)
- I/O: 输入/输出 (Input/Output)
- IP: 互联网协议 (Internet Protocol)
- IPv4: 互联网协议版本 4 (Internet Protocol version 4)
- IPv6: 互联网协议版本 6 (Internet Protocol version 6)
- lcore: 处理器的逻辑执行单元,有时称为硬件线程。
- KNI: 内核网络接口 (Kernel Network Interface)
- L1: 第一层 (Layer 1)
- L2: 第二层 (Layer 2)
- L3: 第三层 (Layer 3)
- L4: 第四层 (Layer 4)
- LAN: 局域网 (Local Area Network)
- LPM: 最长前缀匹配 (Longest Prefix Match)
- master lcore: 执行主()函数并启动其他 lcore 的执行单元。
- mbuf: 一种用于内部携带消息(主要是网络数据包)的数据结构。名称来源于 BSD 栈。要了解数据包缓冲区或 mbuf 的概念,请参阅《TCP/IP 详解》第 2 卷:实现。
- MESI: 修改(Modified)、互斥(Exclusive)、共享(Shared)、失效(Invalid)(CPU 缓存一致性协议)
- MTU: 最大传输单元 (Maximum Transfer Unit)
- NIC: 网络接口卡 (Network Interface Card)
- OOO: 指令在 CPU 流水线内的乱序执行 (Out Of Order)
- NUMA: 非一致性内存访问 (Non-uniform Memory Access)
- PCI: 外设连接接口 (Peripheral Connect Interface)
- PHY: OSI 模型的物理层的缩写。
- pktmbuf: 携带网络数据包的 mbuf。
- PMD: 轮询模式驱动程序 (Poll Mode Driver)
- QoS: 服务质量 (Quality of Service)
- RCU: 读-拷贝-更新算法 (Read-Copy-Update),用于替代简单读写锁。
- Rd: 读取 (Read)
- RED: 随机早期检测 (Random Early Detection)
- RSS: 接收端缩放 (Receive Side Scaling)
- RTE: 运行时环境 (Run Time Environment),为快速数据包处理提供了一个快速简单的框架,作为 Linux* 应用程序的轻量环境,并使用轮询模式驱动程序(PMD)来提高速度。
- Rx: 接收 (Reception)
- Slave lcore: 不是主 lcore 的任何 lcore。
- Socket: 物理 CPU,包含多个核心。
- SLA: 服务等级协议 (Service Level Agreement)
- srTCM: 单速三色标记 (Single Rate Three Color Marking)
- SRTD: 调度器往返延迟 (Scheduler Round Trip Delay)
- SW: 软件 (Software)
- Target: 在 DPDK 中,目标是架构、机器、执行环境和工具链的组合。例如:i686-native-linuxapp-gcc。
- TCP: 传输控制协议 (Transmission Control Protocol)
- TC: 流量类别 (Traffic Class)
- TLB: 转换查找缓冲区 (Translation Lookaside Buffer)
- TLS: 线程本地存储 (Thread Local Storage)
- trTCM: 双速三色标记 (Two Rate Three Color Marking)
- TSC: 时间戳计数器 (Time Stamp Counter)
- Tx: 传输 (Transmission)
- TUN/TAP: TUN 和 TAP 是虚拟网络内核设备。
- VLAN: 虚拟局域网 (Virtual Local Area Network)
- Wr: 写入 (Write)
- WRED: 加权随机早期检测 (Weighted Random Early Detection)
- WRR: 加权轮询 (Weighted Round Robin)
术语表(续)
- Figures
- Fig. 2.1: 核心组件架构
- Fig. 3.1: 在Linux应用程序环境中的EAL初始化
- Fig. 4.1: malloc堆和malloc库中的malloc元素示例
- Fig. 5.1: 环形结构
- Fig. 5.2: 入队第一步
- Fig. 5.3: 入队第二步
- Fig. 5.4: 入队最后一步
- Fig. 5.5: 出队最后一步
- Fig. 5.6: 出队第二步
- Fig. 5.7: 出队最后一步
- Fig. 5.8: 多消费者入队第一步
- Fig. 5.9: 多消费者入队第二步
- Fig. 5.10: 多消费者入队第三步
- Fig. 5.11: 多消费者入队第四步
- Fig. 5.12: 多消费者入队最后一步
- Fig. 5.13: 32位模数索引 - 示例1
- Fig. 5.14: 32位模数索引 - 示例2
- Fig. 6.1: 两个通道和四排DIMM示例
- Fig. 6.2: 三个通道和两个双排DIMM示例
- Fig. 6.3: 存储器中的mempool及其关联的环
术语表(续)
-
Figures
- Fig. 7.1: 一个片段的mbuf
- Fig. 7.2: 三个片段的mbuf
- Fig. 18.1: DPDK多进程示例应用程序中的内存共享
- Fig. 19.1: DPDK KNI应用程序的组件
- Fig. 19.2: 通过mbufs进行的KNI数据包流
- Fig. 19.3: vHost-net架构概览
- Fig. 19.4: KNI流量流向
- Fig. 21.1: 具有QoS支持的复杂数据包处理流水线
- Fig. 21.2: 分层调度器块内部结构图
- Fig. 21.3: 每个端口的调度层次结构
- Fig. 21.4: 每个端口的内部数据结构
- Fig. 21.5: 分层调度器Enqueue操作的预取管道
- Fig. 21.6: 分层调度器Dequeue操作的管道预取状态机
- Fig. 21.7: DPDK Dropper的高级块图
- Fig. 21.8: Dropper的流程
- Fig. 21.9: Dropper中的数据流示例
- Fig. 21.10: 给定RED配置的数据包丢失概率
- Fig. 21.11: 使用因子1(蓝曲线)和因子2(红曲线)计算的初始丢包概率和实际丢包概率
-
Tables
- Table 21.1: 实施QoS的数据包处理流水线
- Table 21.2: 数据包处理流水线使用的基础结构块
- Table 21.3: 端口调度层次结构
- Table 21.4: 每个端口的调度器内部数据结构
- Table 21.5: 以太网帧开销字段
- Table 21.6: 令牌桶通用操作
- Table 21.7: 令牌桶通用参数
- Table 21.8: 令牌桶持久数据结构
- Table 21.9: 令牌桶操作
- Table 21.10: Subport/Pipe流量类别上限执行持久数据结构
- Table 21.11: Subport/Pipe流量类别上限执行操作
- Table 21.12: 加权轮询(WRR)
- Table 21.13: Subport流量类别过订阅
- Table 21.14: 每个流量类别上限执行周期开始时从子端口级别到成员Pipe的水印传播
- Table 21.15: 水印计算
- Table 21.16: RED配置参数
- Table 21.17: 替代方法的相对性能
- Table 21.18: 对应于RED配置文件的RED配置
术语表(续)
-
Figures
- Fig. 24.1: 网络处理流水线示例,其中输入端口0和1通过表0和1连接到输出端口0、1和2
- Fig. 24.2: 数据包处理上下文中哈希表操作的步骤序列
- Fig. 24.3: 可配置键大小哈希表的数据结构
- Fig. 24.4: 关键查找操作的桶搜索流水线(可配置键大小哈希表)
- Fig. 24.5: 8字节键哈希表的数据结构
- Fig. 24.6: 16字节键哈希表的数据结构
-
Tables
- Table 24.1: 端口类型
- Table 24.2: 20端口抽象接口
- Table 24.3: 表类型
- Table 24.5: 适用于所有哈希表类型的通用配置参数
- Table 24.6: 适用于可扩展桶哈希表的特定配置参数
- Table 24.7: 适用于预计算键签名哈希表的特定配置参数
- Table 24.8: 用于可配置键大小哈希表的主要大型数据结构(数组)
- Table 24.9: 桶数组条目的字段描述(可配置键大小哈希表)
- Table 24.10: 桶搜索流水线阶段描述(可配置键大小哈希表)
- Table 24.11: Match、Match_Many和Match_Pos的查找表
- Table 24.12: Match、Match_Many和Match_Pos的折叠查找表
- Table 24.13: 用于8字节和16字节键大小哈希表的主要大型数据结构(数组)
- Table 24.14: 桶数组条目的字段描述(8字节和16字节键哈希表)
- Table 24.15: 桶搜索流水线阶段描述(8字节和16字节键哈希表)
- Table 24.16: 下一跳操作(已保留)
- Table 24.17: 用户动作示例