DPDK 完全内核旁路技术实现

   日期:2024-12-27    作者:b957861 移动:http://ljhr2012.riyuangf.com/mobile/quote/58724.html

转载自DPDK 完全内核旁路技术实现

 

在 x86 体系结构中,接受数据包的传统方式是 CPU 中断方式,即网卡驱动接收到数据包后通过中断通知 CPU 处理,然后由 CPU 拷贝数据并交给内核协议栈。在数据量大时,这种方式会产生大量 CPU 中断,导致 CPU 无法运行其他程序。

  • 硬件中断导致的线程/进程切换:硬件中断请求会抢占优先级较低的软件中断,频繁到达的硬件中断和软中断意味着频繁的线程切换,随着而来的就是运行模式切换、上下文切换、线程调度器负载、高速缓存缺失(Cache Missing)、多核缓存共享数据同步、竞争锁等一系列的 CPU 性能损耗。
  • 内存拷贝:网卡驱动位于内核态,网络驱动接收到的数据包后会经过内核协议栈的处理,然后再拷贝到处于用户态的应用层缓冲区,这样的数据拷贝是很耗时间的。据统计,这个拷贝的时间会占数据包处理流程时间的 57.1%。
  • 多处理器平台的 CPU 漂移:一个数据包可能中断在 CPU0,内核态处理在 CPU1,用户态处理在 CPU2,这样跨多个物理核(Core)的处理,导致了大量的 CPU Cache Miss,造成局部性失效。如果是 NUMA 架构,还会出现跨 NUMA remote access Memory 的情况,这些都极大地影响了 CPU 性能。
  • 缓存失效:传统服务器大多采用页式虚拟存储器,内存页默认为 4K 的小页,所以在存储空间较大的处理机上会存在大量的页面映射条目。同时因为 TLB 缓存空间有限,最终导致了 TLB 快表的映射条目频繁变更,产生大量的快页表 Cache Miss。

故此,对应的优化方案为

  • 使用 NUMA 亲和:避免 CPU 跨 NUMA 访问内存。
  • 使用 CPU 绑核:避免跨 CPU 的线程/进程切换。
  • 使用大页内存:避免 TLB Cache Miss。
  • 使用 DPDK:内核旁路技术避免了频繁的硬件中断和无效数据拷贝。

DPDK(Data Plane Development Kit,数据平面开发套件)是一个开源的、快速处理数据平面数据包转发的开发平台及接口。运行于 Intel x86 与 ARM 平台上,最新版本也开始支持 PowerPC。

英特尔在 2010 年启动了对 DPDK 技术的开源化进程,于当年 9 月通过 BSD 开源许可协议正式发布源代码软件包,并于 2014 年 4 月在 www.dpdk.org 上正式成立了独立的开源社区平台,为开发者提供支持。开源社区的参与者们大幅推进了 DPDK 的技术创新和快速演进,而今它已发展成为 SDN 和 NFV 的一项关键技术。

DPDK 提供了一个用户态的高效数据包处理库函数,它通过环境抽象层、内核旁路协议栈、轮询模式的报文无中断收发、优化内存/缓冲区/队列管理、基于网卡多队列和流识别的负载均衡等多项技术,实现了在 x86 处理器架构下的高性能报文转发能力,用户可以在 Linux 用户态开发各类高速转发应用,也适合与各类商业化的数据平面加速解决方案进行集成。简而言之,DPDK 重载了网卡驱动,将数据包的控制平面和数据平面分离,驱动在收到数据包后不再硬中断通知 CPU,而是让数据包通过内核旁路的协议栈绕过了 Linux 内核协议栈,并通过零拷贝技术存入内存,这时应用层的程序就可以通过 DPDK 提供的接口读取数据包

NFV 通过在通用 x86 架构硬件上运行的虚拟化网络功能(VNF),为电信运营商和互联网服务商提供了一种灵活的业务部署手段和高效的组网方案,可以支持固/移网络和 IDC 中 NAT、DPI、EPC、FW 等各类业务功能的灵活部署与弹性扩展。

