Linux转发性能评估与优化-转发瓶颈分析与解决方案(补遗)

补遗

关于 络接收的软中断负载均衡,已经有了成熟的方案,但是该方案并不特别适合数据包转发,它对服务器的小包处理非常好,这就是RPS。我针对RPS做了一个patch,提升了其转发效率。

线速问题

很多人对这个线速概念存在误解。认为所谓线速能力就是路由器/交换机就像一根 线一样。而这,是不可能的。应该考虑到的一个概念就是延迟。数据包进入路由器或者交换机,存在一个核心延迟操作,这就是选路,对于路由器而言,就是路由查找,对于交换机而言,就是查询MAC/端口映射表,这个延迟是无法避开的,这个操作需要大量的计算机资源,所以不管是路由器还是交换机,数据包在内部是不可能像在线缆上那样近光速传输的。类比一下你经过十字街头的时候,是不是要左顾右盼呢

       那么,设备的线速能力怎么衡量呢果一个数据包经过一个路由器,那么延迟必览无疑,可是设备都是有队列或者缓冲区的,那么试想一个数据包紧接一个数据包从输入端口进入设备,然后一个数据包紧接一个数据包从输出端口发出,这是可以做到的,我们对数据包不予编 ,因此你也就无法判断出来的数据包是不是刚刚进去的那个了,这就是线速。

       我们可以用电容来理解转发设备。有人可能会觉得电容具有通高频阻低频的功效,我说的不是这个,所以咱不考虑低频,仅以高频为例,电容具有存储电荷的功能,这就类似存储转发,电容充电的过程类似于数据包进入输入队列缓冲区,电容放电的过程类似于数据包从输出缓冲区输出,我们可以看到,在电流经过电容的前后,其速度是不变的,然而针对具体的电荷而言,从电容放出的电荷绝不是刚刚在在另一侧充电的那个电荷,电容的充电放电拥有固有延迟。

       我们回到转发设备。对于交换机和路由器而言,衡量标准是不同的。

       对于交换机而言,线速能力是背板总带宽,因为它的查表操作导致的延迟并不大,大量的操作都在数据包通过交换矩阵的过程,因此背板带宽直接导致了转发效率。而对于路由器,衡量标准则是一个端口每秒输入输出最小数据包的数量,假设数据包以每秒100个进入,每秒100个流出,那么其线速就是100pps。

       在写这个方案的前晚,有一个故事。我最近联系到了初中时一起玩摇滚玩音响的超级铁的朋友,他现在搞舞台设计,灯光音响之类的。我问他在大型舞台上,音箱摆放的位置不同,距离后级,前置,音源也不同,怎么做到不同声道或者相同声道的声音同步的,要知道,好的耳朵可以听出来毫秒级的音差…他告诉我要统一到达时间,即统一音频流到达各个箱子的时间,而这要做的就是调延迟,要求不同位置的箱子路径上要有不同的延迟。这对我的设计方案的帮助是多么地大啊。

       然后,在第二天,我就开始整理这个令人悲伤最终心碎的Linux转发优化方案。

问题综述

Linux内核协议栈作为一种软路由运行时,和其它通用操作系统自带的协议栈相比,其效率并非如下文所说的那样非常低。然而基于工业路由器的评判标准,确实是低了。
 
       市面上各种基于Linux内核协议栈的路由器产品,甚至 上也有大量的此类文章,比如什么将Linux变成路由器之类的,无非就是打开ip_forward,加几条iptables规则,搞个配置起来比较方便的WEB界面…我想说这些太低级了,甚至超级低级。我很想谈一下关于专业路由器的我的观点,但是今天是小小的生日,玩了一天,就不写了。只是把我的方案整理出来吧。

瓶颈分析概述

1.DMA和内存操作

