DPDP 架构和理论

DPDP 架构和理论

原文地址

Introduction to DPDK: Architecture and Principles

概述

 

最近几年大家对Linux网络栈的性能要求越来越高,这也很好理解,大量的数据需要通过网络传输,相应的工作负载不是按天增加而是小时。即使是10G的网卡也没能解决这个问题,原因是Linux内核自身在处理数据包时本身就成为了瓶颈
有一种叫内核bypass的技术可以绕过这些瓶颈(参考 这篇 文章),于是你可以绕过Linux网络栈来处理这些数据包,让应用程序运行在用户态空间然后直接跟网络驱动通讯,这篇文章将要讨论的这种技术叫做 Intel DPDK(Data Plan Development Kit)
虽然有大量的论文介绍DPDK而且内容丰富,但最重要的问题却没有解答,那就是DPDK是如何处理这些数据包的,这些数据包从网络设备到用户的路径是什么
想回答这些问题并不容易,官方文档也给给出解释,我们查阅大量资料并阅读源码
在讨论DPDK如何解决这些问题之前,先回顾一下Linux内核是如何处理数据包的

 

在Linux中处理数据包

当网卡收到第一个数据包时候,会将它送到接收队列中,之后通过DMA机制拷贝到内存
然后系统需要通知新的数据包,将其传送一个特殊的分配缓存中(Linux对每个数据包都会分配一个Buffer),Linux会使用中断机制:当一个新数据来进入系统时会触发几次中断,这个数据包之后会传输到用户空间
很明显这里有瓶颈,当需要处理更多数据包时,会消耗更多资源影响系统性能
前面说了,这些数据包会被保存到一个特殊的分配buffer,更详细的说是 sk_buffer 这个结构体(每个数据包都会被分配一个结构体),当一个数据包进入到用户空间时这个结构体就变成了free。这些操作会导致消耗大量的总线周期(从CPU传输到内存的周期)
sk_buff这个结构体最初设计的目的是为了兼容尽可能多的网络协议,但对于特定的数据格式来说,很多的元数据内容是多余的,复杂的结构体也是导致速度变慢的原因。
当用户空间的应用程序要发送或接受数据包,会执行一个系统调用,然后执行上下文切换到内核空间,再切换回来,这也会消耗大量的系统资源
为了解决这些问题,Linux自2.6版本后包含了NAPI(new API),它将中断和请求合并到一起了
网卡首先会工作在中断模式,一旦有数据包进入网卡,就就会注册自己到一个pull队列中,并关闭中断。系统会周期性的检查这个队列然后批量获取一批数据包处理,一旦数据包被处理网卡就会将他们从队列中删除并将中断模式打开。
这里只是给了一些粗略的描述,细节可以参考 这篇 文章,虽然只是大致的描述了过程不过数据包处理缓慢的原因我们基本上是了解了,下面我们来讨论DPDK是如何解决这些问题的。

 

DPDK 是如何工作的

让我们看看下面这张图

左边是传统的方式,右边是DPDK的实现方式,可以看到右图中内核根本不参与,整个交互过程是在网卡跟特殊的库和驱动之间完成的。
你已经知道DPDK是如何使用的,然后你要知道这些通过网卡进来的数据需要在Linux内核上解绑定,使用
dpdk_nic_bind(或者dodk-devbind)命令,对于早期版本是 ./dpdk_nic_bind.py命令

1
2
ls /sys/bus/pci/drivers/ixgbe
bind  module  new_id  remove_id  uevent  unbind

将设备从驱动上解绑定,需要将设备的总线号写入到解绑定的文件中,类似的绑定设备到另一个驱动,需要将总线号写入到它自己的绑定文件,更相信的信息可以参考 这里 。
DPDK安装手册告诉我们,端口需要被 vfio_pci, igb_uio, or uio_pci_generic driver 管理(我们不打算深入细节,但建议感兴趣的读者可以参考kernel.org的文章: 文章1 和 文章2)
这个驱动可以让用户空间和设备之间进行交换,当然也包含了一个内核模块,但它只是初始化设备和分配PCI接口使用的。
所有的应用程序和网卡之间的通讯都是由DPDK轮询模式驱动PMD组织的,DPDK为所有支持网卡和虚拟网卡的设备提供轮询模式驱动。
DPDK也支持大页内存分配,对于分配大内存块并写入数据是必须的, DPDK大页内存工作方式和DMA是类似的
我们将要讨论更多的细节和细微差距,但现在让我们先看下DPDK处理数据包的主要阶段
1.收到的数据包进入到ring buffer中(在下一节讨论这个问题),应用程序周期的检查这个buffer看是否有新的数据包
2.如果缓冲区中包含了新的包描述,应用程序将使用包描述符中的指针引用特别分配的内存池中的DPDK包缓冲区。
3.如果ring buffer中没有包含任何包,应用程序会在网络设备下进行排队,然后DPDK会再次引用这个ring buffer
之后我们更一步看看DPDK的内部结构

EAL:环境抽象

EAL,环境抽象层,是DPDK的主要概念。
EAL是一系列工具,它可以让DPDK工作在特定的硬件和特定的操作系统之下,DPDK的官方库,依赖包,驱动,EAL的一部分被保存在ret_eal目录。
Linux和BSD系统的驱动和库都被保存在这个目录下了,它也包含了一些用于处理各种处理器架构的头文件,包括:ARM,x86,TILE64,PPC64
当我们通过源码编译好DPDK后就可以通过软件方式访问EAL了

