1. DPDK介绍

1) 简介

DPDK全称Intel Data Plane Development Kit,是intel提供的数据层开发套件工具集,是Intel 处理器架构下用户空间高效的数据包处理的库函数和驱动。通俗地说,就是包数据处理加速的软件库

 

DPDK不同于Linux系统以通用性设计为目的,而是专注于高性能的处理网络应用中的数据包。具体体现在DPDK程序是运行在用户空间上收发数据包,绕过了Linux内核协议栈对数据包处理过程。

相比原生 Linux(Native Linux),采用Intel DPDK技术后能够大幅提升IPV4的转发性能,可以让用户在移植 包处理应用 时(从基于NPU的硬件迁移到基于Intel x86的平台上),获得更好的成本和性能优势。同时可以采用统一的平台部署不同的服务,如应用处理,控制处理和包处理服务。

2) 技术优点

 通过UIO技术将报文拷贝到应用空间处理,规避不必要的内存拷贝和系统调用,便于快速迭代优化。

 通过大页内存HUGEPAGE,降低cache miss(访存开销),利用内存多通道交错访问提高内存访问有效带宽,即提高命中率,进而提高cpu访问速度。

 通过CPU亲和性,绑定网卡和线程到固定的core,减少cpu任务切换。特定任务可以被指定只在某个核上工作,避免线程在不同核间频繁切换,保证更多的cache命中。

 通过无锁队列,减少资源竞争。cache行对齐,预取数据,多元数据批量操作。

 通过轮询可在包处理时避免中断上下文切换的开销。

3) DPDK、网卡、用户应用程序、内核之间的关系

 PMD:Pool Mode Driver,轮询模式驱动,通过非中断,以及数据帧进出应用缓冲区内存的零拷贝机制,提高发送/接受数据帧的效率。

 流分类:Flow Classification,为N元组匹配和LPM(最长前缀匹配)提供优化的查找算法。

 环队列:Ring Queue,针对单个或多个数据包生产者、单个数据包消费者的出入队列提供无锁机制,有效减少系统开销。

 MBUF缓冲区管理:分配内存创建缓冲区,并通过建立MBUF对象,封装实际数据帧,供应用程序使用。

 EAL:Environment Abstract Layer,环境抽象(适配)层,PMD初始化、CPU内核和DPDK线程配置/绑定、设置HugePage大页内存等系统初始化。

2. 源程序包组成

1) Makefile &&CONFIG

MakeFile文件主要位于位于 $(RTE_SDK)/mk 中。此处留在后面第5节进行讨论

配置模板位于 $(RTE_SDK)/config。这些模板描述了为每个目标启用的选项。 配置文件许多可以为DPDK库启用或禁用的选项,包括调试选项。用户应该查看配置文件并熟悉这些选项。配置文件同样也用于创建头文件,创建的头文件将位于新生成的目录中。一般可以根据用户编译的编译器和操作系统来直接选择配置项。

2) Lib库

库文件源码位于目录$(RTE_SDK)/lib中。按照惯例,库指的是为应用程序提供API的任何代码。通常,它会生成一个(.a)文件,这个目录中可能也保存一些内核模块。

Lib常用库文件包含以下内容

lib

+-- librte_cmdline # 命令行接口

+-- librte_distributor # 报文分发器

+-- librte_eal # 环境抽象层

+-- librte_ether # PMD通用接口

+-- librte_hash # 哈希库

+-- librte_ip_frag # IP分片库

+-- librte_kni # 内核NIC接口

+-- librte_kvargs # 参数解析库

+-- librte_lpm # 最长前缀匹配库

+-- librte_mbuf # 报文及控制缓冲区操作库

+-- librte_mempool # 内存池管理器

+-- librte_meter # QoS metering 库

+-- librte_net # IP相关的一些头部

+-- librte_power # 电源管理库

+-- librte_ring # 软件无锁环形缓冲区

+-- librte_sched # QoS调度器和丢包器库

+-- librte_timer # 定时器库