我们考虑一下一个数据包转发流程中需要的内存操作,暂时不考虑DMA。
*)数据包从 卡拷贝到内存
*)CPU访问内存读取数据包元数据
*)三层 头修改,如TTL
*)转发到二层后封装MAC头
*)数据包从内存拷贝到输出 卡
这几个典型的内存操作为什么慢什么我们总是对内存操作有这么大的意见为访问内存需要经过总线,首先总线竞争(特别在SMP和DMA下)就是一个打群架的过程,另外因为内存自身的速度和CPU相比差了几个数量级,这么玩下去,肯定会慢啊!所以一般都是尽可能地使用CPU的cache,而这需要一定的针对局部性的数据布局,对于数据包接收以及其它IO操作而言,由于数据来自外部,和进程执行时的局部性利用没法比。所以必须采用类似Intel I/OAT的技术才能改善。

1.1.Linux作为服务器时

1.2.Linux作为转发设备时

需要采用DMA映射交换的技术才能实现零拷贝。这是Linux转发性能低下的根本。由于输入端口的输入队列和输出端口的输出队列互不相识,导致了不能更好的利用系统资源以及多端口数据路由到单端口输出队列时的队列锁开销过大,总线争抢太严重。DMA影射交换需要超级棒的数据包队列管理设施,它用来调度数据包从输入端口队列到输出端口队列,而Linux几乎没有这样的设施。

虽然近年在路由器领域有人提出了输入队列管理,但是这项技术对于Linux而言就是另一个世界,而我,把它引入了Linux世界。

2. 卡对数据包队列Buff管理

在Linux内核中,几乎对于所有数据结构,都是需要时alloc,完毕后free,即使是kmem_cache,效果也一般,特别是对于高速线速设备而言(skb内存拷贝,若不采用DMA,则会频繁拷贝,即便采用DMA,在很多情况下也不是零拷贝)。

       即使是高端 卡在skb的buffer管理方面,也没有使用完全意义上的预分配内存池,因此会由于频繁的内存分配,释放造成内存颠簸,众所周知,内存操作是问题的根本,因为它涉及到CPU Cache,总线争抢,原子锁等,实际上,内存管理才是根本中的根本,这里面道道太多,它直接影响CPU cache,后者又会影响总线…从哪里分配内存,分配多少,何时释放,何时可以重用,这就牵扯到了内存区域着色等技术。通过分析Intel千兆 卡驱动,在我看来,Linux并没有做好这一点。

3.路由查找以及其它查找操作

Linux不区分对待路由表和转发表,每次都要最长前缀查找,虽然海量路由表时trie算法比hash算法好,但是在路由分布畸形的情况下依然会使trie结构退化,或者频繁回溯。路由cache效率不高(查询代价太大,不固定大小,仅有弱智的老化算法,导致海量地址访问时,路由cache冲突链过长),最终在内核协议栈中下课。

       如果没有一个好的转发表,那么Linux协议栈在海量路由存在时对于线速能力就是一个瓶颈,这是一个可扩展性问题。

       另外,很多的查询结果都是可以被在一个地方缓存的,但是Linux协议栈没有这种缓存。比如,路由查询结果就是下一跳,而下一跳和输出 卡关联,而输出 卡又和下一跳的MAC地址以及将要封装的源MAC地址关联,这些本应该被缓存在一个表项,即转发表项内,然而Linux协议栈没有这么做。

4.不合理的锁

为何要加锁,因为SMP。然而Linux内核几乎是对称的加锁,也就是说,比如每次查路由表时都要加锁,为何为怕在查询的期间路由表改变了…然而你仔细想想,在高速转发情景下,查找操作和修改操作在单位时间的比率是多少呢要以为你用读写锁就好了,读写锁不也有关抢占的操作吗(虽然我们已经建议关闭了抢占)码也浪费了几个指令周期。这些时间几率不对称操作的加锁是不必要的。

       你只需要保证内核本身不会崩掉即可,至于说IP转发的错误,不管也罢,按照IP协议,它本身就是一个尽力而为的协议。

5.中断与软中断调度

Linux的中断分为上半部和下半部,动态调度下半部,它可以在中断上下文中运行,也可以在独立的内核线程上下文中运行,因此对于实时需求的环境,在软中断中处理的协议栈处理的运行时机是不可预知的。Linux原生内核并没有实现Solaris,Windows那样的中断优先级化,在某些情况下,Linux靠着自己动态的且及其优秀的调度方案可以达到极高的性能,然而对于固定的任务,Linux的调度机制却明显不足。

       而我需要做的,就是让不固定的东西固定化。

