kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

X520-T1 Linux内核收包14Mpps

目的

测试并优化Linux内核收包

ixgbe收包

前期设置:cpu和网卡队列一一绑定,service irqbalance stop等。

rx_ring->rx_buffer_info[]是收包用的循环队列。rx_ring中关于循环队列的一些重要变量:

1
2
3
4
rx_ring->next_to_clean    网卡收到的下一个包放这里,所以要预先申请好内存
rx_ring->next_to_alloc 下一个要申请内存的位置
rx_ring->next_to_use   [next_to_clean, next_to_use)这个区间的内存是申请好的了,网卡可以直接用。
			一般取值为[next_to_alloc - IXGBE_RX_BUFFER_WRITE, next_to_alloc], next_to_use 在ixgbe_alloc_rx_buffers里更新。

假设收包队列为512,那么ixgbe初始化之后

1
2
3
rx_ring->next_to_clean    = 0;
rx_ring->next_to_use   = 511;
rx_ring->next_to_alloc = 511;

这里刚好留了一个位置没申请内存,这个跟reuse有关,后面再看。

初始化流程:

ixgbe_up() -> ixgbe_configure() -> ixgbe_configure_rx() -> ixgbe_configure_rx_ring() -> ixgbe_alloc_rx_buffers(ring, ixgbe_desc_unused(ring))

ixgbe_desc_unused 决定了预留一个位置。

1
2
3
4
5
6
7
static inline u16 ixgbe_desc_unused(struct ixgbe_ring *ring)
{
	u16 ntc = ring->next_to_clean;
	u16 ntu = ring->next_to_use;

	return ((ntc > ntu) ? 0 : ring->count) + ntc - ntu - 1; // 这里保留一个位置
}

收包函数 ixgbe_clean_rx_irq 简化流程为

  1. 预先申请内存,为[next_to_use, next_to_clean)这段“没申请”内存的队列申请
1
2
3
4
if (cleaned_count >= IXGBE_RX_BUFFER_WRITE) {
	ixgbe_alloc_rx_buffers(rx_ring, cleaned_count);
	cleaned_count = 0;
}
  1. 检查是否收到数据
1
2
3
4
rx_desc = IXGBE_RX_DESC(rx_ring, rx_ring->next_to_clean);
size = le16_to_cpu(rx_desc->wb.upper.length);
if (!size)
	break;
  1. 申请skb
1
2
3
4
5
rx_buffer = ixgbe_get_rx_buffer(rx_ring, rx_desc, &skb, size);
if (skb)
	ixgbe_add_rx_frag(rx_ring, rx_buffer, skb, size); // EOP 场景才到这里
else
	skb = ixgbe_build_skb(rx_ring, rx_buffer, rx_desc, size);
  1. reuse buffer
1
ixgbe_put_rx_buffer(rx_ring, rx_buffer, skb);
  1. EOP
1
2
if (ixgbe_is_non_eop(rx_ring, rx_desc, skb))
	continue;

reuse buffer

先不看EOP、XDP, 流程就很简单,唯一问题是reuse buffer。

其实在ixgbe初始化时rx_ring->rx_buffer_info队列上每个位置申请的内存都可以放两个包的,假设rx_ring->rx_buffer_info[0]上的空间为b1、b2。

网卡收第一个包时,存在了b1,但b2没被使用,在ixgbe_put_rx_buffer()里将b2放到next_to_alloc里,所以前面初始化时至少要空一个位置。这时

1
2
3
rx_ring->next_to_clean    = 1;
rx_ring->next_to_use   = 511;
rx_ring->next_to_alloc = 0;

b1、b2中间隔了510个包

当收第512个包时,网卡把它存在了b2,这时b1如果被消费了(大概率事件),那么b1又是可以被reuse,再把b1放到next_to_alloc。这样在skb及时消费的情况下就不用再申请内存给网卡。

EOP

EOP = End of Packet

如果收上来的数据没有EOP标志位,说明不是一个完整的包,要等后面的数据。

一个包对应一个skb结构,第一块数据到的时候就申请了skb,下一个数据到来的时候不需要再申请skb。

ixgbe做法是在ixgbe_is_non_eop()中将skb放到ntc=next_to_clean+1中的rx_ring->rx_buffer_info[ntc].skb = skb; 下一块数据到来时直接在ixgbe_get_rx_buffer()中取出这个skb,不需要再申请。之后收到的数据会依次放到skb_shinfo(skb)->frags[]中, 具体见ixgbe_add_rx_frag()。

XDP

XDP是在申请skb结构之前直接处理网卡收到的数据,如果要丢弃就不用申请skb了。

较新的Linux内核或官网驱动ixgbe-5.6.5中包含该功能。

测试

环境

ga-b250m-hd3 I5-6500, 4核4线程 X520-T1 ubuntu 16.04, linux-image-4.15.0-XX 主板设置只开启一核(直接丢包场景下增加核数处理能力线性增加)

netmap

netmap是一种网卡旁路方法,用netmap测试,只用一个cpu。 1个cpu, 2GHz, 12.8Mpps 100% 1个cpu, 3.6GHz,14.2Mpps 100%

模仿XDP丢包

ixgbe-5.6.5 的带了XDP功能, 在 ixgbe_run_xdp 直接 return ERR_PTR(-IXGBE_XDP_CONSUMED); 也就是全部丢包。 1个cpu, 2GHz, 14.2Mpps <20% ? 用 ixgbe-5.3.8 加上XDP功能 cpu需要 93% 1个cpu, 3.6GHz,14.2Mpps <10% ? 用 ixgbe-5.3.8 加上XDP功能 cpu需要 30%

pre_routing 丢包

1个cpu, 2GHz, 3.2Mpps 100% 1个cpu, 3.6GHz,5.5Mpps 100%

local_in 丢包

1个cpu, 2GHz, 1.2Mpps 100% 1个cpu, 3.6GHz,2.1Mpps 100% local_in比pre_routing多了路由查找,这个都太慢。

不处理

1个cpu, 2GHz, 1.2Mpps 100% 1个cpu, 3.6GHz,2.1Mpps 100%

参考

linux-source-4.15.0_4.15.0-65.74_all.deb

二层报文发送之qdisc实现分析

netmap配置

基于82599网卡的二层网络数据包接收

基于82599网卡的二层网络数据包发送

如何在一秒之内丢弃1000万个网络数据包

X520-T1 Linux内核发包14Mpps

目的

测试并优化Linux内核发包

环境

ga-b250m-d3h I7-7700k, 4核8线程,no_trubo=1即最高4.2GHz X520-T1 ubuntu 18.04, linux-image-4.15.0-XX

netmap

netmap是一种网卡旁路方法,用netmap测试,发包在 14.3Mpps, 单cpu 60%。测试命令

1
./build-apps/pkt-gen/pkt-gen -i enp3s0 -f tx -c 1 -p 1 -z -d 12.0.0.100:80

Linux多线程发包

在模块中用kthread创建多线程,每个线程构造skb,然后调dev_queue_xmit(skb)。测试发现cpu全部100%,发包大多在12Mpps

1
2
3
4
		tcp      udp
fq_codel  12.0Mpps/100%   12.8Mpps/100%
pfifo_fast    12.7Mpps/100%   14.0Mpps/100%
noqueue       12.0Mpps/100%   12.8Mpps/100%

猜测可能优化点

  1. qdisc流控
  2. skb的申请、释放

优化qdisc流控

qdisc流控可以简单看作把包存下,然后再发送出去。取消qdisc应该能减少一层消耗。

1
dev_queue_xmit() -> dev_hard_start_xmit() -> xmit_one() -> netdev_start_xmit() -> __netdev_start_xmit()

将上面的代码copy到模块,就跳过了qdisc。其实noqueue也是这个流程,但为什么noqueue的pps、cpu都没提升呢?

测试发现skb->xmit_more是关键,ixgbe中关于xmit_more的代码:

1
2
3
4
5
6
7
8
    if (netif_xmit_stopped(txring_txq(tx_ring)) || !skb->xmit_more) {
            writel(i, tx_ring->tail);

            /* we need this if more than one processor can write to our tail
             * at a time, it synchronizes IO on IA64/Altix systems
             */
            mmiowb();
    }