1
make config T=x86_64-native-linuxapp-gcc

上面这个命令可以在x86_64架构中编译成用于Linux的DPDK
EAL绑定DPDK到应用程序,所有的使用DPDK的应用程序必须包含EAL的头文件(参考这个例子)
最常用的头文件包括:
rte_lcore.h — 管理处理器内核跟其插口socket
rte_memory.h — 管理内存
rte_pci.h — 提供接口访问PCI地址空间
rte_debug.h — 提供trace和debug函数(logging,dump_stack等等)
rte_interrupts.h — 处理中断
更多的架构细节和EAL函数可以参考官方文档

队列管理: rte_ring

我们已经说了,网卡收到数据包就会发送到ring buffer中,它被昂做一个接收队列,DPDK上收到的包会被发送到一个叫 ret_ring队列实现库中。下面的库描述信息来自于开发者手册和源码的评论。
ret_ring 是在FreeBSD的ring buffer基础上开发的,如果你看过源码,你会看到下面这个评论:它是派生自FreeBSD的 bufring.c
队列是在FIFO基础上构建的无锁ring buffer,ring buffer是一个指针表,指向内存中保存的对象,指针被分成四种类别:prod_tail, prod_head, cons_tail, cons_head
prod是生产者的缩写,cons是消费者的缩写,生产者在指定的时间内处理数据写入,消费者处理从buffer中删除的数据
tail是用于ring buffer写入数据的地方,head是在给定时间内从buffer中读取数据的地方
从队列中增加和删除元素的主要逻辑如下:
当一个新对象被添加到队列时,ring->prod_tail应该指向ring->prod_head之前指向的地方
这里只是一个简短的概述,更详细的解释ring buffer脚本如何工作的是参考DPDK网站的 开发者手册
这种方法有很多优点,首先数据写入到buffer非常的快,其次从队列中增加或删除大量对象时缓存miss的频率很低,因为指针都被保存在表(table)里了
DPDK ring buffer的缺点是其大小是固定的不能动态增长,由于buffer总是使用最大数量的指针,对于内存消耗来说会超过普通的linked队列

内存管理:rte_mempool

之前提到DPDK需要大页支持,安装手推荐创建2M的大页
这些页被组织成segment,然后被划分到不同的zone中,对象被应用程序和库创建,就像队列和包缓冲区一样,被放置到这些zone中。
这些包含内存池的对象,是通过rte_mempoll库创建的,对象池的大小是固定的,通过rte_ring存储空间对象,并且有对象名字是唯一的
内存对齐技术可以更好的提升性能
虽然访问空闲对象是通过无锁的ring buffer,但系统消耗仍然很高,因为多处理器可以访问ring,所以每次访问时都会执行CAS操作
为了解决瓶颈问题每个CPU都有一个本地的cache,使用这种机制,CPU可以完全访问这些空闲对象cache,当cache满了或者空了,内存池会跟ring buffer交换数据,于是CPU就是频繁的访问经常被使用的对象。

buffer管理:rte_mbuf

在Linux网络栈中,每个网络包都是用sk_buff数据结构来表示的,在DPDK中使用的是rte_mbuf结构,它是在rte_mbuf.h结构体中描述的
DPDK管理buffer的方式让人联想到FreeBSD中的方式,除了一个大的sk_buff结构体,还有很小的rte_mbuf buffer,这些buffer是在DPDK应用程序启动之前被创建的,并保存在内存池中(内存是通过rte_mempoll被创建的)
除了它自己的数据包,每个buffer还包含了元数据(message type, length, data segment starting address),buffer还包含了指向下个buffer的指针,当要处理大量的数据包时候需要这样处理,在这种情况下,包会被结合到一起(就像FreeBSD那样,更多的细节信息参考这里)

其他库:总体概述

前面的章节中,我们已经讨论了基本的DPDK库,除此之外还有大量的其他库,不过一篇文章没有足够的篇幅讨论这么多内存,我们再简短的概述一下
LPM库,DPDK使用 最长匹配前缀 LPM算法,可以根据数据包的IPv4地址做转发,这个库的主要函数是增加和删除IP地址,还有使用LPM算法查询一个新地址。
LPM6库,跟LPM类似,它是用于用于执行IPv6地址的
其他库在hash函数基础上提供类似的功能,使用 rte_hash,你可以用唯一键搜索大量的数据,这个库可以对包进行分类和发布。
rte_timer库可以让你执行异步的函数,定时器可以一次或周期性的被执行。

结论

我们在这篇文章中讨论了DPDK的内部设备和原理,但这远不够因为这种这个主题太复杂太广了,一篇文章根本不够,我们将在后续的文章中继续讨论这个主题,并讨论DPDK的方方面面。
我们很乐意在下面的评论中回答你的问题,如果你有任何DPDK的使用经验,我们也可以乐意听听你的想法
如果想更进一步了解,想参考下面的链接
所有DPDK库的描述
简要介绍DPDK的功能并和其他框架进行比较
给初学者的DPDK介绍
介绍DPDK架构的pdf文档

14 次阅读

发表评论

电子邮件地址不会被公开。