6.通用操作系统内核协议栈的通病

作为一个通用操作系统内核,Linux内核并非仅仅处理 络数据,它还有很多别的子系统,比如各种文件系统,各种IPC等,它能做的只是可用,简单,易扩展。

       Linux原生协议栈完全未经 络优化,且基本装机在硬件同样也未经优化的通用架构上, 卡接口在PCI-E总线上,如果DMA管理不善,总线的占用和争抢带来的性能开销将会抵消掉DMA本意带来的好处(事实上对于转发而言并没有带来什么好处,它仅仅对于作为服务器运行的Linux有好处,因为它只涉及到一块 卡)

[注意,我认为内核处理路径并非瓶颈,这是分层协议栈决定的,瓶颈在各层中的某些操作,比如内存操作(固有开销)以及查表操作(算法不好导致的开销)]

综述:Linux转发效率受到以下几大因素影响

IO/输入输出的队列管理/内存修改拷贝 (重新设计类似crossbar的队列管理实现DMA ring交换)
各种表查询操作,特别是最长前缀匹配,诸多本身唯一确定的查询操作之间的关联没有建立
SMP下处理器同步(锁开销)(使用大读锁以及RCU锁)以及cache利用率
中断以及软中断调度

Linux转发性能提升方案

概述

此方案的思路来自基于crossbar的新一代硬件路由器。设计要点:

1.重新设计的DMA包管理队列(思路来自Linux O(1)调度器,crossbar阵列以及VOQ[虚拟输出队列]
2.重新设计的基于定位而非最长前缀查找的转发表
3.长线程处理(中断线程化,处理流水线化,增加CPU亲和)
4.数据结构无锁化(基于线程局部数据结构)
5.实现方式
5.1.驱动以及内核协议栈修改
5.2.完全的用户态协议栈
5.3.评估:用户态协议栈灵活,但是在某些平台要处理空间切换导致的cache/tlb/mmu表的flush问题

内核协议栈方案

优化框架

0.例行优化

1). 卡多队列绑定特定CPU核心(利用RSS特性分别处理TX和RX)
[可以参见《Effective Gigabit Ethernet Adapters-Intel千兆 卡8257X性能调优》]
2).按照包大小统计动态开关积压延迟中断ThrottleRate以及中断Delay(对于Intel千兆卡而言)
3).禁用内核抢占,减少时钟HZ,由中断粒度驱动(见上面)
4).如果不准备优化Netfilter,编译内核时禁用Netfilter,节省指令
5).编译选项去掉DEBUG和TRACE,节省指令周期
6).开启 卡的硬件卸载开关(如果有的话)
7).最小化用户态进程的数量,降低其优先级
8).原生 络协议栈优化
    由于不再作为通用OS,可以让除了RX softirq的task适当饥饿
    *CPU分组(考虑Linux的cgroup机制),划一组CPU为数据面CPU,每一个CPU绑定一个RX softirq或者
    *增加rx softirq一次执行的netdev_budget以及time limit,或者
    *只要有包即处理,每一个。控制面/管理面的task可以绑在别的CPU上。

宗旨:
原生协议栈的最优化方案

1.优化I/O,DMA,减少内存管理操作

    1).减少PCI-E的bus争用,采用crossbar的全交叉超立方开关的方式
        [Tips:16 lines 8 bits PCI-E总线拓扑(非crossbar!)的 络线速不到满载60% pps]
    2).减少争抢式DMA,减少锁总线[Tips:优化指令LOCK,最好采用RISC,方可调高内核HZ]
        [Tips:交换DMA映射,而不是在输入/输出buffer ring之间拷贝数据!现在,只有傻逼才会在DMA情况拷贝内存,正确的做法是DMA重映射,交换指针!]
    3).采用skb内存池,避免频繁内存分配/释放造成的内存管理框架内的抖动
        [Tips:每线程负责一块 卡(甚至输入和输出由不同的线程负责会更好),保持一个预分配可循环利用的ring buffer,映射DMA]

宗旨:
减少cache刷新和tlb刷新,减少内核管理设施的工作(比如频繁的内存管理)

2.优化中断分发