不同于典型数据中心业务和企业网业务,电信广域网业务要求网元(如 BNG、DPI 等)具有高吞吐、低时延、海量流表支持、用户级 QoS 控制的特点。大量实践表明,通用 x86 服务器作为 NFV 基础设施用于高转发业务时,面临着严重的数据包转发性能瓶颈,需要有针对性地从硬件架构、系统 I/O、操作系统、虚拟化层、组网与流量调度、VNF 功能等层面进行性能优化,才能达到各类 NFV 网络业务的高性能转发要求。

根据 ETSI 的 NFV 参考架构,现实中的 NFV 应用系统一般由 NFV 基础设施(VIM)和 VNF 两类系统服务商提供。因此,相应的 NFV 端到端性能测试,也应划分为底层的 VIM 性能与上层的 VNF 性能两类,以明确各自的性能瓶颈,并避免性能调优工作相互干扰。

在 VIM 层面,由于采用了软件转发和软件交换技术,单个物理服务器内部的数据转发能力是 NFV 的主要性能瓶颈。在各类高速转发的 NFV 应用中,数据报文从网卡中接收,再传送到虚拟化的用户态应用程序处理。整个过程要经历 CPU 中断处理、虚拟化 I/O 与地址映射转换、虚拟交换层、内核协议栈、内核上下文切换、内存拷贝等多个费时的 CPU 操作和 I/O 处理环节。面对这样的性能损耗问题,业内通常采用消除海量中断、内核旁路协议栈、减少内存拷贝、CPU 多核任务分担、Intel VT 等技术来综合提升服务器数据平面的报文处理性能。但由于技术栈复杂,普通用户较难掌握,业界迫切需要一种综合的性能优化方案,同时提供良好的用户开发和商业集成环境。

DPDK 技术分为基本技术和优化技术两类。其中,前者指标准的 DPDK 数据平面开发包和 I/O 转发实现技术。

  • 内核协议栈(左边):网卡 -> 驱动 -> 协议栈 -> Socket 接口 -> 业务。
  • DPDK 基于 UIO(User Space I/O)的内核旁路(右边):网卡 -> DPDK 轮询模式-> DPDK 基础库 -> 业务。

NOTE:说 DPDK 依赖网卡设备不如说 DPDK 依赖的是网卡设备对应的驱动程序。支持 DPDK 的 NIC Kernel Driver 可以转换为 UIO Driver 模式。由此,如有需要,DPDK 实际上是可以在虚拟机上使用的,前提是网卡设备通过 Passthrough 的方式给到虚拟机。所以,该场景中 SR-IOV 网卡会是一个不错的选择。

在最底部的内核态(Linux Kernel),DPDK 拥有两个模块:KNI 与 IGB_UIO。而 DPDK 的上层用户态由很多库组成,主要包括核心部件库(Core Libraries)、平台相关模块(Platform)、网卡轮询模式驱动模块(PMD-Natives&Virtual)、QoS 库、报文转发分类算法(Classify)等几大类,用户应用程序可以使用这些库进行二次开发。下面我们逐一介绍这些组件的功能和作用。

传统的收发数据包方式,首先网卡通过中断方式通知内核协议栈对数据包进行处理,内核协议栈先会对数据包进行合法性进行必要的校验,然后判断数据包目标是否为本机的 Socket,满足条件则会将数据包拷贝一份向上递交到用户态 Socket 来处理。不仅处理路径冗长,还需要从内核到应用层的一次拷贝过程。

为了使得网卡驱动(e.g. PMD Driver)运行在用户态,实现内核旁路。Linux 提供了 UIO(User Space I/O)机制。使用 UIO 可以通过 感知中断,通过 实现和网卡设备的通讯。

