Receive packet steering patch详解
时间:2010-08-14 来源:robbielee
简介在这里:
http://lwn.net/Articles/362339/
linux现在网卡的驱动支持两种模式,一种是NAPI,一种是非NAPI模式,这两种模式的区别,我前面的blog都有介绍,这里就再次简要的介绍下。
在NAPI中,中断收到数据包后调用__napi_schedule调度软中断,然后软中断处理函数中会调用注册的poll回掉函数中调用netif_receive_skb将数据包发送到3层,没有进行任何的软中断负载均衡。
在非NAPI中,中断收到数据包后调用netif_rx,这个函数会将数据包保存到input_pkt_queue,然后调度软中断,这里为了兼容NAPI的驱动,他的poll方法默认是process_backlog,最终这个函数会从input_pkt_queue中取得数据包然后发送到3层。
通过比较我们可以看到,不管是NAPI还是非NAPI的话都无法做到软中断的负载均衡,因为软中断此时都是运行在在硬件中断相应的cpu上。也就是说如果始终是cpu0相应网卡的硬件中断,那么始终都是cpu0在处理软中断,而此时cpu1就被浪费了,因为无法并行的执行多个软中断。
google的这个patch的基本原理是这样的,根据数据包的源地址,目的地址以及目的和源端口(这里它是将两个端口组合成一个4字节的无符数进行计算的,后面会看到)计算出一个hash值,然后根据这个hash值来选择软中断运行的cpu,从上层来看,也就是说将每个连接和cpu绑定,并通过这个hash值,来均衡软中断在多个cpu上。
这个介绍比较简单,我们来看代码是如何实现的。
它这里主要是hook了两个内核的函数,一个是netif_rx主要是针对非NAPI的驱动,一个是netif_receive_skb这个主要是针对NAPI的驱动,这两个函数我前面blog都有介绍过,想了解可以看我前面的blog,现在这里我只介绍打过patch的实现。
在看netif_rx和netif_receive_skb之前,我们先来看这个patch中两个重要的函数get_rps_cpu和enqueue_to_backlog,我们一个个看。
先来看相关的两个数据结构,首先是netdev_rx_queue,它表示对应的接收队列,因为有的网卡可能硬件上就支持多队列的模式,此时对应就会有多个rx队列,这个结构是挂载在net_device中的,也就是每个网络设备最终都会有一个或者多个rx队列。这个结构在sys文件系统中的表示类似这样的/sys/class/net/<device>/queues/rx-<n> 几个队列就是rx-n.
struct netdev_rx_queue {
//保存了当前队列的rps map
struct rps_map *rps_map;
//对应的kobject
struct kobject kobj;
//指向第一个rx队列
struct netdev_rx_queue *first;
//引用计数
atomic_t count;
} ____cacheline_aligned_in_smp;
然后就是rps_map,其实这个也就是保存了能够执行数据包的cpu。
struct rps_map {
//cpu的个数,也就是cpus数组的个数
unsigned int len;
//RCU锁
struct rcu_head rcu;
//保存了cpu的id.
u16 cpus[0];
};
看完上面的结构,我们来看函数的实现。get_rps_cpu主要是通过传递进来的skb然后来选择这个skb所应该被处理的cpu。它的逻辑很简单,就是通过skb计算hash,然后通过hash从对应的队列的rps_mapping中取得对应的cpu id。
这里有个要注意的就是这个hash值是可以交给硬件网卡去计算的,作者自己说是最好交由硬件去计算这个hash值,因为如果是软件计算的话会导致CPU 缓存不命中,带来一定的性能开销。
还有就是rps_mapping这个值是可以通过sys 文件系统设置的,位置在这里:
/sys/class/net/<device>/queues/rx-<n>/rps_cpus 。
static int get_rps_cpu(struct net_device *dev, struct sk_buff *skb) |
enqueue_to_backlog接受一个skb和cpu为参数,通过cpu来判断skb如何处理。要么加入所属的input_pkt_queue中,要么schecule 软中断。
还有个要注意就是我们知道NAPI为了兼容非NAPI模式,有个backlog的napi_struct结构,也就是非NAPI驱动会schedule backlog这个napi结构,而在enqueue_to_backlog中则是利用了这个结构,也就是它会schedule backlog,因为它会将数据放到input_pkt_queue中,而backlog的pool方法process_backlog就是从input_pkt_queue中取得数据然后交给上层处理。
这里还有一个会用到结构就是 rps_remote_softirq_cpus,它主要是保存了当前cpu上需要去另外的cpu schedule 软中断的cpu 掩码。因为我们可能将要处理的数据包放到了另外的cpu的input queue上,因此我们需要schedule 另外的cpu上的napi(也就是软中断),所以我们需要保存对应的cpu掩码,以便于后面遍历,然后schedule。
而这里为什么mask有两个元素,注释写的很清楚:
/* |
static int enqueue_to_backlog(struct sk_buff *skb, int cpu) |
google的patch对这个是这样处理的,在软中断处理函数中当数据包处理完毕,会调用net_rps_action来调度前面保存到其他cpu上的input队列。
下面就是代码片断(net_rx_action)
//得到对应的rcpus. |
static void net_rps_action(cpumask_t *mask) |
-
上面我们看到会调用csd方法,而上面的csd回掉就是被初始化为trigger_softirq函数。
static void trigger_softirq(void *data) |
首先来看netif_rx如何被修改的,它被修改的很简单,首先是得到当前skb所应该被处理的cpu id,然后再通过比较这个cpu和当前正在处理的cpu id进行比较来做不同的处理。
int netif_rx(struct sk_buff *skb) |
int netif_receive_skb(struct sk_buff *skb) |
最后来总结一下,可以看到input_pkt_queue是一个FIFO的队列,而且如果当qlen有值的时候,也就是在另外的cpu有数据包放到input_pkt_queue中,则当前cpu不会调度napi,而是将数据包放到input_pkt_queue中,然后等待trigger_softirq来调度napi。
因此这个patch完美的解决了软中断在多核下的均衡问题,并且由于是同一个连接会map到相同的cpu,并且input_pkt_queue的使用,因此乱序的问题也不会出现。
我们部门有人测试了这个patch,据说能差不多提高20%。
我看了下35的内核,这个补丁还有另外一个也是这个作者的补丁RFS(rps的增强)都已经进入内核了,看来35的网络性能还是值得期待的。