3) 应用程序

应用程序是包含 main() 函数的源文件。 他们位于 $(RTE_SDK)/app 和 $(RTE_SDK)/examples 目录中。

常用示例文件:

examples

+-- cmdline # Example of using the cmdline library

+-- exception_path # Sending packets to and from Linux TAP device

+-- helloworld # Basic Hello World example

+-- ip_reassembly # Example showing IP reassembly

+-- ip_fragmentation # Example showing IPv4 fragmentation

+-- ipv4_multicast # Example showing IPv4 multicast

+-- kni # Kernel NIC Interface (KNI) example

+-- l2fwd # L2 forwarding 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 apps using multiple DPDK processes

+-- qos_meter # QoS metering example

+-- qos_sched # QoS scheduler and dropper example

+-- timer # Example of using librte_timer library

+-- vmdq_dcb # Example of VMDQ and DCB receiving

+-- vmdq # Example of VMDQ receiving

+-- vhost # Example of userspace vhost and switch

3. DPDK架构分析

 

【DPDK】DPDK(数据层开发套件)简介--编辑中_应用程序

4. Dpdk基础库介绍

1) EAL 环境适配层

环境抽象层为底层资源如硬件和内存空间的访问提供了接口。 这些通用的接口为APP和库隐藏了不同环境的特殊性。 EAL负责初始化及分配资源(内存、PCI设备、定时器、控制台等等)。

典型函数:rte_eal_init

抄自dpdk网站:

EAL提供的典型服务有:

• DPDK的加载和启动:DPDK和指定的程序链接成一个独立的进程,并以某种方式加载

• CPU亲和性和分配处理:DPDK提供机制将执行单元绑定到特定的核上,就像创建一个执行程序一样。

• 系统内存分配:EAL实现了不同区域内存的分配,例如为设备接口提供了物理内存。

• PCI地址抽象:EAL提供了对PCI地址空间的访问接口

• 跟踪调试功能:日志信息,堆栈打印、异常挂起等等。

• 公用功能:提供了标准libc不提供的自旋锁、原子计数器等。

• CPU特征辨识:用于决定CPU运行时的一些特殊功能,决定当前CPU支持的特性,以便编译对应的二进制文件。

• 中断处理:提供接口用于向中断注册/解注册回掉函数。

• 告警功能:提供接口用于设置/取消指定时间环境下运行的毁掉函数。

2) Ring 库

环形缓冲区支持队列管理。rte_ring并不是具有无限大小的链表,它具有如下属性:

先进先出(FIFO)

最大大小固定,指针存储在表中

无锁实现

多消费者或单消费者出队操作

多生产者或单生产者入队操作

批量出队 - 如果成功,将指定数量的元素出队,否则什么也不做

批量入队 - 如果成功,将指定数量的元素入队,否则什么也不做

突发出队 - 如果指定的数目出队失败,则将最大可用数目对象出队

突发入队 - 如果指定的数目入队失败,则将最大可入队数目对象入队

单生产者入队:

 

【DPDK】DPDK(数据层开发套件)简介--编辑中_linux_02

单消费者出队

 

【DPDK】DPDK(数据层开发套件)简介--编辑中_linux_03

3) Mempool 库

DPDK提供了内存池机制,使得内存的管理的使用更加简单安全。在设计大的数据结构时,都可以使用mempool分配内存,同时,mempool也提供了内存的获取和释放等操作接口。对于数据包mempool甚至提供了更加详细的接口-rte_pktmbuf_pool_create()

 mempool的创建

