数据平面的性能在很大程度上取决于网络 I/O 的性能,而网络数据包从网卡到用户空间的应用程序需要经历多个阶段,本文从数据平面基础到NFV,NFC基础设施再到OVS-DPDK VPP进行概论上的描述。
部分内容来源于《Linux开源网络全栈详解:从DPDK到OpenFlow》
Linux网络数据包的处理流程
在Linux系统中,网络数据包的处理流程主要包括以下几个步骤:
- 数据包接收:数据包通过网络接口卡(NIC)到达Linux系统。NIC将接收到的数据包复制到内存中的网络缓冲区。
- 中断处理:NIC触发一个中断,通知CPU有新数据包到达。CPU响应中断,执行网络驱动程序中的中断处理程序。
- 数据包解码:驱动程序读取网络缓冲区中的数据包,并解析数据包头部(如以太网头部)。根据协议类型(IPv4、IPv6等)决定后续处理。
- 传递到协议栈:数据包被传递给相应的协议栈(如IP层)。IP层对数据包进行处理,包括路由查找、分片和重组等。
- 传递到上层协议:如果数据包是TCP或UDP,则会进一步传递到相应的传输层协议。在此阶段,TCP会处理流控制、拥塞控制等,而UDP则会直接将数据交给应用层。
- 数据传递到应用层:数据包的有效负载被传递到相应的应用程序(如Web服务器、FTP等)。应用程序可以读取并处理这些数据。
发送数据就是反过来的方向。整个过程的多个阶段都存在着不可忽视的开销,主要有以下几点:
网卡中断: 轮询与中断是操作系统与硬件设备进行 I/O 通信的两种主要方式。在一般情况下,网络数据包的到来都是不可预测的,若采用轮询模式,则会造成很高的CPU负载,因此主流操作系统都会采用中断的方式来处理网络的请求。随着网络I/O速率的不断提升,网卡面对大量的高速数据分组将会引发频繁的中断,每次中断都会引起一次上下文切换(从当前正在运行的进程切换到因等待网络数据而阻塞的进程),操作系统都需要保存和恢复相应的上下文,造成较高的时延,并引起吞吐量下降。
内存拷贝: 为了使位于用户空间的应用程序能够处理网络数据,内核首先需要将收到的网络数据从内核空间拷贝到用户空间,同样,为了能够发送网络数据,也需要进行从用户空间到内核空间的数据拷贝。每次拷贝都会占用大量的 CPU、内存带宽等资源
锁: 在Linux内核的网络协议栈实现中,存在大量的共享资源访问。当多个进程需要对某一共享资源进行操作时,就需要通过锁的机制来保证数据的一致。然而,为共享资源上锁或者去锁的过程中通常需要几十纳秒。此外,锁的存在也降低了整个系统的并发性能。
缓存未命中: 因不合理的设计而造成频繁的缓存未命中,会严重削弱数据平面的性能。在L3(Last Level Cache)命中与未命中条件下,数据操作耗时相差数倍。如果存在频繁的跨核调用,由此带来的缓存未命中会造成严重的数据读写时延,从而降低系统的整体性能。
高性能数据平面基础
对于数据面的包处理而言,使用的主流硬件平台一般大致可分为硬件加速器、网络处理器及通用处理器。依据处理复杂度、成本、功耗等因素的不同,这些硬件平台在各自特定的领域发挥着作用。硬件加速器和网络处理器由于其专属性,往往具有高性能、低成本的优点。而通用处理器则在复杂多变的数据包处理上更有优势。
内核旁路
在操作系统的设计中,内核通过硬件抽象和硬件隔离的方法,给上层应用程序的开发带来了简便性,但也导致了一些性能的下降。在网络方面,主要体现在整体吞吐率的减少和报文延迟的增加上。这种程度的性能下降对大多数场景来说可能不是问题,因为整体系统的瓶颈更多地会出现在业务处理逻辑和数据库上面。但对NFV(Network Functions Virtualization,网络功能虚拟化)这样的纯网络应用而言,内核的性能就有些捉襟见肘,性能优化显得很有必要。
一个数据包从网卡到应用程序要经过内核中的驱动、协议栈处理,然后从内核的内存复制到用户空间的内存中,加上系统调用要求的用户到内核空间的切换,都会导致内核性能的下降。可以使用内核旁路技术,就是应用程序不通过内核直接操作硬件。
内核旁路之后,应用程序直接和硬件打交道,但也需要解决硬件的抽象接口、内存分配和CPU调度等问题,甚至还有网络协议栈的处理。这方面有DPDK、Netmap、及XDP等开源框架,在一定程度上起到了硬件抽象和隔离功能,简化了应用程序开发。
DPDK: 是一个全面的网络内核旁路解决方案,不仅支持众多的网卡类型,也有多种内存和CPU调度的优化方案。在DPDK之上还有VPP、fstack等网络应用和网络协议栈的实现。
Netmap: 是一个高效的收发报文的 I/O 框架,已经集成在 FreeBSD 的内部,也可以在Linux下编译使用。和DPDK不同的是,Netmap并没有彻底地从内核接管网卡,而是采用一个巧妙的Netmap ring结构来从内核管理的网卡上直接接收和发送数据。也就是把网卡的缓冲区从内核映射到用户空间,并且实现了自己的发送和接收报文的netmap_ring来对应网卡的 NIC ring。
XDP: 绕过了内核的协议栈部分,在继承内核的I/O部分的基础上,提供了介于原有内核和完整内核旁路之间的另一种选择。它允许开发者使用BPF(Berkeley Packet Filter)程序来定义如何处理网络数据包。使用BPF程序的一个重要优点是,如果业务代码出现错误,它不会导致整个内核崩溃。这种隔离性提高了系统的稳定性和安全性。
平台增强
在IA(Intel Architecture)多核通用处理器平台上的许多技术也可以被用来提高网络的处理能力,大致可以归纳为以下几个方面:
多核及亲和性: 利用CPU的亲和性能够使一个特定的任务在指定的核上尽量长时间地运行而不被迁移到其他处理器。在多核处理器上,每个核自己本身会缓存着任务使用的信息,而任务可能会被操作系统调度到其他核上。每个核之间的L1、L2缓存是非共享的,如果任务频繁地在各个核间进行切换,就需要不断地使原来核上的缓存失效,如此一来缓存命中率就低了。当绑定核后,任务就会一直在指定的核运行,大大增加了缓存的命中率。
Intel数据直接 I/O 技术: DDIO(Data Direct I/O)技术能够支持以太网控制器将I/O流量直接传输到处理器高速缓存(LLC)中,缩短将其传输到系统内存的路线,从而降低功耗和I/O延迟。
在没有DDIO的系统中,来自NIC的报文通过DMA最先进入处理器的系统内存,当CPU核需要处理这个报文时,它会从内存中读取该报文至缓存,也就是说在CPU真正处理报文之前,就发生了内存的读和写。在具有DDIO的系统中,来自NIC的报文直接传输至缓存,对于报文的数据处理来说,避免了多次的内存读写,在提高性能、降低延时的同时也降低了功耗。
大页内存: 可以采用2MB或者1GB的大页。它可以减少页表级数,也就是地址转换时访问内存的次数,同时减少TLB不命中的情况。一个使用了2MB内存的程序,TLB中只需要存有1个页表表项就能保证不会出现TLB不命中的情况。对于网络包处理程序,内存需要高频访问,在设计程序时,可以利用大页尽量独占内存防止内存溢出,提高TLB命中率。
NUMA: NUMA(非一致性内存访问架构)是一种计算机架构,其中多个处理器模块(节点)各自拥有独立的本地内存和I/O设备,节点之间通过高速互连进行通信。由于访问本地内存的速度快于访问其他节点的内存,NUMA调度器旨在尽量将进程安排在同一节点的CPU上运行,以提高性能,只有在负载过高时才会将进程迁移到其他节点。
DPDK
DPDK的广泛应用很好地证明了IA多核处理器可以解决高性能数据包处理的需求。其核心思想可以归纳成以下几个方面:
-
轮询模式: DPDK 轮询网卡是否有网络报文的接收或放送,这样避免了传统网卡驱动的中断上下文的开销,当报文的吞吐量大的时候,性能及延时的改善十分明显。
-
用户态驱动: DPDK 通过用户态驱动的开发框架在用户态操作设备及数据包,避免了不必要的用户态和内核态之间的数据拷贝和系统调用。同时,为开发者开拓了更广阔的天地,比如快速迭代及程序优
化。 -
降低访问存储开销: 高性能数据包处理意味着处理器需要频繁访问数据包。显然降低访问存储开销可以有效地提高性能。DPDK使用大页降低TLB 未命中率,保持
缓存对齐
避免处理器之间缓存交叉访问,利用预取
等指令提高缓存的访问率等。 -
亲和性和独占: 利用线程的CPU亲和绑定的方式,将特定的线程指定在固定的核上工作,可以避免线程在不同核间频繁切换带来的开销,提高可扩展性,更好地达到并行处理提高吞吐量的目的。
-
批处理: DPDK 使用批处理的概念,一次处理多个包,降低了一个包处理的平均开销。
-
利用IA新硬件技术: 利用vector指令(向量指令)并行处理多个报文,原子指令避免锁开销等。例如,使用AVX指令可以在一个时钟周期内处理多个32位或64位数据,从而加速计算过程。
-
充分挖掘外部设备潜能: 以网卡为例,一些网卡的功能,例如 RSS、Flow director、TSO等技术可以被用来加速网络的处理。比如RSS可以将包负载分担到不同的网卡队列上,DPDK 多线程可以分别直接处理不同队列上的数据包。
基于上面的技术点,DPDK建议用户使用两种开发模型,具体也可见报文转发模型:
Run-to-Completion模型: 在该模型下,每个执行单元在多核系统中分别运行在各自的逻辑核上,也就是多个核上执行一样的逻辑程序。为了可线性扩展吞吐量,可以利用网卡的硬件分流机制,如RSS,把报文分配到不同的硬件网卡队列上,每个核针对不同的队列轮询,执行一样的逻辑程序,从而提高单位时间处理的网络量。
Run-to-Completion 模型有许多优势,但是针对单个报文的处理始终集中在一个CPU核,无法利用其他CPU核,并且程序逻辑的耦合性太强,可扩展性有限。Pipeline模型的引入正好弥补了这个缺点
Pipeline模型: 在该模型下,每个执行单元分别运行在不同的CPU核上,各个执行单元之间通过环形队列连接。这样的设计可以将报文的处理分为多步,将不同的工作交给不同的模块,使得代码的可扩展性更强。
DPDK由一系列可用于包处理的软件库组成,能够支持多种类型设备,包括以太网设备、加密设备、事件驱动设备等,这些设备以PMD(Polling Mode Driver)的形式存在于DPDK中,并提供了一系列用于硬件加速的软件接口。
- 核心库(Core Libraries):这部分是DPDK程序的基础,它包括系统抽象内存管理、无锁环、缓存池等。
- 流分类(Packet Classification):支持精确匹配、最长匹配和通配符匹配,提供常用的包处理查表操作。
- 软件加速库(Accelerated SWLibraries):一些常用的包处理软件库的集合,比如IP分片、报文重组、排序等。 Stats:提供用于查询或通知统计数据的组件。
- QoS:提供网络服务质量相关组件,比如限速(Meter)和调度(Scheduler)。
- 数据包分组架构(PacketFramework):提供了搭建复杂的多核Pipeline模型的基础组件。
NFV和NFC基础设施
DPDK 为高性能数据面的处理提供了可能。DPDK 已作为 NVF(Network Function Virtualization) 和NFC(Network Function Containerization)即网络功能虚拟化和容器化,的重要组件,参与到NFV和NFC的基础设施建设中。下面就从NFV、NFC和平台设备抽象两方面展开描述NFV和NFC基础设施的特质。
网络功能虚拟化
网络功能虚拟化的一个重要特征是软硬件解耦。当网络功能从专用硬件向通用硬件平台乃至虚拟通用硬件平台转移时,作为承载各种网络功能的基础设施层(NFVi),其重要性也越发突出。
对于一个VNF(虚拟化网络功能)应用,快速地从NFVi获取网络帧是后续业务逻辑的基础,这就涉及虚拟主机接口(Host I/O Interface)。从NFVi的视角来看,虚拟主机接口是其面向虚拟主机提供的北向虚接口;从VNF的视角来看,虚拟主机接口是承载其运行的主机I/O设备。
北向接口:与上层应用、管理系统交互,提供资源管理和监控功能。
配合不同类型虚拟主机接口,NFVi 提供了不同的数据面策略,Bypass 和 Relay就是两种比较典型的数据面策略。从数据面的角度,前者依赖外部系统提供NFVi数据面,绕过了整个Host软件部分,后者则由Host软件提供NFVi数据面。
Bypass:在数据流转过程中,某些处理步骤被跳过,直接将数据从源头传输到目的地。提高了传输效率,减少延迟,但可能降低一些处理功能的使用。
Relay:数据通过中间节点进行转发,通常会经过一定的处理或检查。可以实现更复杂的功能,例如协议转换和负载均衡,但可能引入延迟。
网络设备按照不同的虚拟化实现方式,可以粗略地分为全模拟(FullyEmulated)、半虚拟化(Para-Virtualized)和硬直通(Pass-thru)。对于主流的VMM及其网络设备,DPDK支持相对都比较完善。全模拟和半虚拟化类型的虚拟主机接口主要与 NFVi 的 Relay 策略一起工作,而硬直通 NFVi 一般采用Bypass 策略。
在网络功能虚拟化场景下,对网络带宽都有一定的要求。相对于全模拟设备方式,半虚拟化和硬直通是更为主流的使用方式。下面以QEMU/KVM开源VMM为例,分别介绍这两种方式的特点和优势。
半虚拟化: Virtio是QEMU/KVM下的半虚拟化设备框架,允许虚拟机与宿主机之间直接交换信息,减少了上下文切换开销,从而提升I/O性能。Virtio定义了一组标准化的设备接口,使得不同的虚拟设备(如网络、磁盘等)可以通过统一的方式进行管理和访问。
Virtio支持多种设备类型,包括Virtio-Net(网络设备)、Virtio-Block(块设备)和Virtio-SCSI(SCSI设备)等。Virtio使用环形缓冲区(ring buffer)作为通信机制,虚拟机和宿主机通过该缓冲区交换数据。虚拟机将请求放入环中,宿主机则从环中获取请求并处理。
硬直通: 一个硬件设备的能力直接赋予某一个虚拟主机,使得虚拟主机可以获得和裸机下极其相近的性能。但一台设备如果需要被赋予多个虚拟主机,就需要设备能够被切片,或者说能有设备及总线级别的多路复用技术。
SR-IOV是一种使PCIe设备能够切分成多个虚拟功能(VF)的技术,允许这些VF被视为独立的PCIe设备。硬直通则是利用SR-IOV实现高性能的数据传输,并通过平台I/O虚拟化技术(如IOMMU)解决设备访问和中断处理的问题。
硬直通技术解决的问题:
- 设备BAR配置空间的访问:通过不同的方法来实现对设备的配置。
- DMA内存请求的直达:需要平台特性支持,通常依赖于IOMMU。
- 中断请求的送达:同样需要IOMMU的支持,确保设备中断能正确地发送到虚拟CPU(vCPU)。
IOMMU(输入输出内存管理单元):支持DMA重映射和中断重映射。允许DMA请求使用虚拟地址访问主存,不再要求物理地址的连续性。支持将设备发送的中断重定向到合适的vCPU,提高虚拟机的响应能力。
从虚拟机到容器的网络I/O虚拟化
虚拟化技术将网络功能与底层硬件彻底解耦,不仅为开发人员提供了更加灵活的开发环境,为产品更新提供了更短的迭代周期,更保证了网络功能的高度隔离性、易用性及安全性。凭借这些优势,基于Hypervisor的虚拟化技术,比如KVM、HyperV,已广泛应用于NFV环境中。然而,近两年随着网络速度的不断上升、互联网数据量的不断膨胀,一种更加轻量级的虚拟化技术——容器虚拟化,正逐渐广泛应用于NFV场景中。
基于 Hypervisor 的虚拟化技术通过一层中间软件将固定的硬件资源抽象为众多的虚拟化资源(通常称为虚拟机)。每一个虚拟机都具有独立的操作系统,运行于完全独立的上下文中。因此,通常一台主机上运行有多个操作系统。
而与完全模拟硬件资源的Hypervisor虚拟化不同,容器虚拟化是一种资源隔离技术。利用操作系统的命名空间和Cgroups资源分配,容器虚拟化将用户程序隔离于不同的资源实体中运行,而每一个资源实体就称为容器。在容器虚拟化中,同一主机上的所有容器共享主机操作系统,不对底层的硬件资源进行模拟。相比于虚拟机,容器是一种更加轻量级的虚拟化,能更高效地利用系统资源,有更快的启动时间,更易于部署和维护。
面向虚拟机的I/O加速方案: 在基于 Hypervisor 的虚拟化中,I/O 虚拟化主要包括两种方式,一种是基于SR-IOV的硬件虚拟化方案,另一种是基于Virtio的半虚拟化。针对这两种方式,DPDK提供了相应的两种用户态加速方案,分别为DPDK PF驱动、DPDK VF驱动
和基于DPDK的软件交换机
网卡的SR-IOV技术将一个网卡虚拟化成许多VF,并将VF暴露给虚拟机,使每个虚拟机都可以独享虚拟网卡资源,从而获得能够与本机性能媲美的 I/O 性能。为加速基于 SR-IOV的网络,DPDK 为其提供了相应的用户态的网卡驱动:PF 驱动和VF 驱动。通过在虚拟机中使用用户态的VF驱动,虚拟机就可以实现更高效的网络I/O性能。
在Virtio环境中,前端的Virtio驱动和后端的vHost设备互联,利用主机端的虚拟交换机实现网络通信。为加速基于Virtio的网络,DPDK为前端的Virtio PCI设备提供了用户态驱动(Virtio Polling Mode Driver),并且支持了用户态的vHost 设备——vHost-user。通过在虚拟机中使用Virtio PMD和软件交换机中使用vHost-user,可以大幅提高虚拟机的网络I/O性能。
面向容器的网络I/O加速方案: Linux为容器提供了十分丰富的网络I/O方案,如主机网络和Docker默认使用的网桥,然而基于内核的网络 I/O 方案在性能上往往无法满足追求高吞吐、低延迟的NFV 业务的需求。并且,因为虚拟机和容器是两种完全不同的虚拟化技术,因此面向虚拟机的网络I/O加速方案无法直接应用于容器网络。此外,现有大部分的基于容器的应用都运行于Kubernetes环境下,这就要求I/O加速方案也能同时支持Kubernetes运行环境。
为了增强容器网络I/O的性能,Intel为Docker容器也提出了与虚拟化I/O加速方案类似的两个I/O加速方案:SR-IOV网络插件
和Virtio-user
。
SR-IOV网络插件将网卡的SR-IOV技术应用到容器中,通过将网卡VF加入容器的网络命名空间,容器运行时可以直接看到网卡。利用DPDK的用户态VF驱动,容器运行时可以实现高速的网络收发包。
与传统的 Virtio 驱动不同,Virtio-user 允许用户态进程直接访问虚拟设备,而不需要经过内核的网络栈。在使用Virtio-user的软件交换机方案中,每个容器运行时都有一个 Virtio-user 设备,此设备将 DPDK 使用的大页内存共享给后端的软件交换机;在共享的内存空间中创建Virtio Ring结构,并按照Virtio规范定义的Ring操作方式实现通信。
OVS-DPDK
Open vSwitch(OVS)是一个产品级质量的多层虚拟交换机,基于Apache 2.0许可。OVS 的设计初衷是支持可编程自动化网络大规模部署及拓展。
OVS 基本功能包括如下4大方面。
- 自动化控制:OVS 支持OpenFlow,用户可以通过ovs-ofctl使用OpenFlow协议连接交换机实现查询和控制。
- QoS:支持拥塞管理和流量整形。
- 安全:支持VLAN 隔离、流量过滤等功能,保证了虚拟网络的安全性。
- 监控:支持Netflow、SFlow、SPAN、RSPAN等网络监控技术。
OVS在实现中分为用户空间和内核空间两个部分。用户空间拥有多个组件,它们主要负责实现数据交换和OpenFlow流表功能,还有一些工具用于虚拟交换机管理、数据库搭建以及和内核组件的交互。内核组件主要负责流表查找的快速通道。OVS的核心组件及其关联关系如图:
下图显示了OVS数据通路的内部模块图:
ovs-vswitchd主要包含ofproto、dpif、netdev模块,ofproto模块实现openflow的交换机
,dpif模块抽象一个单转发路径,netdev模块抽象网络接口(无论物理的还是虚拟的)。
openvswitch.ko主要由数据通路模块组成,里面包含着流表。流表中的每个表项由一些匹配字段和要做的动作组成。DPDK加速的思想就是专注在这个数据通路上。
VPP(矢量报文处理)
VPP 是一个模块化和可扩展的软件框架,用于创建网络数据平面应用程序。更重要的是,VPP 代码为现代通用处理器平台而生,并把重点放在优化软件和硬件接口上,以便用于实时的网络输入输出操作和报文处理。
VPP充分利用通用处理器优化技术,包括矢量指令(例如Intel SSE和AVX)及I/O和CPU缓存间的直接交互(例如 Intel DDIO),以达到最好的报文处理性能。利用这些优化技术的好处是:使用最少的CPU核心指令和时钟周期处理每个报文。在最新的Intel Xeon-SP处理器上,可以达到Tbps的处理性能。
输入节点轮询(或中断驱动)接口的接收队列,得到批量报文。接着把这些报文按照下个节点功能组成一个矢量(vector)或一帧(frame)。
比如,输入节点收集所有IPv4的报文并把它们传递给ip4-input节点,输入节点收集所有IPv6的报文并把它们传递给ip6-input节点。当ip6-input节点被调度时,它取出这一帧报文,利用双次循环(dual-loop)、四次循环(quad-loop)及预取报文到 CPU 缓存技术处理报文,以达到最优性能。这能够通过减少缓存未命中数来有效利用CPU缓存。当ip6-input节点处理完当前帧的所有报文后,会把报文传递到后续不同的节点。如果某报文校验失败,就被传送到error-drop节点,正常报文被传送到ip6-lookup节点。一帧报文依次通过不同的图形节点,直到它们被interface-output节点发送出去。
从性能的角度看,首要好处是可以优化CPU指令缓存的使用。当前帧的第一个报文加载当前节点的指令到指令缓存,当前帧的后续报文就可以“免费”使用指令缓存。VPP充分利用了CPU的超标量结构,使报文内存加载和报文处理交织进行,更有效地利用CPU处理流水线。
CPU的超标量结构允许处理器在每个时钟周期内发射和执行多条指令,通过多个执行单元并行处理,从而提高计算性能和资源利用率。这种架构能够有效地实现指令级并行性
VPP也充分利用了CPU的预测执行功能来达到更好的性能。预测重用报文间的转发对象(比如邻接表和路由查找表),以及预先将报文内容加载到CPU的本地数据缓存供下一次循环使用,这些有效使用计算硬件的技术,使得VPP可以利用更细粒度的并行性。