免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 35301 | 回复: 54

在多核系统上网络数据转发实验和一点思考 [复制链接]

论坛徽章:
0
发表于 2009-05-19 12:10 |显示全部楼层
小弟刚刚鸟枪换小炮。得到一台Intel(R) Core(TM)2 CPU   6400  @ 2.13GHz + PCI-E 4X 2.5GB的机器,以前看大家讨论多核,IRQ中断亲和的问题,心里头就发痒,现在终于有机会测试了!!!反复做了些测试,有一些值得思考的地方,将整个测试过程发上来(不包括性能改进方面的内容),与大家一起讨论(有点长,适合有耐心的TX看):

一些个人结论性的东西可能有误,希望大家指点!!!


一、测试环境:

发包机(PC_A) -------- (eth1)Linux(eth2)---------收包机(PC_B)

内核版本:2.6.12
网卡驱动:Intel e1000e[Intel现在把pci-e的千兆网卡单独拿出来了。整了个e1000e],NAPI模式;
发包工具:bwtest
Linux配置:网桥 + Netfilter;
数据包是单向发送64bytes小包。即PC_B不发包。

二、不开启IRQ中断均衡;
内核编译中,不开启此选项。
  1. Cpu(s):   0.0% user,   0.5% system,   0.0% nice,  50.3% idle
  2. Cpu0  :   1.0% user,   0.0% system,   0.0% nice,   1.0% idle
  3. Cpu1  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle
  4. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  50.8% idle
  5. Cpu0  :   0.0% user,   0.0% system,   0.0% nice,   1.0% idle
  6. Cpu1  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle
  7. Cpu(s):   0.5% user,   0.0% system,   0.0% nice,  50.8% idle
  8. Cpu0  :   0.0% user,   1.0% system,   0.0% nice,   2.0% idle
  9. Cpu1  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle
复制代码

此时数据转发约166Mb(这是我发包机的上限了……)

从三次采样结果来看,所有负载都被放在了CPU0上面,CPU1基本上是在睡大觉。
同时,查看/proc/interrupt,也可以看到,CPU1上面没有中断。
结论:多核下不启用IRQ中断均衡功能是一种资源浪费。

三、开启IRQ中断均衡:
在内核编译中,启用该选项。
  1. [root@SkyNet ~]# cat /proc/interrupts
  2.            CPU0       CPU1      
  3. 74:     154789          1         PCI-MSI  eth1
  4. 82:      16393    2102221         PCI-MSI  eth2
复制代码

并没有去手动修改smp_affinity文件。在开机的时候,短暂的把eth2的中断也放到了CPU0后,立马自己学习,转到cpu1上面去了。实现了两张网卡,两个CPU,一人一个。哥俩好!!!
但是,这并不能让我高兴,因为问题才刚刚开始:
  1. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  38.5% idle
  2. Cpu0  :   1.0% user,   1.0% system,   0.0% nice,   2.0% idle
  3. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  73.7% idle

  4. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  37.2% idle
  5. Cpu0  :   0.0% user,   0.0% system,   0.0% nice,   2.1% idle
  6. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  72.4% idle

  7. Cpu(s):   0.5% user,   0.5% system,   0.0% nice,  38.2% idle
  8. Cpu0  :   0.0% user,   0.0% system,   0.0% nice,   3.0% idle
  9. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  73.7% idle
复制代码

从三次采样结果来看,
1、CPU总负载不降反升了,从50%左右,上升到63%左右了。[从ilde的百分比可以看出来]
2、CPU0的下来了(因为eth2的中断不需要它去处理了);
3、CPU1的负载从0%上升到了27%左右。

为什么会有这种情况发生呢?此时猜测唯一可以解释的就是:
  1. “CPU1此时只分担到了发送数据帧的中断工作,网络内核栈的工作,从net_rx_action开始,包括网桥、Netfilter、队列调度等等工作,全部集中到了CPU0上,网络栈的工作,并没有实现负载均衡,换句话说,net_rx_action这个软中断,只在一个CPU上运行了,并没有实现多个CPU的同时运行和调度(通过后面的实验和ShadowStar同学 的指点,最后这一句的结论是错的,我最后会说明)”
复制代码