内存池的创建使用的接口是rte_mempool_create()。在仔细分析代码之前,先说明一下mempool的设计思路:在DPDK中,总体来说,mempool的组织是通过3个部分实现的

 mempool头结构。mempool由名字区分,挂接在struct rte_tailq_elem rte_mempool_tailq全局队列中,可以根据mempool的名字进行查找,使用rte_mempool_lookup()接口即可。这只是个mempool的指示结构,mempool分配的内存区并不在这里面,只是通过物理和虚拟地址指向实际的内存地址。

 mempool的实际空间。这就是通过内存分配出来的地址连续的空间,用来存储mempool的obj对象。主要利用rte_mempool_populate_default()进行创建

 ring队列。其作用就是存放mempool中的对象指针,提供了方便存取使用mempool的空间的办法。

 mempool的常见使用是获取元素空间和释放空间。

 rte_mempool_get可以获得池中的元素,其实就是从ring取出可用元素的地址。

 rte_mempool_put可以释放元素到池中。

 rte_mempool_in_use_count查看池中已经使用的元素个数

 rte_mempool_avail_count 查看池中可以使用的元素个数

4) 定时器库

定时器库为DPDK执行单元提供定时器服务,使得执行单元可以为异步操作执行回调函数。定时器库的特性如下:

定时器可以周期执行,也可以执行一次。

need-to-insert-img

定时器可以在一个核心加载并在另一个核心执行。但是必须在调用rte_timer_reset()中指定它。

定时器提供高精度(取决于检查本地核心的定时器到期的rte_timer_manage()的调用频率)。

如果应用程序不需要,可以在编译时禁用定时器,并且程序中不调用rte_timer_manage()来提高性能。

具体使用参考:http://blog.csdn.net/linzhaolover/article/details/9410529

5. 库的编译(Makefile)及使用(API使用方案)

need-to-insert-img

makefile位于目录: /examples/xxx/Makefile。 文件中真正必须的为两个变量APP和SRCS-y,前者为示例程序编译生成的目标文件名称,后者为要编译的源文件。

另外两个必须的为makefile文件rte.vars.mk和rte.extapp.mk。前者定义一些全局的编译打包链接用到的选项,如CFLAGS、ASFLAGS、LDFLAGS等变量,头文件位置和库路径等和体系架构相关编译选项。后者rte.extapp.mk内部又包含了重要的mk/rte.app.mk文件,首先变量LDLIBS初始化为DPDK核心编译生成的所有静态库文件。

makefile编写

在DPDK中,Makefiles的套路是

1.在文件开头,包含$(RTE_SDK)/mk/rte.vars.mk

2.设置RTE构建系统的变量,比如设置RTE_SDK和RTE_TARGET环境变量

3.包含指定的 $(RTE_SDK)/mk/rte.XYZ.mk,其中XYZ 可以填写app, lib, extapp, extlib, obj,依赖构建的目标类型.

3.1 应用程序类型(application)

- rte.app.mk

- rte.extapp.mk

- rte.hostapp.mk

3.2 库类型 (library)

- rte.lib.mk

- rte.extlib.mk

- rte.hostlib.mk

3.3 安装类型(install)

rte.install.mk

没有生成任何文件,仅仅用于创建链接和将文件拷贝到安装目录.

3.4 内核模块(Kernel Module )

rte.module.mk

用于构建内核模块,在dpdk开发包框架中.

3.5 Objects类型

- rte.obj.mk

- rte.extobj.mk

3.6 Misc类型

- rte.doc.mk

- rte.gnuconfigure.mk

- rte.subdir.mk

4.包含用户自定义的规则和变量

常用的变量

系统构建变量

RTE_SDK

RTE_TARGET

编译变量(库文件,库目录,C编译标志,C++编译标志)

CFLAGS: C编译标志. 使用 += 为变量添加内容

LDFLAGS: 链接标志

need-to-insert-img

CPPFLAGS: C++编译标志. 使用 += 为变量添加内容

LDLIBS: 待添加的库文件的列表

SRC-y: 源文件列表

警告变量

WERROR_CFLAGS:在dpdk示例makefile中是加上此标志的.

CFLAGS += $(WERROR_CFLAGS)

6. 性能优化

1) 内存

 内存拷贝:不要在数据层程序中使用libc

通过Linux应用程序环境,DPDK中可以使用许多libc函数。 这可以简化应用程序的移植和控制层的开发。 但是,这些功能中有许多不是为了性能而设计的。 诸如memcpy() 或 strcpy() 之类的函数不应该在数据层中使用。 要复制小型结构体,首选方法是编译器可以优化一个更简单的技术。