简单来说,UIO 是用户态的一种 I/O 技术,DPDK 能够绕过内核协议栈,提供了用户态 PMD Driver 的支持,根本上是得益于 UIO 技术。DPDK 架构在 Linux 内核中安装了 IGB_UIO(igb_uio.ko 和 kni.ko.IGB_UIO)模块,以此借助 UIO 技术来截获中断,并重设中断回调行为,从而绕过内核协议栈后续的处理流程。并且 IGB_UIO 会在内核初始化的过程中将网卡硬件寄存器映射到用户态。

UIO 的实现机制是:对用户态暴露文件接口。当注册一个 UIO 设备 uioX 时,就会出现系统文件 /dev/uioX,对该文件的读写就是对网卡设备内存的读写。除此之外,对网卡设备的控制还可以通过 /sys/class/uio 下的各个文件的读写来完成。如下图:

此外,DPDK 还在用户态实现了一套精巧的内存池技术,内核态和用户态之间的的内存交互不进行拷贝,只做控制权转移。这样,当收发数据包时,就减少了内存拷贝的开销。

我们知道,Linux 内核在收包时有两种方式可供选择,一种是中断方式,另外一种是轮询方式。

从哲学的角度来说,中断是外界强加给你的信号,你必须被动应对,而轮询则是你主动地处理事情。前者最大的影响就是打断你当前工作的连续性,而后者则不会,事务的安排自在掌握。

中断对性能的影响有多大?在 x86 体系结构中,一次中断处理需要将 CPU 的状态寄存器保存到堆栈,并运行中断服务程序,最后再将保存的状态寄存器信息从堆栈中恢复。整个过程需要至少 300 个处理器时钟周期。

轮询对性能的提升有多大?网卡收到报文后,可以借助 DDIO(Direct Data I/O)技术直接将报文保存到 CPU 的 Cache 中,或者保存到内存中(没有 DDIO 技术的情况下),并设置报文到达的标志位。应用程序则可以周期性地轮询报文到达的标志位,检测是否有新报文需要处理。整个过程中完全没有中断处理过程,因此应用程序的网络报文处理能力得以极大提升。

故此,想要 CPU 执行始终高效,就必然需要一个内核线程去主动 Poll(轮询)网卡,而这种行为与当前的内核协议栈是不相容的,即便当前内核协议栈可以使用 NAPI 中断+轮询的方式,但依旧没有根本上解决问题。除非再重新实现一套全新的内核协议栈,显然这并不现实,但幸运的是,我们可以在用户态实现这一点。

针对 Intel 网卡,DPDK 实现了基于轮询方式的 PMD(Poll Mode Drivers)网卡驱动。该驱动由用户态的 API 以及 PMD Driver 构成,内核态的 UIO Driver 屏蔽了网卡发出的中断信号,然后由用户态的 PMD Driver 采用主动轮询的方式。除了链路状态通知仍必须采用中断方式以外,均使用无中断方式直接操作网卡设备的接收和发送队列。

PMD Driver 从网卡上接收到数据包后,会直接通过 DMA 方式传输到预分配的内存中,同时更新无锁环形队列中的数据包指针,不断轮询的应用程序很快就能感知收到数据包,并在预分配的内存地址上直接处理数据包,这个过程非常简洁。

PMD 极大提升了网卡 I/O 性能。此外,PMD 还同时支持物理和虚拟两种网络接口,支持 Intel、Cisco、Broadcom、Mellanox、Chelsio 等整个行业生态系统的网卡设备,以及支持基于 KVM、VMware、 Xen 等虚拟化网络接口。PMD 实现了 Intel 1GbE、10GbE 和 40GbE 网卡下基于轮询收发包。

UIO+PMD,前者旁路了内核,后者主动轮询避免了硬中断,DPDK 从而可以在用户态进行收发包的处理。带来了零拷贝(Zero Copy)、无系统调用(System call)的优化。同时,还避免了软中断的异步处理,也减少了上下文切换带来的 Cache Miss。