为了进一步证明我的这个结论,我在Netfilter的raw表的PREROUTING中,丢弃所有数据:
  1. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  78.6% idle,   0.0% x,   2.1% y
  2. Cpu0  :   0.0% user,   0.0% system,   0.0% nice,  57.0% idle,   0.0% x,   5.4% y
  3. Cpu1  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle,   0.0% x,   0.0% y
  4. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  78.1% idle,   0.0% x,   2.7% y
  5. Cpu0  :   0.0% user,   0.0% system,   0.0% nice,  55.3% idle,   0.0% x,   5.3% y
  6. Cpu1  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle,   0.0% x,   0.0% y
  7. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  80.1% idle,   0.0% x,   2.2% y
  8. Cpu0  :   0.0% user,   0.0% system,   0.0% nice,  60.6% idle,   0.0% x,   4.3% y
  9. Cpu1  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle,   0.0% x,   0.0% y
复制代码

当数据被丢弃时,从三次采样的结果来看,
1、CPU1因为不再发送数据,又没有事情干了。它的空闲是100%,所以,像网桥处理,软中断,肯定也没有它的份。再一次印证了刚才的想法(尽管它是错的);
2、CPU0负载也大幅的下降,这是因为。它不再处理连接跟踪那些东东了——再一次证明,Netfilter是一个很吃CPU的东东。

那有没有可能:让一个CPU来处理内核网格栈的功能,一个CPU来专门处理网卡中断呢??我突发奇想了!!!
即然现在net_rx_action软中断是运行在CPU0上的,那我调整中断亲和,把CPU0上的中断负载调整到CPU1上去,不就完美了么??呵呵:
  1. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  59.0% idle,   0.0% x,   0.5% y
  2. Cpu0  :   0.0% user,   1.1% system,   0.0% nice,  98.9% idle,   0.0% x,   0.0% y
  3. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  18.1% idle,   0.0% x,   1.1% y
  4. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  59.6% idle,   0.0% x,   0.5% y
  5. Cpu0  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle,   0.0% x,   0.0% y
  6. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  18.1% idle,   0.0% x,   1.1% y
  7. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  59.6% idle,   0.0% x,   0.5% y
  8. Cpu0  :   0.0% user,   0.0% system,   0.0% nice, 100.0% idle,   0.0% x,   0.0% y
  9. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  20.2% idle,   0.0% x,   1.1% y
复制代码

实验结果让我失望:
1、总CPU负载的确是下降了;
2、此时Cpu0变为空闲又变为100%——软中断函数并没有像预期的那样,跑到Cpu0上面去;而是所有的东东又跑到Cpu1了,此时CPU1负载明显上升很多,net_rx_action好像是随着中断落到哪个CPU上,它就跑到哪个CPU上面去
3、一个有趣的现像是:所有任务由Cpu0处理,总负载是50%,所有任务由Cpu1处理,总负载下降很明显,这个原因没有仔细去考究了,难道是第二个核性能比第一个好???

因为通过上述实验,得到了“net_rx_action好像是随着中断落到哪个CPU上,它就跑到哪个CPU上面去”的结论,那么一开始的“net_rx_action这个软中断,只在一个CPU上运行了,并没有实现多个CPU的同时运行和调度”的结论就被推翻了!!那为什么会造成这种情况呢??我陷入了沉思当中。

四、为什么会是这样呢?
通过查看代码,找到了原因(代码有删减):

  1. static void net_rx_action(struct softirq_action *h)
  2. {
  3.         struct softnet_data *queue = &__get_cpu_var(softnet_data);
  4.        
  5.         while (!list_empty(&queue->poll_list)) {
  6.                 struct net_device *dev;

  7.                 dev = list_entry(queue->poll_list.next,
  8.                                  struct net_device, poll_list);
  9.                 netpoll_poll_lock(dev);

  10.                 if (dev->quota <= 0 || dev->poll(dev, &budget)) {
  11.                         list_del(&dev->poll_list);
  12.                         list_add_tail(&dev->poll_list, &queue->poll_list);
  13.                         if (dev->quota < 0)
  14.                                 dev->quota += dev->weight;
  15.                         else
  16.                                 dev->quota = dev->weight;
  17.                 } else {

  18.                 }
  19.         }
  20. out:
  21.         local_irq_enable();
  22.         return;

  23. softnet_break:
  24.         __get_cpu_var(netdev_rx_stat).time_squeeze++;
  25.         __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  26.         goto out;
  27. }
复制代码