对于经常调用的特定函数,提供一个自制的优化函数也是一个好主意,该函数应声明为静态内联。DPDK API提供了一个优化的rte_memcpy() 函数。

 内存申请

need-to-insert-img

libc的其他功能,如malloc(),提供了一种灵活的方式来分配和释放内存。 在某些情况下,使用动态分配是必要的,但是建议不要在数据层使用类似malloc的函数,因为管理碎片堆可能代价高昂,并且分配器可能无法针对并行分配进行优化。

如果您确实需要在数据层中进行动态分配,最好使用固定大小对象的内存池。 这个API由librte_mempool提供。 这个数据结构提供了一些提高性能的服务,比如对象的内存对齐,对对象的无锁访问,NUMA感知,批量get/put和percore缓存。 rte_malloc() 函数对mempools使用类似的概念。

 内存区域的并发访问

need-to-insert-img

几个lcore对同一个内存区域进行的读写(RW)访问操作可能会产生大量的数据高速缓存未命中,这代价非常昂贵。 通常可以使用per-lcore变量来解决这类问题。例如,在统计的情况下。 至少有两个解决方案:

使用 RTE_PER_LCORE 变量。注意,在这种情况下,处于lcore x的数据在lcore y上是无效的。

使用一个表结构(每个lcore一个)。在这种情况下,每个结构都必须缓存对齐。

如果在同一缓存行中没有RW变量,那么读取主要变量可以在不损失性能的情况下在内核之间共享。

 NUMA

need-to-insert-img

在NUMA系统上,由于远程内存访问速度较慢,所以最好访问本地内存。 在DPDK中,memzone,ring,rte_malloc和mempool API提供了在特定内存槽上创建内存池的方法。

有时候,复制数据以优化速度可能是一个好主意。 对于经常访问的大多数读取变量,将它们保存在一个socket中应该不成问题,因为数据将存在于缓存中。

 跨存储器通道分配

need-to-insert-img

现代内存控制器具有许多内存通道,可以支持并行数据读写操作。 根据内存控制器及其配置,通道数量和内存在通道中的分布方式会有所不同。 每个通道都有带宽限制,这意味着如果所有的存储器访问都在同一通道上完成,则存在潜在的性能瓶颈。

默认情况下, Mempool Library 分配对象在内存通道中的地址。

2) lcore之间的通信

need-to-insert-img

为了在内核之间提供基于消息的通信,建议使用提供无锁环实现的DPDK ring API。

该环支持批量访问和突发访问,这意味着只需要一次昂贵的原子操作即可从环中读取多个元素(请参阅 Ring 库 )。

使用批量访问操作时,性能会大大提高。

出队消息的代码算法可能类似于以下内容:

#define MAX_BULK 32

while (1) {

/* Process as many elements as can be dequeued. */

count = rte_ring_dequeue_burst(ring, obj_table, MAX_BULK, NULL);

if (unlikely(count == 0))

continue;

my_process_bulk(obj_table, count);

}

3) PMD 驱动

DPDK轮询模式驱动程序(PMD)也能够在批量/突发模式下工作,允许在发送或接收功能中对每个呼叫的一些代码进行分解。

避免部分写入。 当PCI设备通过DMA写入系统存储器时,如果写入操作位于完全缓存行而不是部分写入操作,则其花费较少。 在PMD代码中,已采取了尽可能避免部分写入的措施。

 低报文延迟

need-to-insert-img

传统上,吞吐量和延迟之间有一个折衷。 可以调整应用程序以实现高吞吐量,但平均数据包的端到端延迟通常会因此而增加。 类似地,可以将应用程序调整为平均具有低端到端延迟,但代价是较低的吞吐量。

为了实现更高的吞吐量,DPDK尝试通过突发处理数据包来合并单独处理每个数据包的成本。