noqueue中没有设置xmit_more,但fq_codel,pfifo_fast等用到qdisc框架的都会设置xmit_more,qdisc是用skb->next将skb串起来,当skb->next != NULL时在dev_hard_start_xmit就会设置xmit_more=1。

照此优化,在我们的模块中每次申请多个包,将他们用skb->next串起来再发送。优化后发包在14.2Mpps,每个cpu都60%。

优化skb的申请、释放

每秒申请、释放14M的skb有点多。。。

我们可以创建percpu的skb池,申请的时候从池子里拿,释放的时候放回池子。

  1. 怎么做到释放时放回池子?用skb->destructor指向我们的回调函数即可
  2. 怎么做到不让Linux内核释放skb以及skb->data的空间?增加如下patch重新编译内核,设置skb池子里的skb->fclone = SKB_FCLONE_USER即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 2742528..cd240a9 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -532,6 +532,7 @@ enum {
  SKB_FCLONE_UNAVAILABLE, /* skb has no fclone (from head_cache) */
  SKB_FCLONE_ORIG,    /* orig skb (from fclone_cache) */
  SKB_FCLONE_CLONE,   /* companion fclone skb (from fclone_cache) */
+ SKB_FCLONE_USER,
 };
 
 enum {
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index cac95aa..b8fb200 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -585,6 +585,8 @@ static void kfree_skbmem(struct sk_buff *skb)
  case SKB_FCLONE_UNAVAILABLE:
      kmem_cache_free(skbuff_head_cache, skb);
      return;
+ case SKB_FCLONE_USER:
+     return;
 
  case SKB_FCLONE_ORIG:
      fclones = container_of(skb, struct sk_buff_fclones, skb1);
@@ -627,7 +629,7 @@ void skb_release_head_state(struct sk_buff *skb)
 static void skb_release_all(struct sk_buff *skb)
 {
  skb_release_head_state(skb);
- if (likely(skb->head))
+ if (likely(skb->head) && skb->fclone != SKB_FCLONE_USER)
      skb_release_data(skb);
 }

优化后: 单独优化skb申请释放: 发送14.2Mpps 每个cpu 50%

qdisc优化和skb申请释放优化: 发送14.2Mpps 每个cpu 20%

只用1个cpu:发送9.6Mpps, cpu 100% 用2个cpu:发送14.2Mpps, 每个cpu 80%

相比netmap单cpu 60%发送14.3Mpps, 两轮优化后还略差些,不过perf看热点已经在ixgbe里了

参考

linux-source-4.15.0_4.15.0-65.74_all.deb

二层报文发送之qdisc实现分析

netmap配置

基于82599网卡的二层网络数据包接收

基于82599网卡的二层网络数据包发送

如何在一秒之内丢弃1000万个网络数据包

X520-T1 Linux内核收发包14Mpps

收包

1
2
3
ethtool -K enp3s0 gro off
PRE_ROUTING 丢包,14Mpps
LOCAL_IN 丢包,待优化

发包(I7-7700k, no_trubo=1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
timer 8Mpps
timer+gso  tcp:11Mpps; udp:14Mpps,但是是IP分片的包
gso 需要关闭tso??  ethtool -K enp3s0 tso off gso off


kthread pfifo    14Mpps           cpu: 80%
kthread fq_codel 12~14Mpps        cpu: 100%
kthread noqueue  12Mpps           cpu: 100%


kthread pfifo static_skb    14Mpps    cpu: 40%
kthread fq_codel static_skb 14Mpps    cpu: 40%
kthread noqueue static_skb  12Mpps    cpu: 100%


kthread noqueue static_skb skb_list  14Mpps   cpu: 20%
				1cpu: 9Mpps, cpu 100%
				2cpu: 14Mpps, cpu 60%


M.2 SSD 增加收包si 20%,发包10%
I5-6500 只能发送12.5Mpps, netmap也一样 ???

转发

1
2
12Mpps 以上? 待测
把收到的包转发比申请一个包发出更优

细节待更新