所有问题有核心在于,softnet_data是一个pre_cpu变量,net_rx_action被某个CPU执行时,它只会遍历属于自己的网络设备队列。如上面的实验中,当eth1只会出现在cpu0的网络设备队列,eth2只会出现在CPU1的队列中。
遗憾的是,我的测试中,数据发送是单向的,所以,eth2没有接收数据。所以,所有的网络栈的工作,就理所当然地落到了CPU0上面来了。
那为什么,“当eth1只会出现在cpu0的网络设备队列,eth2只会出现在CPU1的队列中”,也就是随着硬件中断落到哪个CPU上,它就会在哪个CPU响应呢???这需要看poll_list这个网络设备队列的添加的实现过程了。
这个过程,都是在网卡中断函数中,它会调用:
netif_rx_schedule

  1. static inline void netif_rx_schedule(struct net_device *dev)
  2. {
  3.         if (netif_rx_schedule_prep(dev))
  4.                 __netif_rx_schedule(dev);
  5. }
复制代码

  1. static inline void __netif_rx_schedule(struct net_device *dev)
  2. {
  3.         unsigned long flags;

  4.         local_irq_save(flags);
  5.         dev_hold(dev);
  6.         list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
  7.         if (dev->quota < 0)
  8.                 dev->quota += dev->weight;
  9.         else
  10.                 dev->quota = dev->weight;
  11.         __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  12.         local_irq_restore(flags);
  13. }
复制代码


所以,每个网络设备中断,会把产生中断的网络设备(也就是自己)放到响应中断的那个CPU的softnet_data的队列上去。这就是原因所在了。
对于上面的实验,当一个网卡一个CPU时:eth1产生中断,把自己放到cpu0 的队列,eth2产生中断,把自己放到cpu1的队列,因为数据发送是单向的,当cpu1进入net_rx_action时,它的设备列表中显然不会有eth1,所以它也就没有了处理后续处理工作的机会,而所有的革命重任都落到了cpu0上。这就是前面实验中,为什么虽然硬中断已经实现一人处理一个,但是cpu0的负载很高,而cpu1的负载很低的原因了。

五、最后一个实验
为了证明以上的推断,将测试数据包方向改为双向发送。这样,eth2也会产生接收中断,会把eth2的接收帧放到CPU1的队列上去。就能够实现两个net_rx_action并行——cpu0的队列中包含eth1,cpu1的队列中包含eth2……
  1. Cpu(s):   0.5% user,   0.5% system,   0.0% nice,  16.6% idle,   0.0% x,   1.6% y
  2. Cpu0  :   1.1% user,   0.0% system,   0.0% nice,  11.6% idle,   0.0% x,   0.0% y
  3. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  21.9% idle,   0.0% x,   2.1% y
  4. Cpu(s):   0.0% user,   0.0% system,   0.0% nice,  16.8% idle,   0.0% x,   2.1% y
  5. Cpu0  :   0.0% user,   1.1% system,   0.0% nice,  10.5% idle,   0.0% x,   2.1% y
  6. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  22.1% idle,   0.0% x,   2.1% y
  7. Cpu(s):   0.5% user,   0.5% system,   0.0% nice,  15.1% idle,   0.0% x,   2.1% y
  8. Cpu0  :   1.0% user,   0.0% system,   0.0% nice,  11.5% idle,   0.0% x,   1.0% y
  9. Cpu1  :   0.0% user,   0.0% system,   0.0% nice,  19.8% idle,   0.0% x,   3.1% y
复制代码

1、cpu0的负载下降了,从2%的空闲到10%左右。这跟我的测试环境有关——数据包改为双向后,发包机的性能下降,它发送的数据帧从166Mb/s降到了100Mb/s。
2、可以看到CPU1负载明显地上升了,从70%多的空闲到20%左右,很明显,它此时也要运行net_rx_action,处理从收包机过来的接收到的数据帧,并处理网桥,Netfilter……等网络栈的工能。

六:初步结论
1、多核下,IRQ的负载均衡应该开启;
2、中断亲和内核自己可以通过调度算法解决,自己定义也可以;
3、中断实现多核并行后,内核协议栈的并行工作,包括网桥、ipv4、防火墙……的多核并行,跟硬中断落到哪个CPU上,也有直接关系。

在实践中,可能会遇到CPU数量大于/小于/等于网卡的情况,也有可能出现上/下行流量极不对称的情况,但是以上实验对于多核下调整内核的性能,还是很有意义的!

[ 本帖最后由 独孤九贱 于 2009-5-19 15:51 编辑 ]

论坛徽章:
0
发表于 2009-05-19 12:21 |显示全部楼层
中断处理函数仅会在触发中断的CPU上运行。

九贱兄,又一次验证了这个。

PS. 一直不知道九贱兄是做什么的?

论坛徽章:
0
发表于 2009-05-19 12:23 |显示全部楼层

回复 #2 ShadowStar 的帖子