虽然 PMD 是在用户态实现的驱动程序,但实际上还是会依赖于内核提供的策略。其中 UIO 内核模块,是内核提供的用户态驱动框架,而 igb_uio 是 DPDK 用于与 UIO 交互的内核模块,通过 igb_uio 来 bind 指定的 PCI 网卡设备到 DPDK 使用。

igb_uio 内核模块主要功能之一就是用于注册一个 PCI 设备。实际上这是由 DPDK 提供个一个 Python 脚本 dpdk-devbind 来完成的,当执行 dpdk-devbind 来 bind 网卡时,会通过 sysfs 与内核交互,让内核使用指定的驱动程序来匹配网卡。具体的行为是向文件 /sys/bus/pci/devices/(pci id)/driver_override 写入指定驱动的名称,或者向 /sys/bus/pci/drivers/igb_uio(驱动名称)/new_id 写入要 bind 的网卡设备的 PCI ID。前者是配置设备,让其选择驱动;后者是是配置驱动,让其支持新的 PCI 设备。按照内核的文档 https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci 中提到,这两个动作都会促使驱动程序 bind 新的网卡设备。

dpdk-devbind 具体的步骤如下

  1. 获取脚本执行参数指定的网卡(e.g. eth1)设备的 PCI 信息。实际是执行指令 查看,主要关注 Slot、Vendor 以及 Device 信息。

  1. unbind 网卡设备之前的 igb 模块,将 Step 1 中获取到的 eth1 对应的 Slot 信息 值写入 igb 驱动的 unbind 文件。e.g. 。
  2. bind 网卡设备到新的 igb_uio 模块,将 eth1 的 Vendor 和 Device ID 信息写入 igb_uio 驱动的 new_id 文件。e.g. 。

igb_uio 内核模块的另一个主要功能就是让用于态的 PMD 驱动程序得以与 UIO 进行交互

  1. 调用 igbuio_setup_bars,设置 uio_info 的 uio_mem 和 uio_port。
  2. 设置 uio_info 的其他成员。
  3. 调用 uio_register_device,注册 UIO 设备。
  4. 打开 UIO 设备并注册中断。
  5. 调用 uio_event_notify,将注册的 UIO 设备的 “内存空间” 映射到用户态的应用空间。其 mmap 的函数为 uio_mmap。至此,UIO 就可以让 PMD 驱动程序在用户态应用层访问设备的大部分资源了。
  6. 应用层 UIO 初始化。同时,DPDK 还需要把 PCI 设备的 BAR 映射到应用层。在 pci_uio_map_resource 函数中会调用 pci_uio_map_resource_by_index 做资源映射。
  7. 在 PMD 驱动程序中,DPDK 应用程序,会调用 rte_eth_rx_burst 读取数据报文。如果网卡接收 Buffer 的描述符表示已经完成一个报文的接收(e.g. 有 E1000_RXD_STAT_DD 标志),则 rte_mbuf_raw_alloc 一个 mbuf 进行处理。
  8. 对应 RTC 模型的 DPDK 应用程序来说,就是不断的调用 rte_eth_rx_burst 去询问网卡是否有新的报文。如果有,就取走所有的报文或达到参数 nb_pkts 的上限。然后进行报文处理,处理完毕,再次循环。

KNI(Kernel NIC Interface,内核网卡接口),是 DPDK 允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供 DPDK 应用程序和 Linux 内核之间通讯没接。即 KNI 接口允许报文从用户态接收后转发到 Linux 内核协议栈中去。

虽然 DPDK 的高速转发性能很出色,但是也有自己的一些缺点,比如没有标准协议栈就是其中之一,当然也可能当时设计时就将没有将协议栈考虑进去,毕竟协议栈需要将报文转发处理,可能会使处理报文的能力大大降低。