以testpmd应用程序为例,突发大小可以在命令行上设置为16(也是默认值)。 这允许应用程序一次从PMD请求16个数据包。 然后,testpmd应用程序立即尝试传输所有接收到的数据包,在这种情况下是全部16个数据包。

在网络端口的相应的TX队列上更新尾指针之前,不发送分组。 当调整高吞吐量时,这种行为是可取的,因为对RX和TX队列的尾指针更新的成本可以分布在16个分组上, 有效地隐藏了写入PCIe 设备的相对较慢的MMIO成本。 但是,当调优为低延迟时,这不是很理想,因为接收到的第一个数据包也必须等待另外15个数据包才能被接收。 直到其他15个数据包也被处理完毕才能被发送,因为直到TX尾指针被更新,NIC才知道要发送数据包,直到所有的16个数据包都被处理完毕才被发送。

为了始终如一地实现低延迟,即使在系统负载较重的情况下,应用程序开发人员也应避免处理数据包。 testpmd应用程序可以从命令行配置使用突发值1。 这将允许一次处理单个数据包,提供较低的延迟,但是增加了较低吞吐量的成本。

4) 锁和原子操作

need-to-insert-img

原子操作意味着在指令之前有一个锁定前缀,导致处理器的LOCK#信号在执行下一条指令时被断言。 这对多核环境中的性能有很大的影响。

可以通过避免数据平面中的锁定机制来提高性能。 它通常可以被其他解决方案所取代,比如percore变量。 而且,一些锁定技术比其他锁定技术更有效率。 例如,Read-Copy-Update(RCU)算法可以经常替换简单的rwlock

7. 遇到的问题及解决方法

https://www.xuebuyuan.com/1149388.html

https://blog.csdn.net/hz5034/article/details/78811445



链接:https://www.jianshu.com/p/86af81a10195

 

 

​高性能网络技术​

随着云计算产业的异军突起,网络技术的不断创新,越来越多的网络设备基础架构逐步向基于通用处理器平台的架构方向融合,从传统的物理网络到虚拟网络,从扁平化的网络结构到基于 SDN 分层的网络结构,无不体现出这种创新与融合。

这在使得网络变得更加可控制和成本更低的同时,也能够支持大规模用户或应用程序的性能需求,以及海量数据的处理。究其原因,其实是高性能网络编程技术随着网络架构的演进不断突破的一种必然结果。

C10K 到 C10M 问题的演进​​​

如今,关注的更多是 C10M 问题(即单机 1 千万个并发连接问题)。很多计算机领域的大佬们从硬件上和软件上都提出了多种解决方案。从硬件上,比如说,现在的类似很多 40Gpbs、32-cores、256G RAM 这样配置的 X86 服务器完全可以处理 1 千万个以上的并发连接。

但是从硬件上解决问题就没多大意思了,首先它成本高,其次不通用,最后也没什么挑战,无非就是堆砌硬件而已。所以,抛开硬件不谈,我们看看从软件上该如何解决这个世界难题呢?

这里不得不提一个人,就是 Errata Security 公司的 CEO Robert Graham,他在 Shmoocon 2013 大会上很巧妙地解释了这个问题。有兴趣可以查看其 YouTube 的演进视频: ​​C10M Defending The Internet At Scale​​。

【DPDK】DPDK(数据层开发套件)简介--编辑中_linux_04

他提到了 UNIX 的设计初衷其实为电话网络的控制系统而设计的,而不是一般的服务器操作系统,所以,它仅仅是一个数据负责数据传送的系统,没有所谓的控制层面和数据层面的说法,不适合处理大规模的网络数据包。最后他得出的结论是:

OS 的内核不是解决 C10M 问题的办法,恰恰相反 OS 的内核正式导致 C10M 问题的关键所在。

为什么这么说?基于 OS 内核的数据传输有什么弊端?​

1、中断处理。当网络中大量数据包到来时,会产生频繁的硬件中断请求,这些硬件中断可以打断之前较低优先级的软中断或者系统调用的执行过程,如果这种打断频繁的话,将会产生较高的性能开销。

