zoukankan      html  css  js  c++  java
  • linux 3.10 gro的理解和改进

    gro,将同一个flow的一定时间范围之内的skb进行合并,减少协议栈的消耗,用于收包性能提升。gro网上的资料很多,但是都很少谈到gro的改进,刚好身边有个同事也想改这块的内容,

    所以将最近看的gro内容总结一下,作为记录。

    gro的层次,很少有资料提到,可能是大牛们觉得太简单,但我还是记录一下,毕竟我基础不好。

     先看关键的数据结构,然后分析流程:

    为了在skb中记录相关的gro信息,使用了skb的cb字段。

    crash>  napi_gro_cb
    struct napi_gro_cb {
        void *frag0;
        unsigned int frag0_len;
        int data_offset;
        u16 flush;
        u16 flush_id;
        u16 count;
        u16 gro_remcsum_start;
        unsigned long age;
        u16 proto;
        u8 encap_mark : 1;
        u8 csum_valid : 1;
        u8 csum_cnt : 3;
        u8 is_ipv6 : 1;
        u8 free : 2;
        u8 same_flow : 1;
        u8 recursion_counter : 4;
        u8 is_atomic : 1;
        __wsum csum;
        struct sk_buff *last;
    }
    SIZE: 48

    48字节的cb字段,被用完了。

    所有的packet 级别的gro的类型,放在一个公共链表头 offload_base 变量中管理,我测试的系统中的packet级别的gro类型有:

    crash> list packet_offload.list  -H offload_base -s packet_offload
    ffffffff81b41bc0
    struct packet_offload {
      type = 8,
      priority = 0,
      callbacks = {
        gso_segment = 0xffffffff816155b0 <inet_gso_segment>,
        gro_receive = 0xffffffff816159a0 <inet_gro_receive>,
        gro_complete = 0xffffffff816148c0 <inet_gro_complete>
      },
      list = {
        next = 0xffffffff81b43b40 <ipv6_packet_offload+32>,
        prev = 0xffffffff81b3f0e0 <offload_base>
      }
    }
    ffffffff81b43b20
    struct packet_offload {
      type = 56710,
      priority = 0,
      callbacks = {
        gso_segment = 0xffffffff8168c670 <ipv6_gso_segment>,
        gro_receive = 0xffffffff8168c300 <ipv6_gro_receive>,
        gro_complete = 0xffffffff8168c120 <ipv6_gro_complete>
      },
      list = {
        next = 0xffffffff81b3f7c0 <eth_packet_offload+32>,
        prev = 0xffffffff81b41be0 <ip_packet_offload+32>
      }
    }
    ffffffff81b3f7a0
    struct packet_offload {
      type = 22629,
      priority = 10,
      callbacks = {
        gso_segment = 0x0,
        gro_receive = 0xffffffff815bbd60 <eth_gro_receive>,
        gro_complete = 0xffffffff815bbbe0 <eth_gro_complete>
      },
      list = {
        next = 0xffffffff81b3f0e0 <offload_base>,
        prev = 0xffffffff81b43b40 <ipv6_packet_offload+32>
      }
    }

    所有的inet层的gro回调,都存储在inet_offloads 数组中,根据当前加载的模块,本机器对应支持的gro就有:

    p inet_offloads
    inet_offloads = $48 =
     {0x0, 0x0, 0x0, 0x0, 0xffffffff8176fd80 <ipip_offload>, 0x0, 0xffffffff8176f220 <tcpv4_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff8176f560 <udpv4_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff81777680 <sit_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff81770be0 <gre_offload>, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
    。。。。
     0x0, 0x0, 0x0}

    gro的调用查找过程如下:

    从dev层,根据到来的skb,可以根据skb->protocol 作为type的类型,比如type是 .type = cpu_to_be16(ETH_P_IP),然后才会进入 ip_packet_offload 这个层次,

    在 offload_base这个链表头找到对应的type,然后获取对应的callback.gro_receive 函数。

    找到了对应的inet_gro_receive,就进入了packet层,那么根据iph->protocol,就在 net_offload 数组中,找到对应协议类型的gro结构,比如找到的是 tcpv4_offload。

    那么针对tcp的gro,其i40e驱动的调用顺序就是:

    i40e_napi_poll--->|i40e_clean_tx_irq

                            --->|i40e_clean_rx_irq-->napi_gro_receive-->dev_gro_receive-->inet_gro_receive-->tcp4_gro_receive

    对应的堆栈如下:

    [root@localhost caq]# stap -d i40e netif_rx.stp
    System Call Monitoring Started (10 seconds)...
    WARNING: DWARF expression stack underflow in CFI
     0xffffffff816041a0 : tcp4_gro_receive+0x0/0x1b0 [kernel]
     0xffffffff81615be9 : inet_gro_receive+0x249/0x290 [kernel]
     0xffffffff815951b0 : dev_gro_receive+0x2b0/0x3e0 [kernel]
     0xffffffff815955d8 : napi_gro_receive+0x38/0x130 [kernel]-------------gro处理开始
     0xffffffffc01f4bde : i40e_clean_rx_irq+0x3fe/0x990 [i40e]
     0xffffffffc01f5440 : i40e_napi_poll+0x2d0/0x710 [i40e]
     0xffffffff81594cf3 : net_rx_action+0x173/0x380 [kernel]
     0xffffffff8109404d : __do_softirq+0xfd/0x290 [kernel]
     0xffffffff816c8afc : call_softirq+0x1c/0x30 [kernel]
     0xffffffff8102d435 : do_softirq+0x65/0xa0 [kernel]
     0xffffffff81094495 : irq_exit+0x175/0x180 [kernel]
     0xffffffff816c9da6 : __irqentry_text_start+0x56/0xf0 [kernel]
     0xffffffff816bc362 : ret_from_intr+0x0/0x15 [kernel]

    理清楚了大的流程,我们再来看目前的gro小的流程。在收到一个skb的时候,我们把它和napi_struct中的gro_list的skb进行比较,看能否合并,当然合并的前提是同一个flow的,

    除此之外,除了满足同一个flow,还有很多要求。

    那这个gro_list最大多长呢?

    /* Instead of increasing this, you should create a hash table. */
    #define MAX_GRO_SKBS 8

    才8个,这8个skb跟新进来的skb是flow相同的概率其实真不高,比如在tcp4_gro_receive中,我想跟踪它接着调用的 skb_gro_receive,无奈由于合并的几率太低而无法跟到,

    毕竟还有一个在gro_list中停留的时间限制,为一个jiffies。后来修改了jiffies并且修改了合并的条件才能抓到。

     当然,根据作者的注释,与其将这8改大,不如改成一个hash表,不同的skb先哈希到一个flow链,然后在链中比较看能否合并,这样对于gro流程需要改动为:

    1.创建flow的hash表,让skb中看到flow,然后在flow的冲突链中找对应的gro_list,然后看能否合并。

    2.percpu模式,不适用napi_struct来管理gro_list.

    3.修改合并条件,比如对于tcp的ack来说,目前不带数据的ack无法合并,因为才54个字节,而以太网发出的时候会填充,导致不满足如下条件:

    flush = (u16)((ntohl(*(__be32 *)iph) ^ skb_gro_len(skb)) | (id & ~IP_DF));

    但对于流媒体服务器来说,纯ack占入向的比例很高,需要将条件改动,由于ack还涉及到快发流程的进入和退出,所以ack合并还是有一些工作要做的。

    4.修改间隔,目前限制死了是一个jiffies,比如服务器8M左右的发送码率,收到的ack间隔可以释放放大,不然合并几率也比较低,当然这个是以tcp的send_buf中的数据占用更多内存为前提的。

       所以需要一个导出到/proc文件系统的间隔字段来控制。

    5.对于低速发送码率的服务器来说,可以关闭gro,对于lvs服务器来说,应该关闭gro。

    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    木有晚餐吃的教训暴力图的时候
    HDU1231最大连续子序列DP
    java连连看(GUI有进度条加背景音乐)
    HDU2064简单hanno塔
    HDU1232并查集入门(畅通工程)
    HDU3460Ancient Printer(trie)
    在window下搭建php+apche+masql的方法(个人的蛋疼经历,绝对可靠)
    Java学生管理系统(GUI)(又写了这种破玩意儿了老师,放过我们吧,能不能来点新意)
    VUE使用elpagination添加分页功能
    JS 中深拷贝的几种实现方法
  • 原文地址:https://www.cnblogs.com/10087622blog/p/9922121.html
Copyright © 2011-2022 走看看