上图是 KNI 的 mbuf 的使用流程,也可以看出报文的流向,因为报文在代码中其实就是一个个内存指针。其中 rx_q 右边是用户态,左边是内核态。最后通过调用 netif_rx 将报文送入 Linux 内核协议栈,这其中需要将 DPDK 的 mbuf 转换成标准的 skb_buf 结构体。当 Linux 内核向 KNI 端口发送报文时,调用回调函数 kni_net_tx,然后报文经过转换之后发送到端口上。

核心部件库(Core Libraries)是 DPDK 面向用户态协议栈应用程序员开发的模块。

  • EAL(Environment Abstraction Layer,环境抽象层):对 DPDK 的运行环境(e.g. Linux 操作系统)进行初始化,包括:HugePage 内存分配、内存/缓冲区/队列分配、原子性无锁操作、NUMA 亲和性、CPU 绑定等,并通过 UIO 或 VFIO 技术将 PCI/PCIe 设备地址映射到用户态,方便了用户态的 DPDK 应用程序调用。同时为应用程序提供了一个通用接口,隐藏了其与底层库以及设备打交道的相关细节。

  • MALLOC(堆内存管理组件):为 DPDK 应用程序提供从 HugePage 内分配堆内存的接口。当需要为 SKB(Socket Buffer,本质是若干个数据包的缓存区)分配大量的小块内存时(如:分配用于存储 Buffer descriptor table 中每个表项指针的内存)可以调用该接口。由于堆内存是从 HugePage 内存分配的,所以可以减少 TLB 缺页。

NOTE:堆,是由开发人员主动分配和释放的存储空间, 若开发人员不释放,则程序结束时由 OS 回收,分配方式类似于链表;与堆不同,栈,是由操作系统自动分配和释放的存储空间 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。

  • MBUF(网络报文缓存块管理组件):为 DPDK 应用程序提供创建和释放用于存储数据报文信息的缓存块的接口。提供了两种类型的 MBUF,一种用于存储一般信息,一种用于存储实际的报文数据。这些 MBUF 存储在一个内存池中。

  • MEMPOOL(内存池管理组件):为 DPDK 应用程序和其它组件提供分配内存池的接口,内存池是一个由固定大小的多个内存块组成的内存容器,可用于存储不同的对像实体,如:数据报文缓存块等。内存池由内存池的名称(一个字符串)进行唯一标识,它由一个 Ring 缓冲区和一组本地缓存队列组成,每个 CPU Core 优先从自身的缓存队列中分配内存块,当本地缓存队列减少到一定程度时,开始从内存环缓冲区中申请内存块来进行补充。

  • RING(环缓冲区管理组件):为 DPDK 应用程序和其它组件提供一个无锁的多生产者多消费者 FIFO 队列。

NOTE:DPDK 基于 Linux 内核的无锁环形缓冲 kfifo 实现了一套自己的无锁机制。支持单生产者入列/单消费者出列和多生产者入列/多消费者出列操作,在数据传输的时候,降低性能的同时还能保证数据的同步。

  • TIMER(定时器组件):提供一些异步周期执行的接口(也可以只执行一次),可以指定某个函数在规定时间内的异步执行,就像 LIBC 中的 timer 定时器。但是这里的定时器需要 DPDK 应用程序在主循环中周期内调用 来使能定时器,使用起来不那么方便。TIMER 的时间参考来自 EAL 层提供的时间接口。

  • RTE:Run-Time Environment
  • EAL:Environment Abstraction Layer
  • PMD:Poll-Mode Driver

Memory Manager(librte_malloc,堆内存管理器):提供一组 API,用于从 HugePages 内存创建的 memzones 中分配内存。

Ring Manager(librte_ring,环形队列管理器):在一个大小有限的页表中,Ring 数据结构提供了一个无锁的多生产者-多消费者 FIFO API。相较于无锁队列,它有一些的优势,如:更容易实现,适应于大容量操作,而且速度更快。 Ring 在 Memory Pool Manager 中被使用,而且 Ring 还用于不同 CPU Core 之间或是 Processor 上处理单元之间的通信。

