DPDK学习开篇
1.前言
说来想学习DPDK已经是很久的事情了,奈何总是被所谓的紧急的事情耽误,故而决心学习一下,记录一下以便总结,暂时工作还没完全用到, 但是DPDK总会成为一个大趋势,借此学习一下前辈的设计思想,希望能有所启发
2.DPDK介绍
Intel® DPDK 全称 __Intel Data Plane Development Kit__,是intel提供的数据平面开发工具集,为Intel architecture(IA)处理器架构下用户空间高效的数据包处理提供库函数和驱动的支持,它不同于Linux系统以通用性设计为目的,而是专注于网络应用中数据包的高性能处理。其工作在用户层,取代传统Linux系统中的网络数据报文处理。但需要注意的是,DPDK提供的是高性能处理报文的能力,而不是对报文的处理。这也提供了我们自定义用户协议栈的能力
3.DPDK 优势
1)轮询与中断
起初的纯轮询模式是指收发包完全不使用任何中断,集中所有运算资源用于报文处理。DPDK纯轮询模式是指收发包完全不使用中断处理的高吞吐率的方 式。DPDK所有的收发包有关的中断在物理端口初始化的时候都会关 闭,也就是说,CPU这边在任何时候都不会收到收包或者发包成功的中 断信号,也不需要任何收发包有关的中断处理。具体收发包流程参见之后的文章单独说明。网络应用中可能存在的潮汐效应,在某些时间段网络数据 流量可能很低,甚至完全没有需要处理的包,这样就会出现在高速端口 下低负荷运行的场景,而完全轮询的方式会让处理器一直全速运行,明 显浪费处理能力和不节能。因此在DPDK R2.1和R2.2陆续添加了收包中 断与轮询的混合模式的支持,类似NAPI的思路,用户可以根据实际应 用场景来选择完全轮询模式,或者混合中断轮询模式。而且,完全由用 户来制定中断和轮询的切换策略,比如什么时候开始进入中断休眠等待 收包,中断唤醒后轮询多长时间,等等。
2)多线程编程:
多线程编程早已不是什么新鲜的事物了,多线程的初衷是提高整体应用程序的性能,但是如果不加注意,就会将多线程的创建和销毁开销,锁竞争,访存冲突,cache失效,上下文切换等诸多消耗性能的因素引入进来。为了进一步提高性能,就必须仔细斟酌考虑线程在CPU不同核上的分布情况,这也就是常说的多核编程。多核编程和多线程有很大的不同:多线程是指每个CPU上可以运行多个线程,涉及到线程调度、锁机制以及上下文的切换;而多核则是每个CPU核一个线程,核心之间访问数据无需上锁。为了最大限度减少线程调度的资源消耗,需要将Linux绑定在特定的核上,释放其余核心来专供应用程序使用。DPDK的线程基于pthread接口创建,属于抢占式线程模型,受内核 调度支配。DPDK通过在多核设备上创建多个线程,每个线程绑定到单 独的核上,减少线程调度的开销,以提高性能。DPDK的线程可以作为控制线程,也可以作为数据线程。在DPDK 的一些示例中,控制线程一般绑定到MASTER核上,接受用户配置,并 传递配置参数给数据线程等;数据线程分布在不同核上处理数据包。同时还需要考虑CPU特性和系统是否支持NUMA架构,如果支持的话,不同插槽上CPU的进程要避免访问远端内存,尽量访问本端内存。
3)CPU亲核性:
当处理器进入多核架构后,自然会面对一个问题,按照什么策略将 任务线程分配到各个处理器上执行。众所周知的是,这个分配工作一般 由操作系统完成。负载均衡当然是比较理想的策略,按需指定的方式也 是很自然的诉求,因为其具有确定性。简单地说,CPU亲和性(Core affinity)就是一个特定的任务要在某 个给定的CPU上尽量长时间地运行而不被迁移到其他处理器上的倾向 性。这意味着线程可以不在处理器之间频繁迁移。这种状态正是我们所 希望的,因为线程迁移的频率小就意味着产生的负载小。将线程与CPU绑定,最直观的好处就是提高了CPU Cache的命中 率,从而减少内存访问损耗,提高程序的速度。在Linux内核中,所有的线程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与亲和性相关度最 高的是cpus_allowed位掩码。这个位掩码由n位组成,与系统中的n个逻 辑处理器一一对应。具有4个物理CPU的系统可以有4位。如果这些CPU 都启用了超线程,那么这个系统就有一个8位的位掩码。如果针对某个线程设置了指定的位,那么这个线程就可以在相关的 CPU上运行。因此,如果一个线程可以在任何CPU上运行,并且能够根 据需要在处理器之间进行迁移,那么位掩码就全是1。实际上,在Linux 中,这就是线程的默认状态。DPDK通过把线程绑定到逻辑核的方法来避免跨核任务中的切换开 销,但对于绑定运行的当前逻辑核,仍然可能会有线程切换的发生,若 希望进一步减少其他任务对于某个特定任务的影响,在亲和的基础上更 进一步,可以采取把逻辑核从内核调度系统剥离的方法。
4)大页表:
默认下Linux采用4KB为一页,页越小内存越大,页表的开销越大,页表的内存占用也越大。CPU有TLB(Translation Lookaside Buffer)成本高所以一般就只能存放几百到上千个页表项。如果进程要使用64G内存,则64G/4KB=16000000(一千六百万)页,每页在页表项中占用16000000 * 4B=62MB。如果用HugePage采用2MB作为一页,只需64G/2MB=2000,数量不在同个级别。而DPDK采用HugePage,在x86-64下支持2MB、1GB的页大小,几何级的降低了页表项的大小,从而减少TLB-Miss。
5)无锁机制:
实际上DPDK内部也有读写锁,LINUX系统本身也支持无锁操作,并且DPDK内部的无锁机制实现原理同LINUX系统提供的无锁机制的实现原理类似。两者都采用无锁环形队列的方式,采用环形队列的好处是,当一个数据元素被用掉后,其余数据元素不需要移动其存储位置,从而减少拷贝,提高效率。LINUX系统如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以 保证数据的正确性。但是,如果有多个读写用户访问环形缓冲区,那么 必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。DPDK的无锁环形队列无论是单用户读写还是多用户读写都不需要使用互斥锁保护。
5)cache预取:
处理器从一级Cache读取数据需要3~5个时 钟周期,二级是十几个时钟周期,三级是几十个时钟周期,而内存则需 要几百个时钟周期。DPDK必须保证所有需要读取的数据都在Cache中,否则一旦出现Cache不命中,性能将会严重下降。为了保证这点,DPDK采用 了多种技术来进行优化,预取只是其中的一种
6)利用UIO技术:
为了让驱动运行在用户态,Linux提供UIO机制。使用UIO可以通过read感知中断,通过mmap实现和网卡的通讯
4.数据包处理流程
1)Linux传统方式处理数据包流程:
硬件中断--->取包分发至内核线程--->软件中断--->内核线程在协议栈中处理包--->处理完毕通知用户层
用户层收包-->网络层--->逻辑层--->业务层
2)DPDK处理数据包流程:
硬件中断--->放弃中断流程
用户层通过设备映射取包--->进入用户层协议栈--->逻辑层--->业务层
5.学习目标
理解DPDK的内存机制, 以及处理数据包的流程