2、内存拷贝。正常情况下,一个网络数据包从网卡到应用程序需要经过如下的过程:数据从网卡通过 DMA 等方式传到内核开辟的缓冲区,然后从内核空间拷贝到用户态空间,在 Linux 内核协议栈中,这个耗时操作甚至占到了数据包整个处理流程的 57.1%。

3、上下文切换。频繁到达的硬件中断和软中断都可能随时抢占系统调用的运行,这会产生大量的上下文切换开销。另外,在基于多线程的服务器设计框架中,线程间的调度也会产生频繁的上下文切换开销,同样,锁竞争的耗能也是一个非常严重的问题。

4、局部性失效。如今主流的处理器都是多个核心的,这意味着一个数据包的处理可能跨多个 CPU 核心,比如一个数据包可能中断在 cpu0,内核态处理在 cpu1,用户态处理在 cpu2,这样跨多个核心,容易造成 CPU 缓存失效,造成局部性失效。如果是 NUMA 架构,更会造成跨 NUMA 访问内存,性能受到很大影响。

5、内存管理。传统服务器内存页为 4K,为了提高内存的访问速度,避免 cache miss,可以增加 cache 中映射表的条目,但这又会影响 CPU 的检索效率。

综合以上问题,可以看出内核本身就是一个非常大的瓶颈所在。那很明显解决方案就是想办法绕过内核。

解决方案探讨​

针对以上弊端,分别提出以下技术点进行探讨。

1、控制层和数据层分离。将数据包处理、内存管理、处理器调度等任务转移到用户空间去完成,而内核仅仅负责部分控制指令的处理。这样就不存在上述所说的系统中断、上下文切换、系统调用、系统调度等等问题。

2、使用多核编程技术代替多线程技术,并设置 CPU 的亲和性,将线程和 CPU 核进行一比一绑定,减少彼此之间调度切换。

3、针对 NUMA 系统,尽量使 CPU 核使用所在 NUMA 节点的内存,避免跨内存访问。

4、使用大页内存代替普通的内存,减少 cache-miss。

5、采用无锁技术解决资源竞争问题。

经很多前辈先驱的研究,目前业内已经出现了很多优秀的集成了上述技术方案的高性能网络数据处理框架,如 6wind、windriver、netmap、dpdk 等,其中,Intel 的 dpdk 在众多方案脱颖而出,一骑绝尘。

【DPDK】DPDK(数据层开发套件)简介--编辑中_linux_05

dpdk 为 Intel 处理器架构下用户空间高效的数据包处理提供了库函数和驱动的支持,它不同于 Linux 系统以通用性设计为目的,而是专注于网络应用中数据包的高性能处理。

也就是 dpdk 绕过了 Linux 内核协议栈对数据包的处理过程,在用户空间实现了一套数据平面来进行数据包的收发与处理。在内核看来,dpdk 就是一个普通的用户态进程,它的编译、连接和加载方式和普通程序没有什么两样。


dpdk 的突破​

相对传统的基于内核的网络数据处理,dpdk 对从内核层到用户层的网络数据流程进行了重大突破,我们先看看传统的数据流程和 dpdk 中的网络流程有什么不同。

传统 Linux 内核网络数据流程:

Copy

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

dpdk 网络数据流程:

Copy

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

下面就具体看看 dpdk 做了哪些突破?

UIO (用户空间的 I/O 技术)的加持。

dpdk 能够绕过内核协议栈,本质上是得益于 UIO 技术,通过 UIO 能够拦截中断,并重设中断回调行为,从而绕过内核协议栈后续的处理流程。

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

【DPDK】DPDK(数据层开发套件)简介--编辑中_数据_06

内存池技术

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

大页内存管理

dpdk 实现了一组大页内存分配、使用和释放的 API,上层应用可以很方便使用 API 申请使用大页内存,同时也兼容普通的内存申请。

无锁环形队列

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

poll-mode网卡驱动