Memory Pool Manager(librte_mempool,内存池管理器):内存池管理器负责分配的内存中的 Pool 对象。Pool 由名称唯一标识,并使用一个 Ring 来存储空闲对象。它提供了其他一些可选的服务,例如:每个 CPU Core 的对象缓存和对齐方式帮助,以确保将填充的对象在所有内存通道上得到均匀分布。

Network Packet Buffer Management(librte_mbuf,网络报文缓冲管理):提供了创建和销毁数据报文缓冲区的能力。DPDK 应用程序中可以使用这些缓存区来存储消息以及报文数据。

Timer Manager(librte_timer,定时器管理):为 DPDK 应用程序的执行单元提供了定时服务,为函数异步执行提供支持。定时器可以设置周期调用或只调用一次。DPDK 应用程序可以使用 EAL 提供的接口获取高精度时钟,并且能在每个核上根据需要进行初始化。

平台相关模块(Platform)包括 KNI、POWER(能耗管理)以及 IVSHMEM 接口。

  • KNI:主要通过 Linux 内核中的 kni.ko 模块将数据报文从用户态传递给内核态协议栈处理,以便常规的用户进程(e.g. Container)可以使用 Linux 内核协议栈传统的 Socket 接口对相关报文进行处理。

  • POWER:提供了一些 API,让 DPDK 应用程序可以根据收包速率动态调整 CPU 频率或让 CPU 进入不同的休眠状态。

  • IVSHMEM:模块提供了虚拟机与虚拟机之间,或者虚拟机与主机之间的零拷贝共享内存机制。当 DPDK 应用程序运行时,IVSHMEM 模块会调用 Core Libraries 的 API,把几个 HugePage 内存映射为一个 IVSHMEM 设备池,并通过参数传递给 QEMU,这样,就实现了虚拟机之间的零拷贝内存共享。

  • Buffer Manager API:通过预先从 EAL 上分配固定大小的多个内存对象,避免了在运行过程中动态进行内存分配和回收,以此来提高效率,用于数据包 Buffer 的管理。
  • Queue/Ring Manager API:以高效的方式实现了无锁的 FIFO 环形队列,适用于一个生产者多个消费者、一个消费者多个生产者模型。支持批量无锁操作,可避免锁冲突导致的等待。
  • Packet Flow Classification API:通过 Intel SSE 基于多元组的方式实现了高效的 HASH 算法,以便快速对数据包进行分类处理。该 API 一般用于路由查找过程中的最长前缀匹配。此外,安全产品场景中,可以根据 DataFlow 五元组来标记不同的用户。

DPDK 优化技术指在 DPDK 应用过程中,为进一步提高各类用户应用程序的转发性能,所采取的性能调优方法和关键配置。

本节主要介绍基于 DPDK 进行应用开发和环境配置时,应用程序性能的影响因素以及相应的优化调整方法。这些因素并非必然劣化性能,可能因硬件能力、OS 版本、各类软硬环境参数配置等的差异产生较大波动,或者存在较大的不稳定性,相关的调优方法需要用户结合自身的 VNF 应用部署在实践中不断完善。

DPDK 具有广泛的平台适应性,可以运行在整个 x86 平台,从主流服务器平台(从高性能或者高能效产品系列),到桌面或者嵌入式平台,也可以运行于基于 Power 或者其他架构的运算平台。下图展示了一个通用双路服务器的内部架构,它包含了 2 个中央处理器,2 个分离的内存控制单元来连接系统内存,芯片组会扩展出大量高速的 PCIe 2.0/3.0 接口,用于连接外设,如 10Gbps 或者 25Gbps 网卡外设。

  • CPU 频率:CPU 频率越高,DPDK 性能越高。
  • LLC(Last Leve Cache)大小:缓存越大,DPDK 性能越高。
  • PCIe Lane 的数目:PCIe 链路可以支持 1、2、4、8、12、16 和 32 个 Lane,需要确保其带宽可以满足所插网卡的带宽。
  • NUMA:网络数据报文的处理由网卡所在的 NUMA 节点处理,将会比远端 NUMA 节点处理的性能更高。