中断处理函数仅会在触发中断的CPU上运行
——不是“中断处理函数”,而是软中断函数net_rx_action(因为net_rx_action会涉到到调用整个网络栈的数据接收流程,包括桥、VLAN、三层、防火墙、QoS……,而它们的并行,非常地重要)

我是做网络程序开发工作方面的,空了就看看Linux。呵呵。

[ 本帖最后由 独孤九贱 于 2009-5-19 12:24 编辑 ]

论坛徽章:
0
发表于 2009-05-19 12:25 |显示全部楼层
原帖由 独孤九贱 于 2009-5-19 12:23 发表
中断处理函数仅会在触发中断的CPU上运行
——不是“中断处理函数”,而是软中断函数net_rx_action(因为net_rx_action会涉到到调用整个网络栈的数据接收流程,包括桥、VLAN、三层、防火墙、QoS……,而它们的并 ...


不仅仅是net_rx_action。
也不仅仅是软中断。

论坛徽章:
0
发表于 2009-05-19 12:28 |显示全部楼层

回复 #4 ShadowStar 的帖子

你说“不仅仅”是什么意思??没懂。
其实我想要的结论是:
1、硬中断如何分摊到各个CPU上去;
2、内核网络栈如何在各个CPU上并行;

现在这两个结论都得到了。剩下的就是如何进一步优化它,如果需要的话。

关于优化,我现在有一个初步的想法,但是还没有成形:
就是把网卡加入到所有CPU的设备队列上去,但是
1、添加/移除设备时,就需要一个共享保护机制;
2、所有的NAPI的poll都需要被改写,因为skb_buff队列这个时候就也需要共享保护机制;
这样做会带来多少性能的提升?因为多个CPU可以同时处理同一个网卡接收的数据帧。但是因为同一个网卡的队列必然要引入锁机制,又会抵销多少??

呵呵,或许这个想法一开始就是荒诞的,再想想……

[ 本帖最后由 独孤九贱 于 2009-5-19 12:46 编辑 ]

论坛徽章:
0
发表于 2009-05-19 12:46 |显示全部楼层
原帖由 独孤九贱 于 2009-5-19 12:28 发表
你说“不仅仅”是什么意思??没懂。
其实我想要的结论是:
1、硬中断如何分摊到各个CPU上去;
2、内核网络栈如何在各个CPU上并行;

现在这两个结论都得到了。剩下的就是如何进一步优化它,如果需要的话。


不仅仅就是说,硬中断的响应函数也在触发这个中断的CPU上执行;其他软中断,例如定时器也会在触发的CPU上执行。

1。硬中断分摊,建议采用手动绑定的方式。即使在内核配置中打开了irqbalance,也仅会在中断触发达到一定频繁度时才会移动到其他CPU上。

2。软中断的问题。其实,只要硬中断绑定到了特定的CPU上,那么网络协议栈也就是在这个CPU上处理。
因为本地CPU的硬中断处理完成后,会触发本地软中断NET_RX_SOFTIRQ。

对于conntrack,主要是我觉得并行效果不好的原因,主要是锁的问题。因为正反向tuple都在一个hash表中,所以不能像路由查询一样,采用多个锁。
我也在思考这部分的优化处理。

论坛徽章:
0
发表于 2009-05-19 12:50 |显示全部楼层
2。软中断的问题。其实,只要硬中断绑定到了特定的CPU上,那么网络协议栈也就是在这个CPU上处理。
因为本地CPU的硬中断处理完成后,会触发本地软中断NET_RX_SOFTIRQ。
——我与你结论相同,但是原因却不同:你仔细看我贴子的最后一部份,并不是因为谁触发了NET_RX_SOFTIRQ而造成的。而是因为pre_cpu变量中设备队列的关系!你说这个,我个人认为:触发的时候,仅仅是挂起软中断,也就是设置了一个位图标志而已。它并不能决定,谁触发,即调用_netif_rx_schedule来进一步调用__raise_softirq_irqoff(NET_RX_SOFTIRQ);
而是调用了list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);的原因。
这样:实际上软中断net_rx_action会在多个并行在CPU上,它的并行与否,与产生数据接收的网卡的硬中断无关,但是即使有两个net_rx_action同时被两个cpu执行,因为其中一个cpu设备队列中,没有与之对应的接收数据帧的网络设备(因为中断时没有安装进队列来)。也会很快退出。此例中,cpu1中,没有eth1。所以,它即使进入了net_rx_action,也会很快退出。
当然,这个结论很有可能是错的,我还没有进一步证实,因为对软中断的调度这块,的确以前没有深入学习过。

