DPDK概述

463 阅读15分钟

DPDK(Data Plane Development Kit)

用于快速数据包处理的函数库与驱动集合,可以极大提高数据处理性能和吞吐量,提高数据平面应用程序的工作效率。DPDK使用了轮询(polling)而不是中断来处理数据包。在收到数据包时,经DPDK重载的网卡驱动不会通过中断通知CPU,而是直接将数据包存入内存,交付应用层软件通过DPDK提供的接口来直接处理,这样节省了大量的CPU中断时间和内存拷贝时间。

  • EAL(Environment Abstraction Layer):DPDK的创造的环境抽象层(EAL, Environment Abstraction Layer)主要负责对计算机底层资源(如硬件和内存空间)的访问,并对提供给用户的接口实施了实现细节的封装。其初始化例程决定了如何分配这些资源。Part of the initialization is done by the start function of glibc. A check is also performed at initialization time to ensure that the micro architecture type chosen in the config file is supported by the CPU. Then, the main() function is called. The core initialization and launch is done in rte_eal_init() (see the API documentation). EAL Initialization in a Linux Application Environment: EAL Initialization in a Linux Application Environment.png

  • PMD(Poll Mode Driver): 是DPDK中的一种基于用户态的轮询机制的驱动。PMD驱动是一种用户态驱动,它可以在用户空间中直接操作硬件,避免了内核空间的开销。在DPDK中,PMD驱动被用于替代传统的中断驱动方式,通过轮询的方式处理数据包。PMD驱动的主要特点是轮询方式处理数据包,避免了中断上下文切换的开销。在轮询模式下,PMD驱动会定期检查数据包是否到达,一旦发现数据包到达,就立即进行处理,而不是等待中断信号的到来。这种方式避免了中断上下文切换的开销,提高了处理效率。

DPDK环境为数据包处理应用考虑了两种模型:运行至完成(run-to-completion)模型和管道(pipeline)模型。在运行至完成模型中,一个API向某个特定端口的接收描述符环轮询以接收数据包。接着这个数据包在同一个核上被处理,之后被一个发送用API放到端口的传输描述符环上(同步模型)。在管道模型中,一个核心会通过API对一个或多个端口的接收描述符环进行轮询,数据包通过环被接收和传递给另一个核心,然后在这个核心上被处理,之后可能被发送用API放到端口的传输描述符环上(异步模型)。

传统网络模块结构和DPDK中的网络结构做对比

传统linux网络层:

硬件中断-->取包分发至内核线程--->软件中断-->内核线程在协议栈中处理包--->处理完毕通知用户层 用户层收包-->网络层--->逻辑层-->业务层

内核数据包接收:从网卡接收到报文后,通过MDA机制将报文放到内存,网卡触发中断通知内核有报文到达(一个报文一个中断)->内核会分配一个sk_buff结构体来存储该数据包(socket buff),将报文copy到这块sk_buff ->内核会对sk_buff中的数据进行协议栈分用处理 ->内核会将sk_buff传递给相应用程序进行进一步的处理。 内核数据包发送:应用程序通过系统调用发送数据包 ->内核接收到应用程序的发送请求后,会进行协议栈封装处理 ->内核使用sk_buff结构体来存储将要发送的数据包 ->内核将sk_buff通过DMA传递给网络设备驱动程序,驱动程序负责将数据包实际发送出去 -> 当数据包发送完成后,网卡可能会产生一个中断,通知内核数据包已经成功发送或发生错误。

dpdk网络层:

硬件中断--->放弃中断流程 用户层通过设备映射取包--->进入用户层协议栈-->逻辑层-->业务层

dpdk收发包流程:整个DPDK收发包过程中实现了零拷贝(DMA将包从网卡的FIFO接收队列拷贝到内存以及从内存中将包拷贝到网卡的FIFO发送队列;CPU没有参与,零拷贝是说CPU没有参与拷贝)。

数据包接收:数据包到达网卡 ->网卡经过DMA操作将数据包从网卡拷贝到接收队列(内存,Ring,RX buffer,使用mmap()直接将RX buffer的数据映射到用户用户空间,使用户空间可以直接访问RX buffer的数据,以此实现了零拷贝)->DPDK应用从接收队列中取包。

数据包发送:DPDK应用将数据包送到发送队列(内存,Ring,TX buffer)->网卡经过DMA操作将发送队列中的数据包拷贝到网卡 ->数据包从网卡发出。

Use of Hugepages in the Linux Environment

