一、网络发展和DPDK
在上篇分析过网络应用对DPDK出现的影响。而具体体现在技术上,从最简单来看就是从C10K到c100K甚至更多。而相应的计算的发展也从挖掘单CPU的性能发展到了瓶颈,同样,对于网络设备也遇到了类似的问题。而目前解决问题的方法硬件上就是多核多CPU(分布式中多台电脑也可以理解成宏观上的多CPU),而在软件上就是云的虚拟化。
而在原来的网络IO处理路径中,本身就已经较长,再经过虚拟化(含NFV)后,导致整体上的一些辅助工作大消耗了时间,而且过长的路径也会导致更多的意外发生,另外即使在正常的情况下,过长路径也会造成缓存命中失效的可能性大大增加。而学过计算机的人都知道二八原理,缓存命中失效会使得耗费的时间更长。
DPDK针对上面的这些问题,主要解决了IO控制(硬中断和CPU消耗等)、不再通过内核路径(拷贝、上下文切换、锁、过滤、Cache等)。这样,一方面大大提高了IO的处理速度,又解决了路径长所引起的内核瓶颈及相关的意外因素。所以从某种程度上可以理解DPDK是在网卡到用户态绕过了内核直接桥接过去,达成了一个旁路。这其实和实际生活中,如果电路必须过某个复杂的电缆柜,但又不知道内部的情况,直接飞线过去有相通之处(当然,飞线是不好的)。不过在Linux社区上,提供了旁路机制,不过这玩意儿没弄好,所以现在其实就是半死活。至于DPDK和它们的关系和影响,就不得而知了。
传统的网络编程数据流是从:网卡->驱动->协议栈->Socket->应用。
而DPDK则是:网卡->DPDK->DPDK基础库->应用
有过工程管理管理经验的都知道,路径越长,不可控的风险越大。简单就是王道,这才是王道。
二、技术分析
DPDK的处理网络通信的技术特点主要有:
1、轮询
传统的内核处理一般有两种情况,中断和轮询两种方式。中断的耗时对海量数据传输来说太长了。而使用轮询则可以大幅降低中断引起的上下文开销。
2、用户态驱动
前面提到过,路径越长,需要数据传递的路径越长,就不可避免的产生内存复制和系统调用。而直接到用户态,就避免是大量不必要的数据复制。
3、CPU的亲和性设计
现代计算机几乎很难遇到单核单CPU的机器了,所以如果设置CPU的亲和性,保证Cache的命中率就是一个很好的设计。
4、降低访存开销
TLB(Translation Lookaside Buffer),转译后备缓冲区。DPDK利用大页内存减少TLB的丢失命中并利用内存多通道交错访问提高内存带宽利用率
5、软件优化
这个就比较好理解了,缓冲的行对齐(多线程中的伪共享),数据预读以及批量处理等。
在DPDK中,最显著的部分就是上层的用户态库,它主要分成以下几个部分:
1、核心库(core Libraries)
环境抽象层(EAL)在Linux基础上进行初始化,其中有巨页分配、缓冲区和队列分配、无锁操作、CPU亲和性绑定等。它就是绕过内核的关键,直接通过UIO或VFIO技术将PCI设备地址映射到用户空间。同时,为了更好的处理数据,它还创建了适应环境的内存池、缓冲区管理、内存复制、定时器和环境缓冲等。
2、平台库( platform)
这些个主要是包括KNI、能耗管理 和IVSHMEM接口。KNI主要是用进行内核态和用户态间的协议栈处理。能耗管理主要处理休眠状态和CPU频率等;IVSHMEM则提供了虚拟机内存共享管理机制,包括映射世面为IVSHMEME设备池并传递给QEMU。
3、轮询驱动库
虽然DPDK支持混杂中断驱动,但一般还是用轮询为主。它支持物理和虚拟网卡,随着各大硬件厂商的跟进,目前基本整个行业生态都支持了。
4、QoS库
这个用来做一些辅助服务的,比如一些ACL(通配符匹配),精确匹配什么的,还有一些报文转发如限速(METER)和调度(SCHED)等等。
DPDK高性能实现的代码基础:
1、通过HugePage降低TLB miss
这个其实比较好理解,小的缓冲容易溢出,就容易Miss,大的就次数少很多。而在Linux中把默认的4K提到2M及以上,数量级会下降很多,命中率会提高很多。
2、SNA(Shared-nothing Architecture)
其实就是在并行计算中的分治法,减少全局共享。同时,对NUMA体系下不跨Node远程使用内存。
3、SIMD(Single Instruction Multiple Data)
这个学过计算机体系结构的应该很清楚。DPDK中也尽量利用了这个优势,通过批处理和向量编程来增加单位时间内的数据包处理。
4、使用更先进的库接口
这个其实很简单,就是把普适性的库,有针对的进行完善和修改。包括对一些硬件有针对性的处理。
5、优化编译执行
这个就是比较常见的了,比如分支预测(内核和c++新标准),Cache预取,内存对齐(包括前面提到的伪共享和跨Cache Line),常量优化以及直接调用Cpu指令等等。
这里简单分析一下NUMA,Non Uniform Memory Access,非统一内存访问,它和UMA,Uniform Memory Access,一致性内存访问相对应。其实说的通俗一些就是本地管理和分布式管理的区别。但为了解决分布式管理出现的缓慢和无法访问所有存储器的情况就针对性的提出了NUMA,把存储分为本地存储和远端存储。它的优势在于既兼顾上SMP模式下的系统拷贝和易于管理又继承了分布式(MPP)模式的可扩充性。
另外还一个重要的问题,DPDK毕竟不是一个官方统一的基础库,而一个和硬件、OS版本及相关参数配置有关的一个基础框架。所以在实际部署时也要考虑到这些因素,特别是硬件的支持。一般来说,重点需要处理CPU核心的隔离和中断的转移屏蔽。即使无法完全处理好中断也要尽量减少中断的次数。
三、UIO机制
Linux提供了UIO(https://lwn.net/Articles/232575/)机制即user-space drivers,它可以通过mmap实现和网卡的通信。
在linux/uio_driver.h有如下的数据结构体定义:
struct uio_info {
char *name;
char *version;
struct uio_mem mem[MAX_UIO_MAPS];
long irq;
unsigned long irq_flags;
void *priv;
irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
int (*open)(struct uio_info *info, struct inode *inode);
int (*release)(struct uio_info *info, struct inode *inode);
/* Internal stuff omitted * /
};
写过内核驱动的再结合着文档就比较容易写出一个用户态的驱动。具体的方法可以参看相关的技术资料,此处不再赘述。
四、总结
上面把DPDK的相关技术进行了整体的分析,这样就可以从整体上对DPDK的技术栈进行一个认知。从这篇之后,将从两个角度来分析DPDK,一个是应用的角度,如何使用,从分析例程的角度来探究DPDK运行的技术;另外一个角度是从源码的角度,由浅入深的分析相关的源码和上层的应用情况进行结合分析。
作始也简。