0概述
在传统的电信IT产品中,高性能网络接口一般采用特殊的硬件模块来实现,比如网络处理器、ASIC、FPGA等等。这些特殊硬件模块一般会采用特殊的架构和指令集对网络数据收发过程进行优化以达到更好的性能。然而,这也相应使得开发和维护这些模块的成本非常的昂贵,同时还有一个无法解决的问题是基于这些特殊硬件模块实现的网络接口不能移植到云中,因为它们跟硬件的耦合度太高了。摩尔定律的出现,使得通用处理器的性能得到了极大的提升,这也为基于通用处理器实现高性能网络接口提供了可能,同时也为移植到云中提供了前提条件。
图1 网络接口实现的发展趋势
本文对基于通用X86架构处理器和Linux下的传统网络接口实现模式进行了分析,同时针对其不足提出了一种高性能网络接口实现方案-HPNI.
1传统网络接口实现分析
传统网络接口实现如图2所示,Linux下传统网络接口处理流程一般包含以下几个步骤:
图2 传统网络接口实现
(1)系统启动之后,内核中的驱动程序进行相应的资源分配以及网卡寄存器配置,比如分配数据包缓冲区,使能DMA传输通道等等;
(2)网卡初始化完成以后,当网卡从网络收到数据包的时候,会将数据包通过DMA方式传送到内核中的数据包缓冲区中,并生成相应的数据包描述符存放在环形队列里面;
(3)网卡触发硬中断通知内核,内核在中断处理程序中产生相应软中断给应用程序收包任务;
(4)应用程序收包任务通过系统调用收取数据包,数据包也将从内核空间拷贝到用户空间;
(5)收包任务通过共享队列把数据包传递给处理任务;
(6)处理任务通过共享队列把处理好的数据包传递给发包任务;
(7)发包任务通过系统调用把数据包传递给内核,通过采用一次内存拷贝实现;
(8)驱动程序根据存放在环形队列里面的数据包描述符启动相应的DMA传输任务;
(9)数据包传输完成后,网卡触发中断通知内核做相应处理。
下面先对传统网络接口的性能关键点进行分析。
1.1中断和系统调用
Linux下传统网络接口实现的一个关键因素是中断技术,网卡通过中断与内核保持同步。但中断处理会引入很大的性能损失,因为当CPU处理中断的时候,它必须储存和恢复自己的状态,进行上下文切换以及在进入和退出中断处理程序的时候可能引入缓存(cache)操作。在进行大流量数据包处理的时候,由于CPU被频繁地中断,由此带来的性能损失会非常严重。
当使用Linux系统提供的标准socket接口来实现网络通信时,会触发大量的系统调用,当应用程序通过socket系统调用进行网络通信时,需要经历从用户态到内核态的切换以及其反向过程,在切换过程当中需要进行上下文的保存,比如对相关寄存器进行堆栈压栈操作,保存当前指令指针等等。这些操作都会消耗系统资源,当处理高速的数据流量时,随着系统调用数量的急剧增加而导致系统资源的大量消耗。
1.2内存相关性能问题
1.2.1内存拷贝
在使用标准套接字进行数据包收发时,数据在应用程序和内核之间进行传递必须通过内存拷贝来完成,原因在于用户空间的内存和内核空间的内存是处在物理内存不同的位置上。内存拷贝是一个非常消耗资源的过程,当需要收发的数据包流量很大时,这种开销将会极大地影响系统的性能。
1. 2.2内存访问效率
在绝大多数情况下,应用程序并不直接通过物理内存地址来访问内存,而是采用虚拟地址,当CPU收到内存访问指令时会先把虚拟地址转换成实际的物理地址,然后进行内存的访问操作。这种方式已经被普遍接受,甚至被称作是IT时代最杰出的发明之一,但是这种非直接内存访问方式并不是没有代价的,地址的翻译需要通过页表来完成,页表通常情况下是储存在内存当中的,访问速度很慢,为了解决这个问题,大部分系统都采用了TLB(Tralaslation Lookaside Buffer)的方式,最近触发的一些地址翻译结果都会保存在TLB中,TLB实际上使用的是CPU的缓存(cache),访问速度非常快,然而cache容量小,只有最近访问的一部分页表项能保存下来,因此出现了“TLB Miss”;当CPU发现当前虚拟地址无法在TLB里面找到相对应的表项时,就引入了一个TLB Miss,此时CPU需要回到内存当中的页表进行查找,性能会显著降低。因此当程序需要进行频繁的内存操作时,需要尽量减少TLBMiss的次数。当前系统定义的页面大小一般是4k字节,当应用程序使用比如2G这样的大内存时,总共需要50多万个页表项,这个数目是相当庞大的,同时因为只有一小部分的表项能够装载在TLB中,因此TLB Miss的几率也很大。另外,一般情况下程序的虚拟内存空间都是连续的,但其对应的物理内存空间却不一定是连续的,这样会导致一次虚拟内存寻址操作可能需要进行多次物理内存寻址操作才能完成,这也会成倍地增加内存访问消耗的时间。
1.3多核亲和力
多核系统对提高系统的性能有很大的帮助,当前大部分系统的调度算法会把当前的任务放到最空闲的核上执行,这样的好处是能够增加CPU资源的利用率,但因为每个CPU核心都有自己独立的寄存器和cache,当任务从一个核心迁移到另一个核心时,会引发大量的核问切换开销,比如上下文切换,cache miss等等。另外,对于使用NUMA(Non-Uniform Memory Access)架构的系统而言,核间切换的开销会更大,在SMP(Svmmetric Multiprocessing)架构下,所有核心是通过共享接口访问内存的,因此每个核心访问内存的速度是一样的,但在NUMA架构下,核心对内存的访问分为本地访问和远程访问。核心访问本地内存的速度要比访问远端内存的速度快很多,当任务从核心A切换到核心B的时候,如果它仍然使用之前在A上分配的内存,那么其内存访问模式会从本地模式切换成远程模式,从而引起内存访问速度的下降。
1.4共享队列的访问
当把数据包从一个任务传递到另外一个任务的时候,需要用到共享队列。通常情况下,在访问共享队列的时候会用到Mutex锁来保证访问的一致性。当应用程序申请Mutex锁失败之后会陷入内核态中睡眠,当锁可用之后再从内核态切换到用户态执行,这里也引入了上下文切换的开销,而且当数据流量很大的时候,相应的开销也会非常大。为了消除这类开销,业界也提出了一些改进的方法,比如自旋锁(spinlock),自旋锁一直在用户态运行,不会陷入内核态中,因此也不会产生上下文切换的开销,但是它还是存在一些弊端:一方面可能造成死锁,如果一个线程拿到锁之后被意外销毁,其它等待此锁的线程会发生死锁;另一方面,当共享队列和线程数量猛增时,锁的数量也会同时增加,对锁的管理会给系统带来很大的负担。
2 HPNI实现原理
2.1传统网络接口实现模式的不足
从上述分析可以得出传统网络接口的实现主要有以下几点不足:
(1)上下文切换开销太多,这些开销主要是由中断、系统调用、锁以及核间切换引入;
(2)内存拷贝的开销太多;
(3)内存访问效率不高,缺乏相应的优化;
(4)采用带锁共享队列进行数据共享,引入额外开销;
(5)收发包操作必须经过Linux内核单线程完成,无法扩展成多核多线程模式从而提高性能。
2. 2 HPNI的原理
针对上述不足,提出了一种新型的网络接口实现模式,如图3所示。
图3 HPNI网络接口实现
HPNI主要包括以下几项关键技术:
(1)通过Linux提供的UIO框架,实现了网卡用户空间驱动程序,UIO能够把网卡设备内存空间通过文件系统的方式传递给用户空间,比如dev/uioXX,因此用户空间程序能够读取到设备地址段并映射到用户空间内存中,比如通过mmap()。通过上述方式可以在用户空间程序中完成驱动程序的功能。这种方法的优点是去掉了内存拷贝,同时因为所有工作都在用户空间完成,也节省了系统调用的开销。
(2)关掉网卡中断,驱动程序采用轮询方式,消除中断引起的开销。
(3)用户空间的数据包缓冲区采用huge page分配的连续物理内存区,通过LinuX提供的huge page接口能够分配大于4k的页,从而减少页表的大小,降低TLB Miss发生的概率。另外连续的物理内存块也能减少地址转换引起的查表次数,进一步提高性能。
(4)任务线程和CPU核心之间采用静态绑定,比如接收包线程绑定1核,处理包线程绑定2核,发送包线程绑定3核,从而消除核间切换产生的开销。
另外,对于NUMA架构的CPU,每个任务都使用本地内存,进一步提高内存访问速度。
(5)通过CAS(Compare And Swap)原子操作,多个任务可以在不加锁的情况下对共享队列进行访问,增加和删除节点。在X86架构下CAS是通过CMPXCHG指令实现的,该指令的作用就是把一个指针指向内存的值同一个给定的值进行比较,如果相等,就对对应内存赋一个新的值,否则不做任何操作。通过上述方法可以实现一种冲突检测机制,当任务发现该队列己经被访问时,主动等待直到队列空闲。无锁队列消除了加锁引起的开销,同时也能避免死锁的情况。
(6)基于网卡RSS(Receive-side Scaling)功能可以平滑扩展成多任务模式,RSS功能可以将收到的数据包基于五元组做哈希运算,从而分发到不同的队列当中进行并行处理,每一个队列可以对应一个收包任务,从而成倍地提高处理性能。
3对比实验及结果分析
3. 1实验一
实验环境描述如下:一台数据包发生器,最大可产生流量为80Mpps的64字节的数据包。一台服务器配置Intel的Sandy Bridge 8核处理器,每个核心2.0GHZ.操作系统采用RedHat Enterprise Linux 6.2.网卡采用Intel 82599 10G以太网控制器。运行的软件包含三个线程,一个收包线程,一个转发线程,一个发送线程。传统网络实现方式下采用了RAWSocket方式直接收发处理层二数据包,如图4所示。
图4 单线程模式下性能比较图
实验结果如图5所示,可以得出传统网络接口实现模式下性能的峰值在180Kpps左右,而且随着网络数据流量的增大,性能呈现下降趋势,主要因为随着网络流量的增加,额外的系统开销也在不断增加。HPNI模式下性能峰值在1.8Mpps左右,而且随着网络流量的增加,性能比较稳定,抗冲击力比较强。
图5 单线程模式下性能测试结果
3.2实验二
采用与实验一相同的硬件环境,同时开启多个相同的任务线程,每个线程在一个任务循环内完成收包、改包、发包的工作,比较两种接口模式在多核多任务配置下的性能。另外,在HPNI模式下同时使能网卡的RSS功能,生成多个队列分别对应每个任务线程,每个任务线程静态绑定一个CPU核心,如图6所示。
图6 多核多线程模式下网络接口性能分析
实验结果如图7所示,在传统网络接口实现模式下,因为受限于Linux内核处理的瓶颈,即使采用了多线程并发,其性能峰值仍然处于180Kpps左右。HPNI却能很好地利用多线程的并发,在网卡RSS功能的配合之下性能得到成倍的提高。也可以看到多核下面HPNI的性能并不是一直随着核数的增加而线性增加的,主要因为CPU内的核心之间并不是完全独立的,它们之间也存在一些共享资源的竞争,比如总线的访问,从而对性能产生一些负面的影响。
图7 多核多线程模式下网络接口性能测试结果
4结语
本文分析了传统Linux下网络接口实现的性能瓶颈,针对其不足提出了一种新型的网络接口实现模式。实验结果表明,HPNI可以达到12Mpps的包转发速率,完全可以胜任核心路由网络以外网络聚合点的工作,比如小型企业网关等。另外,因为HPNI的容量可以动态调整,因此可以以较高的性价比实现各种性能要求的网络转发节点。基于通用处理器和标准操作系统的特性,也使得HPNI能够快速地部署到SDN中。HPNI既可以直接部署在IT server上,也可以部署在虚拟机当中,从而实现高速NFV的功能。当然,HPNI也存在一点不足,因为采用了轮询模式,虽然保证了数据处理的实时性,但也导致了较大的CPU负载,当网络流量很低的时候,系统资源利用率不是很高。后续可以针对此点做一些优化,比如结合机器学习算法对输入数据流量进行预测,当输入流量降低时通过CPU提供的pause指令降低CPU负载,从而降低系统资源的使用。