1).增加长路径支持,减少进程切换导致的TLB以及Cache刷新
2).利用多队列 卡支持中断CPU亲和力利用或者模拟软多队列提高并行性
3).牺牲用户态进程的调度机会,全部精力集中于内核协议栈的处理,多CPU多路并行的
    [Tips:如果有超多的CPU,建议划分cgroup]
4).中断处理线程化,内核线程化,多核心并行执行长路经,避免切换抖动
5).线程内部,按照IXA NP微模块思想采用模块化(方案未实现,待商榷)

宗旨:
减少cache刷新和tlb刷新
减少协议栈处理被中断过于频繁打断[要么使用IntRate,要么引入中断优先级]

3.优化路由查找算法

1).分离路由表和转发表,路由表和转发表同步采用RCU机制
2).尽量采用线程局部数据
每个线程一张转发表(由路由表生成,OpenVPN多线程采用,但失败),采用定位而非最长前缀查找(DxR或者我设计的那个)。若不采用为每个线程复制一份转发表,则需要重新设计RW锁或者使用RCU机制。
3).采用hash/trie方式以及DxR或者我设计的DxRPro定位结构

宗旨:
采用定位而非查找结构
采用局部表,避免锁操作

4.优化lock

宗旨:锁的粒度与且仅与临界区资源关联,粒度最小化

优化细节概览

1.DMA与输入输出队列优化

1.1.问题出在哪儿

如果你对Linux内核协议栈足够熟悉,那么就肯定知道,Linux内核协议栈正是由于软件工程里面的天天普及的“一件好事”造成了转发性能低效。这就是“解除紧密耦合”。

       Linux协议栈转发和Linux服务器之间的根本区别在于,后者的应用服务并不在乎数据包输入 卡是哪个,它也不必关心输出 卡是哪一个,然而对于Linux协议栈转发而言,输入 卡和输出 卡之间确实是有必要相互感知的。Linux转发效率低的根本原因不是路由表不够高效,而是它的队列管理以及I/O管理机制的低效,造成这种低效的原因不是技术实现上难以做到,而是Linux内核追求的是一种灵活可扩展的性能,这就必须解除出入 卡,驱动和协议栈之间关于数据包管理的紧密耦合。

       我们以Intel千兆 卡驱动e1000e来说明上述的问题。顺便说一句,Intel千兆驱动亦如此,其它的就更别说了,其根源在于通用的 卡驱动和协议栈设计并不是针对转发优化的。

初始化:
创建RX ring:RXbuffinfo[MAX]
创建TX ring:TXbuffinfo[MAX]

RX过程:
i = 当前RX ring游历到的位置;
while(RXbuffinfo中有可用skb) {
        skb = RXbufferinfo[i].skb;
        RXbuffinfo[i].skb = NULL;
        i++;
        DMA_unmap(RXbufferinfo[i].DMA);
        [Tips:至此,skb已经和驱动脱离,完全交给了Linux协议栈]
        [Tips:至此,skb内存已经不再由RX ring维护,Linux协议栈拽走了skb这块内存]
        OS_receive_skb(skb);
        [Tips:由Linux协议栈负责释放skb,调用kfree_skb之类的接口]
        if (RX ring中被Linux协议栈摘走的skb过多) {
                alloc_new_skb_from_kmem_cache_to_RXring_RXbufferinfo_0_to_MAX_if_possible;
                [Tips:从Linux核心内存中再次分配skb]
        }
}

TX过程:
skb = 来自Linux协议栈dev_hard_xmit接口的数据包;
i = TX ring中可用的位置
TXbufferinfo[i].skb = skb;
DMA_map(TXbufferinfo[i].DMA);
while(TXbufferinfo中有可用的skb) {
        DMA_transmit_skb(TXbufferinfo[i]);
}
[异步等待传输完成中断或者在NAPI poll中主动调用]
i = 传输完成的TXbufferinfo索引
while(TXbufferinfo中有已经传输完成的skb) {
        skb = TXbufferinfo[i];
        DMA_unmap(TXbufferinfo[i].DMA);
        kfree(skb);
        i++;
}
以上的流程可以看出,在持续转发数据包的时候,会涉及大量的针对skb的alloc和free操作。如果你觉得上面的代码不是那么直观,那么下面给出一个图示:

在我的这个针对Linux协议栈的VOQ设计中,VOQ总要要配合良好的输出调度算法,才能发挥出最佳的性能。

2.分离路由表和转发表以及建立查找操作之间的关联

Linux协议栈是不区分对待路由表和转发表的,而这在高端路由器上显然是必须的。诚然,我没有想将Linux协议栈打造成比肩专业路由器的协议栈,然而通过这个排名第二的核心优化,它的转发效率定会更上一层楼。

       在大约三个月前,我参照DxR结构以及借鉴MMU思想设计了一个用于转发的索引结构,可以实现3步定位,无需做最长前缀匹配过程,具体可以参见我的这篇文章《以DxR算法思想为基准设计出的路由项定位结构图解》,我在此就不再深度引用了。需要注意的是,这个结构可以根据现行的Linux协议栈路由FIB生成,而且在路由项不规则的情况下可以在最差情况下动态回退到标准DxR,比如路由项不可汇聚,路由项在IPv4地址空间划分区间过多且分布不均。我将我设计的这个结构称作DxR Pro++。

       至于说查找操作之间的关联,这也是一个深度优化,底层构建高速查询流表实现协议栈短路(流表可参照conntrack设计),这个优化思想直接参照了Netfilter的conntrack以及SDN流表的设计。虽然IP 络是一个无状态 络,中间路由器的转发策略也应该是一个无状态的转发。然而这是形而上意义上的理念。如果谈到深度优化,就不得不牺牲一点纯洁性。

       设计一个流表,流的定义可以不必严格按照五元组,而是可以根据协议头的任意字段,每一个表项中保存的信息包括但不限于以下的元素:
*流表缓存路由项
*流表缓存neighbour
*流表缓存NAT
*流表缓存ACL规则       
*流表缓存二层头信息

这样可以在协议栈的底层保存一张可以高速查询的流表,协议栈收到skb后匹配这张表的某项,一旦成功,可以直接取出相关的数据(比如路由项)直接转发,理论上只有一个流的第一个数据包会走标准协议栈的慢速路径(事实上,经过DxR Pro++的优化,一经不慢了…)。在直接快速转发中,需要执行一个HOOK,执行标准的例行操作,比如校验和,TTL递减等。  
        关于以上的元素,特别要指出的是和neighbour与二层信息相关的。数据转发操作一向被认为瓶颈在发不在收,在数据发送过程,会涉及到以下耗时的操作:>添加输出 卡的MAC地址作为源-内存拷贝>添加next hop的MAC地址作为目标-内存拷贝又一次,我们遇到了内存操作,恼人的内存操作!如果我们把这些MAC地址保存在流表中,可以避免吗似只是可以快速定位,而无法避免内存拷贝…再一次的,我们需要硬件的特性来帮忙,这就是分散聚集I/O(Scatter-gather IO),原则上,Scatter-gather IO可以将不连续的内存当成连续的内存使用,进而直接映射DMA,因此我们只需要告诉控制器,一个将要发送的帧的MAC头的位置在哪里,DMA就可以直接传输,没有必要将MAC地址拷贝到帧头的内存区域。如下图所示:

其实,类比火车和出租车我们就能看到这个区别。对于火车而言,它的线路是固定的,比如哈尔滨到汉口的火车,它属于哈尔滨铁路局,满客到达汉口后,下客,然后汉口空车重新上客,它一定返回哈尔滨。然而对于出租车,就不是这样,嘉定的沪C牌的出租车理论上属于嘉定,不拒载情况下,一个人打车到松江,司机到松江后,虽然期待有人打他的车回嘉定,但是乘客上车后(路由查找),告诉司机,他要到闵行,到达后,又一人上车,说要到嘉兴…越走越远,但事实就是这样,因为乘客上车前,司机是不能确定他要去哪里的。

用户态协议栈方案

1.争议

在某些平台上,如果不解决user/kernel切换时的cache,tlb刷新开销,这种方案并不是我主推的,这些平台上不管是写直通还是写回,访问cache都是不经MMU的,也不cache mmu权限,且cache直接使用虚地址。

2.争议解决方案

可以采用Intel I/OAT的DCA技术,避免上下文切换导致的cache抖动

3.采用PF_RING的方式

修改驱动,直接与DMA buffer ring关联(参见内核方案的DMA优化)。

4.借鉴Tilera的RISC超多核心方案

并行流水线处理每一层,流水级数为同时处理的包的数量,CPU核心数+2,流水数量为处理模块的数量。
[流水线倒立]

本质上来讲,用户态协议栈和内核协议栈的方案是雷同的,无外乎还是那几种思想。用户态协议栈实现起来限制更少,更灵活,同时也更稳定,但是并不是一味的都是好处。需要注意的是,大量存在的争议都是形而上的,仁者见仁,智者见智。

稳定性

对于非专业非大型路由器,稳定性问题可以不考虑,因为无需7*24,故障了大不了重启一下而已,无伤大雅。但是就技术而言,还是有几点要说的。在高速总线情形下,并行总线容易窜扰,内存也容易故障,一个位的错误,一个电平的不稳定都会引发不可预知的后果,所以PCI-E这种高速总线都采用串行的方式传输数据,对于硬盘而言,SATA也是一样的道理。

       在多 卡DMA情况下,对于通过的基于PCI-E的设备而言,总线上的群殴是很激烈的,这是总线这种拓扑结构所决定的,和总线类型无关,再考虑到系统总线和多CPU核心,这种群殴会更加激烈,因为CPU们也会参与进来。群殴的时候,打翻桌椅而不是扳倒对方是很常有的事,只要考虑一下这种情况,我就想为三年前我与客户的一次争吵而向他道歉。

       2012年,我做一个VPN项目,客户说我的设备可能下一秒就会宕机,因为不确定性。我说if(true) {printf(“cao ni ma!n”)(当然当时我不敢这么说);确定会执行吗说不一定。我就上火了…可是现在看来,他是对的。

VOQ设计后良好的副作用-QoS

       有了VOQ以后,配合设计良好的调度算法,几乎解决了所有问题,这是令人兴奋的。上文中我提到输出操作的时候,输出线程采用基于数据包长度以及虚拟时间的加权公平调度算法进行输出调度,但是这个算法的效果只是全速发送数据包。如果这个调度算法策略化,做成一个可插拔的,或者说把Linux的TC模块中的框架和算法移植进来,是不是会更好呢

       唉,如果你百度“路由器 线速”,它搜出来的几乎都是“路由器 限速”,这真是一个玩笑。其实对于转发而言,你根本不用添加任何TC规则就能达到限速的效果,Linux盒子在 上上一串,马上就被自动限速了,难道不是这样吗加上VOQ以后,你确实需要限速了。就像在拥挤的中国城市中区,主干道上写着限速60,这不是开玩笑吗个市中心的熙熙攘攘的街道能跑到60….但是一旦上了高速,限速100/120,就是必须的了。

VOQ设计后良好的副作用-队头拥塞以及加速比问题

用硬件路由器的术语,如果采用将数据包路由后排队到输出 卡队列的方案,那么就会有多块 卡同时往一块 卡排队数据包的情况,这对于输出 卡而言是被动的,这又是一个令人悲伤的群殴过程,为了让多个包都能同时到达,输出带宽一定要是各个输入带宽的加和,这就是N倍加速问题,我们希望的是一个输出 卡主动对数据包进行调度的过程,有序必然高效。这就是VOQ设计的精髓。

       对于Linux而言,由于它的输出队列是软件的,因此N加速比问题变成了队列锁定问题,总之,还是一个令人遗憾的群殴过程,因此应对方案的思想是一致的。因此在Linux中我就模拟了一个VOQ。从这里我们可以看出VOQ和输出排队的区别,VOQ对于输出过程而言是主动调度的过程,显然更加高效,而输出排队对于输出过程而言则是一个被动被争抢的过程,显然这是令人感到无望的。

       需要说明的是,VOQ只是一个逻辑上的概念,类比了硬件路由器的概念。如果依然坚持使用输出排队而不是VOQ,那么设计多个输出队列,每一个 卡一个队列也是合理的,它甚至更加简化,压缩掉了一个 卡分派过程,简化了调度。于是我们得到了Linux VOQ设计的第三版:将虚拟输出队列VOQ关联到输出 卡而不是输入 卡(下面一小节我将分析原因)。

总线拓扑和Crossbar

真正的硬件路由器,比如Cisco,华为的设备,路由转发全由线卡硬件执行,数据包在此期间是静止在那里的,查询转发表的速度是如此之快,以至于相对将数据包挪到输出 卡队列的开销,查表开销可以忽略。因此在真正的硬件路由器上,如何构建一个高性能交换 络就是重中之重。
   
       不但如此,硬件路由器还需要考虑的是,数据包在路由查询过后是由输入处理逻辑直接通过交换 络PUSH到输出 卡队列呢,还是原地不动,然后等待输出逻辑通过交换 络把数据包PULL到那里。这个不同会影响到交换 络仲裁器的设计。如果有多个 卡同时往一个 卡输出数据包,PUSH方式可能会产生冲突,因为在Crossbar的一条路径上,它相当于一条总线,而且冲突一般会发生在交换 络内部,因此这种PUSH的情况下,一般会在交换 络内部的开关节点上携带cache,用来暂存冲突仲裁失败的数据包。反之,如果是PULL方式,情况就有所不同。因此把输出队列放在交换 络的哪一侧带来的效果是不同的。

       但是对于通用系统架构,一般都是采用PCI-E总线连接各个 卡,这是一种典型的总线结构,根本就没有所谓的交换 络。因此所谓的仲裁就是总线仲裁,这并不是我关注的重点,谁让我手上只有一个通用架构的设备呢我的优化不包括总线仲裁器的设计,因为我不懂这个。

       因此,对于通用架构总线拓扑的Linux协议栈转发优化而言,虚拟输出队列VOQ关联在输入 卡还是输出 卡,影响不会太大。但是考虑到连续内存访问带来的局部性优化,我还是倾向将VOQ关联到输出 卡。如果VOQ关联到输入 卡,那么在进行输出调度的时候,输出 卡的输出线程就要从输出位图指示的每一个待发送数据的输入 卡VOQ中与自己关联的队列调度数据包,无疑,这些队列在内存中是不连续的,如果关联到输出 卡,对于每一个输出 卡而言,VOQ是连续的。如下图所示:

Linux转发性能评估与优化-转发瓶颈分析与解决方案(补遗)

实现相关

前面我们提到skb只是作为容器(卡车)存在。因此skb是不必释放的。理想情况下,在Linux内核启动, 络协议栈初始化的时候,根据自身的硬件性能和 卡参数做一次自测,然后分配MAX个skb,这些skb可以先均匀分配到各个 卡,同时预留一个socket skb池,供用户socket取。后面的事情就是skb运输行为了,卡车开到哪里算哪里,运输过程不空载。

       可能你会觉得这个没有必要,因为skb本身甚至整个Linux内核中绝大部分内存分配都是被预先分配并cache的,slab就是做这个的,不是有kmem_cache机制吗这样的,我承认Linux内核在这方面做得很不错。但是kmem_cache是一个通用的框架,为何不针对skb再提高一个层次呢一次调用alloc_skb,都会触发到kmem_cache框架内的管理机制做很多工作,更新数据结构,维护链表等,甚至可能会触及到更加底层的伙伴系统。因此,期待直接使用高效的kmem_cache并不是一个好的主意。

       针对skb的修改,我添加了一个字段,指示它的所属地(某个 卡ocket池..),当前所属地,这些信息可以维护skb不会被free到kmem_cache,同时也可以最优化cache利用率。这部分修改已经实现,目前正在针对Intel千兆卡的驱动做进一步修改。关于DxR Pro的性能,我在用户态已经经过测试,目前还没有移植到内核。

       关于快速查找表的实现,目前的思路是优化nf_conntrack,做多级hash查找。

最后的声明

文章知识点与官方知识档案匹配,可进一步学习相关知识CS入门技能树Linux入门初识Linux24696 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2015年6月2日
下一篇 2015年6月3日

相关推荐