不同的 Linux OS 发行版使用的 Linux 内核版本不一样,配置的 Linux OS 服务也不一样。这些差异都会导致应用程序在网络报文处理能力上有所差别。由于 Linux 内核还在不断演进,Linux 的发行版也数量众多,同样的硬件配置环境下,不同的 Linux 发行版在小包的处理能力上存在差异。本文无法提供最佳 Linux 内核版本和配置,而只能给出部分参考建议,如:关闭部分 OS 服务。

OVS 作为 NFV 的一个重要组成模块,会运行在绝大多数的服务器节点上,提供虚拟机和虚拟机之间,以及虚拟网络和物理网络间的互连接口,其性能至关重要。OVS 2.4 开始正式支持 DPDK 加速,相比传统基于 Linux 内核的 OVS 版本,转发性能提高了数倍,为 VNF 在通用 x86 服务器上部署提供了有力支持。

OVS 缺省会为每一个 NUMA 节点创建一个 pmd 线程,该 pmd 线程以轮询方式处理属于其 NUMA 节点上的所有 DPDK 接口。为了高性能,需要利用前面提到的 CPU 绑核技术,把 pmd 线程绑定在一个固定的 CPU core 上处理。此外,为了增加扩展性,OVS 2.4 也支持网卡多队列以及多 pmd 线程数,这些参数均可动态配置,但具体配置应根据具体需求来决定。

如前所述,DPDK 考虑了 NUMA 以及多内存通道的访问效率,会在系统运行前要求配置 Linux 的 HugePage,初始化时申请其内存池,用于 DPDK 运行的主要内存资源。Linux 大页机制利用了处理器核上的的 TLB 的 HugePage 表项,这可以减少内存地址转换的开销。

如果想建立一个基于消息传递的核间通信机制,可以使用 DPDK Ring API,它是一个无锁的 ring 实现。该 Ring 机制支持批量和突发访问,即使同时读取几个对象,也只需要一个昂贵的原子操作,批量访问可以极大地改善性能。

DPDK 支持 CPU 微架构级别的优化,可以通过修改 DPDK 配置文件中的 CONFIG_RTE_MACHINE 参数来定义。优化的程度根据随编译器的能力而不同,通常建议采用最新的编译器进行编译。如果编译器的版本不支持该款 CPU 的特性,比如 Intel AVX 指令,那么它在编译时只会选用自己支持的指令集,这可能导致编译后生成的 DPDK 应用的性能下降。

笔者在 DPDK 的优化方面的实践因为商业敏感的问题,本文不便提及。网上有很多资料,推荐阅读这一篇文章 https://zhaozhanxu.com/2016/08/09/DPDK/2016-08-09-dpdk-optimization/ 和《DPDK 技术白皮书》。

NUMA 的结构设计能够在一定程度上解决 SMP 低存储器访问带宽的问题。假如一个 4 NUMA 节点的系统,每一个 NUMA 节点内部都具有 1GB/s 的存储带宽,外部共享总线也同样具有 1GB/s 的带宽。理想状态下,如果所有的 CPU 总是访问本地内存的话,那么系统就拥有了 4GB/s 的存储带宽能力。此时的每个 NUA 节点都可以近似的看作为一个 SMP(这种假设为了便于理解,并不完全正确);相反,在最不理想的情况下,如果所有处理器总是访问远程内存的话,那么系统就只能有 1GB/s 的存储器访问带宽。

除此之外,使用外部共享总线时可能会触发 NUMA 节点间的 Cache 同步损耗,这会严重影响内存密集型工作负载的性能。当 I/O 性能至关重要时,外部共享总线上的 Cache 资源浪费,会让连接到远程 PCIe 总线上的设备(不同 NUMA 节点间通信)作业性能急剧下降。

由于这个特性,基于 NUMA 架构开发的应用程序应该尽可能避免跨 NUMA 节点的远程内存访问。因为,跨节点内存访问不仅通信速度慢,还可能需要处理不同节点间内存、缓存的数据一致性问题。多线程在不同 NUMA 节点间的切换,是需要花费大成本的。

现代操作系统都是基于分时调用方式来实现任务调度,多个进程或线程在多核处理器的某一个核上不断地交替执行。每次切换过程,都需要将处理器的状态寄存器保存在堆栈中,并恢复当前进程的状态信息,这对系统其实是一种处理开销。将一个线程固定一个核上运行,可以消除切换带来的额外开销。另外将进程或者线程迁移到多核处理器的其它核上进行运行时,处理器缓存中的数据也需要进行清除,导致处理器缓存的利用效果降低

CPU 亲和技术,就是将某个进程或者线程绑定到特定的一个或者多个核上执行,而不被迁移到其它核上运行,这样就保证了专用程序的性能。DPDK 使用了 Linux pthread 库,在系统中把相应的线程和 CPU 进行亲和性绑定,然后相应的线程尽可能使用独立的资源进行相关的数据处理。

处理器的内存管理包含两个概念:物理内存和虚拟内存。Linux 操作系统里面整个物理内存按帧(Frames)来进行管理,虚拟内存按照页(Page)来进行管理。内存管理单元(MMU)完成从虚拟内存地址到物理内存地址的转换。内存管理单元进行地址转换需要的信息保存在
一个叫页表(Page Table)的数据结构里面,页表查找是一种极其耗时的操作。为了减少页表的查找过程,Intel 处理器实现了一块缓存来保存查找结果,这块缓存被称为 TLB(Translation Lookaside Buffer),它保存了虚拟地址到物理地址的映射关系。所有虚拟地址在转换为物理地址以前,处理器会首先在 TLB 中查找是否已经存在有效的映射关系,如果没有发现有效的映射,也就是 TLS miss,处理器再进行页表的查找。页表的查找过程对性能影响极大,因此需要尽量减少 TLB miss 的发生。

x86 处理器硬件在缺省配置下,页的大小是 4K,但也可以支持更大的页表尺寸,例如 2M 或 1G 的页表。使用了大页表功能后,一个 TLB 表项可以指向更大的内存区域,这样可以大幅减少 TLB miss 的发生。早期的 Linux 并没有利用 x86 硬件提供的大页表功能,仅在 Linux 内核 2.6.33 以后的版本,应用软件才可以使用大页表功能。DPDK 则利用大页技术,所有的内存都是从 HugePage 里分配,实现对内存池(Mempool)的管理,并预先分配好同样大小的 mbuf,供每一个数据包使用。

DPDK 目前支持了 2M 和 1G 两种 HugePage。通过编辑 /etc/grub.conf 来设置:


然后,执行下述指令,将 HugePage 文件系统 hugetlbfs 挂载到 /mnt/huge 目录:


如此,用户进程就可以使用 系统调用映射 HugePage 目标文件来使用大页面了。测试表明应用程序使用大页表比使用 4K 的小页表性能提高 10%~15%。

NOTE:大页内存的具体介绍可以参见 Linux 的大页表文件系统(hugetlbfs)特性。

https://vcpu.me/packet_from_nic_to_user_process/
https://blog.csdn.net/City_of_skey/article/details/85038684
https://blog.csdn.net/qq_15437629/article/details/78146823
https://zhaozhanxu.com/2016/08/09/DPDK/2016-08-09-dpdk-optimization/
https://mp.weixin.qq.com/s/RW0GO8hNxoE7upAeAExWHg
https://cloud.tencent.com/developer/article/1198333


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号