Hugepage support is required for the large memory pool allocation used for packet buffers (the HUGETLBFS option must be enabled in the running kernel as indicated the previous section). By using hugepage allocations, performance is increased since fewer pages are needed, and therefore less Translation Lookaside Buffers (TLBs, high speed translation caches), which reduce the time it takes to translate a virtual page address to a physical page address. Without hugepages, high TLB miss rates would occur with the standard 4k page size, slowing performance.

(1) Reserving Hugepages for DPDK Use

The reservation of hugepages can be performed at run time:
a single-node system echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages、 a NUMA machine echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages.

To reserve hugepages at boot time: 修改/etc/grub2.cfg中的Linux启动参数 default_hugepagesz=1G hugepagesz=1G hugepages=4. The tool dpdk-hugepages.py can be used to manage hugepages.

(2) Using Hugepages with the DPDK

To make the hugepages of size 1GB available for DPDK use: mkdir /mnt/huge mount -t hugetlbfs pagesize=1GB /mnt/huge The mount point can be made permanent across reboots, by adding the following line to the /etc/fstab file: nodev /mnt/huge hugetlbfs pagesize=1GB 0 0

Compiling and running the sample applications

(1) using meson

meson configure -Dexamples=helloworld    #-D option Set the value of an option, can be used several times to set multiple options.
ninja

(2) using make

make

(3) running

./dpdk-helloworld -l 0-5 -n 4

Organization of sources in the DPDK framework

(1)app:DPDK应用程序的源代码,用于测试DPDK或测试轮询模式驱动。

(2)buildtools:一些构建脚本工具。

(3)config:不同平台的编译配置。

(4)devtools:DPDK开发人员工具,提供一些shell和python脚本。

(5)doc:DPDK文档,这些文档提供了API参考、使用指南。

(6)drivers:DPDK轮询模式驱动程序的源代码。

(7)dts:DPDK Test Suite. DPDK测试套件。

(8)examples:DPDK应用程序示例的源代码,用于展示如何使用库。

(9)kenerl:某些操作系统所需要的内核模块。

(10)lib:DPDK库的源代码。

(11)license:DPDK许可证信息。(DPDK使用Open Source BSD-3-Clause license, 内核组件使用GPL-2.0 license)

(12)usertools:DPDK应用程序终端用户工具,一些python脚本工具。如dpdk-devbind.py绑定网卡到uio。

(13)MAINTAINERS: DPDK维护人员说明文档。(详细说明各文件的功能)

(14)Makefile: 伪Makefile文件。

(15)meson.build: 是一个使用Meson构建系统编写的构建脚本。它用于定义项目的构建规则、源代码文件、依赖关系和其他构建设置。

(16) meson_options.txt: 是一个用于配置Meson构建系统的选项文件。它提供了一组设置和参数,用于自定义构建过程以满足特定需求或约束。

Code learning

Core Components architecture-overview: Core Components architecture-overview.png

环境抽象层相关代码Environment Abstraction Layer: ./lib/eal/

CPU亲和性相关代码EAL pthread and lcore Affinity: ./lib/eal/common/eal_common_thread.c, rte_thread_set_affinity()

大页内存相关代码Memory Allocation: ./lib/eal/common/eal_hugepages.h, ./lib/eal/linux/eal_hugepage_info.c eal_hugepage_info_init()

内存管理相关代码Memory pool: ./lib/mempool/, ./lib/mempool/rte_mempool.c, rte_mempool_create_empty()

收发包相关代码Ethernet API: ./lib/ethdev/, ./lib/ethdev/rte_ethdev.h rte_eth_tx_burst() rte_eth_rx_burst()

流处理相关代码Flow API: ./lib/ethdev/rte_flow* ./lib/ethdev/rte_flow.c rte_flow_create()

Mellanox网卡相关代码Mellanox(NVIDIA mlx5): ./drivers/common/mlx5/, ./drivers/net/mlx5/, ./drivers/regex/mlx5/, ./drivers/vdpa/mlx5/

基于DPDK开发应用的步骤:

  1. 初始化DPDK的EAL环境:rte_eal_init(argc, argv)

  2. 创建内存池:在内存中创建一个新的内存池(mempool)存储mbufs(包的缓存池)rte_pktmbuf_pool_create()

  3. 初始化所有网卡的端口:

    (1)检查设备的端口ID是否已连接:rte_eth_dev_is_valid_port()

    (2)获取一个以太网设备上下文信息,检查设备是否支持快速释放mbufs:rte_eth_dev_info_get()

    (3)配置以太网设备:rte_eth_dev_configure()

    (4)检查Rx和Tx的描述符的数量是否满足以太网设备信息描述符数量的限制,否则调整他们的边界:rte_eth_dev_adjust_nb_rx_tx_desc()

    (5)为以太网设备分配和设置接收队列:rte_eth_rx_queue_setup()

    (6)为以太网设备分配和设置发送队列:rte_eth_tx_queue_setup()

    (7)启动以太网设备:rte_eth_dev_start()

    (8)获取以太网设备的MAC地址:rte_eth_macaddr_get()

    (9)启用混杂模式下的接收数据:rte_eth_promiscuous_enable()

  4. 创建流规则:

    (1)检查是否可以在给定端口上创建流规则:rte_flow_validate()

    (2)在给定的端口上创建流规则:rte_flow_create()

  5. 启动接收线程:rte_eal_mp_remote_launch()启动rx线程、rte_eth_rx_burst()从以太网设备的接收队列获取输入包

  6. 启动发送线程:rte_eal_mp_remote_launch()启动动tx线程、rte_eth_tx_burst() 在以太网设备的发送队列上发送输出包

  7. 清理DPDK的EAL环境:rte_eal_cleanup()

一个基于DPDK开发的对ftp和http的简单解析程序

FYI:

  • DPDK Doc : Getting Started Guide for LinuxProgrammer’s GuideSample Applications User Guides.
  • UIO(Userspace I/O):是运行在用户空间的I/O技术,是实现用户空间下驱动程序的支撑机制。Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可,而UIO则是将驱动的很少一部分运行在内核空间,而在用户空间实现驱动的绝大多数功能。DPDK使用UIO机制使用网卡驱动程序运行在用户态,并采用轮询和零拷贝方式从网卡收取报文,提高收发(RX/TX Receive/Transmit)报文的性能。
  • VFIO(Virtual Function I/O): VFIO是一套完整的用户态驱动(userspace driver)方案,它可以安全地把设备I/O、中断、DMA等能力呈现给用户空间。
  • KNI(Kernel NIC(Network Interface Card) Interface): 内核网卡接口是DPDK为用户态和内核协议栈交互提供的一种机制。本质上就是在内核注册一个虚拟网口(vEth),使用队列机制和用户态进行报文交换,和其他虚拟口的实现差不多。由于在DPDK报文处理中,有些报文需要发送到内核协议栈进行处理(用户空间没有完善的协议处理栈)。
  • IOVA(IO Virtual Address): IO虚拟地址模式可分为两种作为PA(Physical Address)的IOVA模式和作为VA(Virtual Address)的IOVA模式。
  • IOMMU(I/O Memory Management Unit): IOMMU是一种硬件单元,用于管理设备对内存的访问。它可以将IOVA地址转换为物理地址,从而保证设备访问的安全和正确性。
  • DMA(Direct Memory Access):直接内存访问传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
  • 页表(Page Table):是一种特殊的数据结构,用于存放逻辑页与物理页帧的对应关系。每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。在Linux内存管理中,PGD、PUD、PMD和PTE是页表中的不同层次,用于映射虚拟地址到物理地址。每一个PTE指向一个页框(Page Frame),即真正的物理内存页。
  • 大页(Huge Pages):操作系统使用分段和分页的方式管理内存。CPU通过分段和分页单元把逻辑地址转换为物理地址(CPU控制单元通过一种称为分段单元(segmentation uint)的硬件电路把一个逻辑地址转换成线性地址,然后,通过分页单元(paging uint)的硬件电路把这个线性地址转换成物理地址)。现代处理器的内存管理单元(MMU)大多都支持多种大小的page size。但内核长期以来默认使用4k大小的page size。对大于4k的页,我们统称为大页。Huge Pages从Linux kernel 2.6后被引入,目的是使用更大的内存页面以适应越来越大的系统内存。 使用大页的好处:提高TLB(Translation Lookaside Buffer,转译后备缓冲器)的命中率;减少了页表级数,减少了查找页表的时间;减少缺页异常(page fault)的发生次数。在 Linux 中大页分为两种Huge Pages( 标准大页 )和Transparent Huge Pages( 透明大页 )。 大页实现原理:Linux 采用了 hugetlb 和 hugetlbfs (Huge Translation Lookaside Buffer File System)两个概念。其中,hugetlb 是记录在 TLB 中的条目并指向 hugepages,而 hugetlbfs 则是一个特殊文件系统(本质是内存文件系统)。hugetlbfs 主要的作用是使得应用程序可以根据需要灵活地选择虚拟存储器页面的大小,而不会全局性的强制使用某个大小的页面。在 TLB 中通过 hugetlb 来指向 hugepages,可以通过 hugetlb entries 来调用 hugepages,这些被分配的 hugepages 再以 hugetlbfs 内存文件系统的形式提供给进程使用。当一个进程请求内存时,它需要访问 PageTable 去调用一个实际的物理内存地址,继而获得内存空间(进程当需要使用 Huge pages 时,只需要声明 Hugepage 属性)。 标准大页的使用不像普通内存申请那么简单,而是需要借助Hugetlb文件系统来创建:$ mkdir /mnt/huge $ mount none /mnt/huge -t hugetlbfs #挂载hugetlbfs文件系统$ echo 10 > /proc/sys/vm/nr_hugepages #配置可用 HugePages 数量。 由于标准的Huge Pages很难手动的管理,对于程序而言,可能需要修改很多的代码才能有效的使用。THP的引入就是为了便于系统管理员和开发人员使用大页内存。THP是一个抽象层,能够自动创建、管理和使用传统大页。Huge Pages是预分配的方式,而Transparent Huge Pages则是动态分配的方式,显然后者更适合程序使用。透明大页之所以叫透明大页,是因是其对应用进程而言是透明的。 查看THP是否开启/sys/kernel/mm/transparent_hugepage/enabled/sys/kernel/mm/transparent_hugepage/defrag
  • NUMA(Non-Uniform Memory Access Architecture): 主要应用于多处理器系统。在这种设计中,每个处理器都有自己的本地内存,并且可以访问所有其他处理器的本地内存。但是,访问自己本地内存的速度要比访问其他处理器的内存快。Non-Uniform Memory Access is a computer memory design used in multiprocessing, where the memory access time depends on the memory location relative to the processor. Under NUMA, a processor can access its own local memory faster than non-local memory.
  • lscpu: display information about the CPU architecture. lscpu gathers CPU architecture information from sysfs, /proc/cpuinfo and any applicable architecture-specific libraries. The command output can be optimized for parsing or for easy readability by humans.
ColumnsDescription
CPUThe logical CPU number of a CPU as used by the Linux kernel. 逻辑CPU = Thread * Core * Socket.
COREThe logical core number. A core can contain several CPUs. lcore is A logical execution unit of the processor, sometimes called a hardware thread.
SOCKETThe logical socket number. A socket can contain several cores.
BOOKThe logical book number. A book can contain several sockets.
DRAWERThe logical drawer number. A drawer can contain several books.
NODEThe logical NUMA node number. A node can contain several drawers.
CACHEInformation about how caches are shared between CPUs.
  • CPU Affinity: 是一种调度属性,它可以将一个进程“绑定”到一个或一组CPU上。在多处理器(SMP, Symmetric Multi-Processing)架构下,Linux调度器会根据CPU Affinity的设置让指定的进程运行在“绑定”的CPU上,而不会在别的CPU上运行。这种机制使得进程可以在一个CPU上长期运行,避免被迁移到其他处理器上,从而提高了性能。
  • taskset: set or retrieve a process's CPU affinity. taskset is used to set or retrieve the CPU affinity of a running process given its pid, or to launch a new command with a given CPU affinity.
  • RTE: Run Time Environment. Provides a fast and simple framework for fast packet processing, in a lightweight environment as a Linux* application and using Poll Mode Drivers (PMDs) to increase speed.
  • RCU: Read-Copy-Update algorithm, an alternative to simple rwlocks.
  • HPET(High Precision Event Timer): DPDK can support the system HPET as a timer source rather than the system default timers.
  • Meson: Meson is an open source build system meant to be both extremely fast, and, even more importantly, as user friendly as possible. The main design point of Meson is that every moment a developer spends writing or debugging build definitions is a second wasted. So is every second spent waiting for the build system to actually start compiling code.
  • Ninja: Ninja is a small build system with a focus on speed. By default, Meson will use the Ninja backend to build your project.
  • DPVS(DPDK-LVS (Linux Virtual Server)): DPVS is a high performance Layer-4 load balancer based on DPDK.
  • OvS(Open Virtual Switch): 是一个高质量的,多层虚拟交换机。它旨在通过编程扩展,使庞大的网络自动化(配置、管理、维护),同时还支持标准的管理接口和协议。ovs-vsctl 命令行工具可以对ovs进行管理。ovs主要有内核datapath(ovs kernel module) 和用户空间的vswitchd、ovsdb组成。
  • VPP(Vector Packet Processing): The VPP platform is an extensible framework that provides out-of-the-box production quality switch/router functionality.