现代网络硬件的处理能力已经强大到让主机CPU有时也难以跟上。随着CPU主频提升停滞而核心数量不断增加,结论显而易见:若要使网络协议栈跟上硬件步伐,仅仅依靠更智能的处理(如通用接收卸载GRO)还不够,系统还必须能将工作负载分配到多个处理器上。Tom Herbert提出的接收包调度(RPS)补丁正是为实现这一目标而生。
从操作系统视角看,将发送数据的处理分发到各个CPU上相对简单。产生数据的进程本身就已分布在系统中,网络协议栈无需过多干预,尤其是在现已支持多传输队列的情况下。然而,接收数据的分配更具挑战性,因为所有数据都源自单一来源。部分网络接口卡(NIC)本身支持分配入站数据包,它们拥有多个接收队列和中断线。但另一些接口仅配备单队列,这意味着其驱动程序必须以串行流方式处理所有传入数据包。要对这样的串行流进行并行化处理,就需要主机操作系统具备一定智能。
Tom的补丁通过在驱动将数据包递交给网络子系统的关键入口点——netif_rx()和netif_receive_skb()——插入逻辑来实现这种智能。此刻,系统会根据相关协议数据(主要是IP地址和端口号)计算一个哈希值,并利用此哈希值选择一个目标CPU,随后将数据包排入该CPU的专属处理队列。默认情况下,系统中任何CPU都可被用于网络处理,但管理员也可以为特定网络接口显式配置允许的CPU列表。
代码本身相对简洁,却成功地将接收处理的负载分散到了整个系统。使用哈希值至关重要:它能确保来自同一网络流的数据包最终由同一个处理器处理,这显著提升了缓存局部性,从而带来性能增益。该方案的另一个优势在于完全无需修改驱动程序,因此可以快速部署,对系统干扰极小。
然而,驱动程序可以在一个方面提供帮助。计算哈希值需要访问数据包头部信息,这种访问必然会导致执行调度代码的CPU发生一次或多次缓存未命中——这些数据刚刚由网卡写入,不可能预存在任何CPU缓存中。随后,当数据包被送到真正负责处理的CPU上时,很可能再次发生缓存未命中。不必要的缓存未命中是高速网络处理的“天敌”;人们已投入大量工作来尽可能消除它们。在调度路径中为每个数据包新增一次缓存未命中,无疑是适得其反的。
事实上,许多现代网络接口卡本身就能为传入数据包计算哈希值。这种处理是“免费”的,并且可以避免在调度CPU上计算哈希(以及承担访问数据带来的开销)。为了利用这一特性,RPS补丁在sk_buff(SKB)结构中新增了一个rxhash字段。能够从硬件获取哈希值的驱动程序可以将该值存入SKB;随后网络协议栈便会跳过自身的哈希计算。这可以确保数据包的数据完全不被载入调度CPU的缓存,从而加快整体处理速度。
那么,这种方法效果究竟如何?补丁中包含了使用netperf工具进行的基准测试结果。在一台配备基于tg3驱动网卡的8核服务器上,每秒事务处理量从90,000提升至285,000;同一系统中基于e1000驱动的网卡,其事务处理量则从90,000提升至292,000。在16核服务器上,使用nForce和bnx2x芯片组也观察到了类似的显著提升。由此可见,该补丁确实成功地提升了Linux内核在多核系统上的网络处理能力。
|