DPDK网卡驱动完全抛弃中断模式,基于轮询方式收包,避免了中断开销。

**NUMA **

dpdk 内存分配上通过 proc 提供的内存信息,使 CPU 核心尽量使用靠近其所在节点的内存,避免了跨 NUMA 节点远程访问内存的性能问题。

CPU 亲和性

dpdk 利用 CPU 的亲和性将一个线程或多个线程绑定到一个或多个 CPU 上,这样在线程执行过程中,就不会被随意调度,一方面减少了线程间的频繁切换带来的开销,另一方面避免了 CPU 缓存的局部失效性,增加了 CPU 缓存的命中率。

多核调度框架

dpdk 基于多核架构,一般会有主从核之分,主核负责完成各个模块的初始化,从核负责具体的业务处理。

除了上述之外,dpdk 还有很多的技术突破,可以用下面这张图来概之。

【DPDK】DPDK(数据层开发套件)简介--编辑中_应用程序_07

dpdk 的应用​​​

dpdk 作为优秀的用户空间高性能数据包加速套件,现在已经作为一个“胶水”模块被用在多个网络数据处理方案中,用来提高性能。如下是众多的应用。

【DPDK】DPDK(数据层开发套件)简介--编辑中_数据_08

数据面(虚拟交换机):​​​

OVS

Open vSwitch 是一个多核虚拟交换机平台,支持标准的管理接口和开放可扩展的可编程接口,支持第三方的控制接入。

​https://github.com/openvswitch/ovs​

VPP

VPP 是 cisco 开源的一个高性能的包处理框架,提供了 交换/路由 功能,在虚拟化环境中,使它可以当做一个虚拟交换机来使用。在一个类 SDN 的处理框架中,它往往充当数据面的角色。经研究表明,VPP 性能要好于 ovs+dpdk 的组合,但它更适用于NFV,适合做特定功能的网络模块。

​https://wiki.fd.io/view/VPP​

Lagopus

Lagopus 是另一个多核虚拟交换的实现,功能和 OVS 差不多,支持多种网络协议,如 Ethernet,VLAN,QinQ,MAC-in-MAC,MPLS 和 PBB,以及隧道协议,如 GRE,VxLan 和 GTP。

​https://github.com/lagopus/lagopus/blob/master/QUICKSTART.md​

Snabb

Snabb 是一个简单且快速的数据包处理工具箱。

​https://github.com/SnabbCo/snabbswitch/blob/master/README.md​

数据面(虚拟路由器):​

OPENCONTRAIL

一个集成了 SDN 控制器的虚拟路由器,现在多用在 OpenStack 中,结合 Neutron 为 OpenStack 提供一站式的网络支持。

​http://www.opencontrail.org/​

CloudRouter

一个分布式的路由器。

​https://cloudrouter.org/​

用户空间协议栈​​#​

mTCP

mTCP 是一个针对多核系统的高可扩展性的用户空间 TCP/IP 协议栈。

​https://github.com/eunyoung14/mtcp/blob/master/README​

IwIP

IwIP 针对 RAM 平台的精简版的 TCP/IP 协议栈实现。

​http://git.savannah.gnu.org/cgit/lwip.git/tree/README​

Seastar

Seastar 是一个开源的,基于 C++ 11/14 feature,支持高并发和低延迟的异步编程高性能库。

​http://www.seastar-project.org/​

f-stack

腾讯开源的用户空间协议栈,移植于 FreeBSD协议栈,粘合了 POSIX API,上层应用(协程框架,Nginx,Redis),纯 C 编写,易上手。

​https://github.com/f-stack/f-stack​

总结​

dpdk 绕过了 Linux 内核协议栈,加速数据的处理,用户可以在用户空间定制协议栈,满足自己的应用需求,目前出现了很多基于 dpdk 的高性能网络框架,OVS 和 VPP 是常用的数据面框架,mTCP 和 f-stack 是常用的用户态协议栈。很多大公司都在使用 dpdk 来优化网络性能。