对于conntrack,主要是我觉得并行效果不好的原因,主要是锁的问题。因为正反向tuple都在一个hash表中,所以不能像路由查询一样,采用多个锁。
——我现在发现,单个CPU效果都很差,并行的话,我就更加没有考虑到了,呵呵。空了我先找找单CPU的时候,效率差的原因再说。非常希望这一部份能与你进一步交流。

[ 本帖最后由 独孤九贱 于 2009-5-19 12:58 编辑 ]

论坛徽章:
0
发表于 2009-05-19 13:01 |显示全部楼层
原帖由 独孤九贱 于 2009-5-19 12:50 发表
2。软中断的问题。其实,只要硬中断绑定到了特定的CPU上,那么网络协议栈也就是在这个CPU上处理。
因为本地CPU的硬中断处理完成后,会触发本地软中断NET_RX_SOFTIRQ。
——我与你结论相同,但是原因却不同:你仔细看我贴子的最后一部份,并不是因为谁触发了NET_RX_SOFTIRQ而造成的。而是因为pre_cpu变量中设备队列的关系!你说这个,我个人认为:触发的时候,仅仅是挂起软中断,也就是设置了一个位图标志而已。

你是指
  1. static inline void __netif_rx_schedule(struct net_device *dev)
  2. {
  3.         unsigned long flags;

  4.         local_irq_save(flags);
  5.         dev_hold(dev);
  6.         list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
  7.         if (dev->quota < 0)
  8.                 dev->quota += dev->weight;
  9.         else
  10.                 dev->quota = dev->weight;
  11.         __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  12.         local_irq_restore(flags);
  13. }
复制代码

这个中的list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);吧?

softnet_data本身也是per_cpu的。__get_cpu_var取的是当前CPU的softnet_data。
这里无非就是将dev->poll_list添加到当前CPU的softnet_data中的poll_list。
原帖由 独孤九贱 于 2009-5-19 12:50 发表
对于conntrack,主要是我觉得并行效果不好的原因,主要是锁的问题。因为正反向tuple都在一个hash表中,所以不能像路由查询一样,采用多个锁。
——我现在发现,单个CPU效果都很差,并行的话,我就更加没有考虑到了,呵呵。空了我先找找单CPU的时候,效率差的原因再说。非常希望这一部份能与你进一步交流。


其实对于单CPU的话,我觉得主要还是互斥和同步操作导致的。毕竟关闭软硬中断都是针对本地CPU的。

论坛徽章:
0
发表于 2009-05-19 13:16 |显示全部楼层
原帖由 ShadowStar 于 2009-5-19 13:01 发表

你是指static inline void __netif_rx_schedule(struct net_device *dev)
{
        unsigned long flags;

        local_irq_save(flags);
        dev_hold(dev);
        list_add_tail(&dev->poll ...

是的,我就是指它!
你看,例如eth1触发中断,它就会将自己加入到自己响应中断的那个cpu(如cpu0)的soft_data的设备队列poll_list上去。

这样, 即使有10个cpu同时运行了net_rx_action,也只有cpu0的poll_list上有eth1,所以其它九个net_rx_action会很快退出(在单向数据传输中,没有其它网卡接收数据的情况下)。

这也就是我前面实验中,同时测试单向和双向的原因。

当然,我也只是一个猜测,因为我对do_softirq的调用机制还没有完全看明白。但是我觉得,应该是这样吧,八九不离十!呵呵!

其实对于单CPU的话,我觉得主要还是互斥和同步操作导致的。毕竟关闭软硬中断都是针对本地CPU的

这个我把多核这个搞明白了,回头就去做实验,找到原因所在。

[ 本帖最后由 独孤九贱 于 2009-5-19 13:17 编辑 ]

论坛徽章:
0
发表于 2009-05-19 13:25 |显示全部楼层
原帖由 独孤九贱 于 2009-5-19 13:16 发表

是的,我就是指它!
你看,例如eth1触发中断,它就会将自己加入到自己响应中断的那个cpu(如cpu0)的soft_data的设备队列poll_list上去。

这样, 即使有10个cpu同时运行了net_rx_action,也只有cpu0的poll_ ...


只有cpu0触发的net_rx_action,才会检测到本地CPU(cpu0)的softnet_data.poll_list上有cpu0上硬中断添加的poll_list。

换句话说,软中断触发的net_rx_action仅检测本地CPU上的poll_list。

所以我说,“只要硬中断绑定到了特定的CPU上,那么网络协议栈也就是在这个CPU上处理。”。

[ 本帖最后由 ShadowStar 于 2009-5-19 13:27 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP