TSO GSO

以前有过一篇TSO-GSO文章;目前再来回顾一下:

 TSO与GSO的重要区别
1, TSO只有第一个分片有TCP头和IP头,接着的分段只有IP头。硬件tso由ixgbe_tso(ixgbe网卡)等函数来处理tso ,然后在请求DMA,写寄存器,通知网卡发送数据。
2, GSO在分段时会调用TCP或UDP的回调函数(udp4_ufo_fragment)为每个分段都加上IP头,由于分段是通过mss设置的(mss由发送端设置),所以长度仍然可能超过mtu值,所以在IP层还得再分片(代码位于dev_hard_start_xmit)。

收数据

  LRO(Large Receive Offload),TSO是发,LRO是收。将多个TCP分段聚合成一个skb结构,以减小上层协议栈的skb的开销。skb的数据保存在skb->data中,分段的数据保存在skb_shared_info->frag_list中。
  GRO(Generic Receive Offloading),GSO是发,GRO是收。LRO使用发送方和目的地IP地址,IP封包ID,L4协议三者来区分段,对于从同一个SNAT域的两个机器发向同一目的IP的两个封包可能会存在相同的IP封包ID(因为不是全局分配的ID),这样会有所谓的卷绕的bug。GRO采用发送方和目的地IP地址,源/目的端口,L4协议三者来区分作为改进。所以对于后续的驱动都应该使用GRO的接口,而不是LRO。另外,GRO也支持多协议。

  • 1, 物理网卡不支持GRO时, 使用LRO在驱动处合并了多个skb一次性通过网络栈,对CPU负荷的减轻是显然的。
  • 2, 物理网卡不支持LRO时,使用GRO在从驱动接收数据那一刻合并了多个skb一次性通过网络栈,对CPU负荷的减轻是显然的

关于GRO的相关分析见此篇文章

ip_queue_xmit--->ip_output--->ip_finish_output--->dev_queue_xmit

继续IP层的GSO分片

static int ip_finish_output_gso(struct net *net, struct sock *sk,
                struct sk_buff *skb, unsigned int mtu)
{
    netdev_features_t features;
    struct sk_buff *segs;
    int ret = 0;

    /* common case: locally created skb or seglen is <= mtu */
    //只有ip forward流程该条件才会不成立,否则该条件成  也就是本地发包是会直接走 ip_finish_output2 发包;
    if (((IPCB(skb)->flags & IPSKB_FORWARDED) == 0) ||
          skb_gso_network_seglen(skb) <= mtu)
        return ip_finish_output2(net, sk, skb);

    /* Slowpath -  GSO segment length is exceeding the dst MTU.
     *
     * This can happen in two cases:
     * 1) TCP GRO packet, DF bit not set
     * 2) skb arrived via virtio-net, we thus get TSO/GSO skbs directly
     * from host network stack.
     */
    features = netif_skb_features(skb);
    BUILD_BUG_ON(sizeof(*IPCB(skb)) > SKB_SGO_CB_OFFSET);
    //调用回调函数, 对于TCP则是调用TCP的gso_segment回调函数进行TCP GSO分段 inet_gso_segment--->tcp4_gso_segment
    segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK);//skb gso报文分段 ------>此处可以参考GRO的实现
    if (IS_ERR_OR_NULL(segs)) {
        kfree_skb(skb);
        return -ENOMEM;
    }

    consume_skb(skb);

    do {
        struct sk_buff *nskb = segs->next;
        int err;

        segs->next = NULL;
        //分段报文经过ip分片后通过ip_finish_output2发送
        err = ip_fragment(net, sk, segs, mtu, ip_finish_output2);

        if (err && ret == 0)
            ret = err;
        segs = nskb;
    } while (segs);

    return ret;
}

   IP层发包后进入二层发包!!

二层发包GSO

  IP层发包调用__dev_queue_xmit--->sch_direct_xmit--->validate_xmit_skb_list()  检测报文是否需要进行GSO 等分段:

 

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
            struct netdev_queue *txq)
{
///...
        if (netif_needs_gso(skb, features)) {
            if (unlikely(dev_gso_segment(skb, features))) ///GSO(software offload)
                goto out_kfree_skb;

-------------------------------------
}

 

 

 

int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
            struct net_device *dev, struct netdev_queue *txq,
            spinlock_t *root_lock, bool validate)
{
    int ret = NETDEV_TX_BUSY;
---------------------------------------
    /* Note that we validate skb (GSO, checksum, ...) outside of locks */
    if (validate)
        skb = validate_xmit_skb_list(skb, dev);
-----------------------------------
}

 

static inline bool skb_gso_ok(struct sk_buff *skb, netdev_features_t features)
{
    return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&
           (!skb_has_frag_list(skb) || (features & NETIF_F_FRAGLIST));
    // //feature包含gso_type 并且skb没有frag_list(---->有frag_list 表示不支持SG)或者feature包含NETIF_F_FRAGLIST(表示SG Scatter/gather IO. )
}


static inline bool netif_needs_gso(struct sk_buff *skb,
                   netdev_features_t features)
{
    //判断是否需要进行GSO分段,主要 两个参数features(硬件支持的特性)和skb报文。
    //主要判断条件是features是否包含skb->gso_type。那么我们来看下features是怎么得到的,
    //其实是通过netif_skb_features得到的。

//skb 为gso报文   && (gso 没有准备好---且feature不包含skb->gso_type)
//skb 为gso报文,且skb_ipsummed不为CHECKSUM_PARTIAL和CHECKSUM_UNNECESSARY
    return skb_is_gso(skb) && (!skb_gso_ok(skb, features) ||
        unlikely((skb->ip_summed != CHECKSUM_PARTIAL) &&
             (skb->ip_summed != CHECKSUM_UNNECESSARY)));
}

 

对于GSO相关逻辑可以参考:mac-ip-tcp-GSO

/* This data is invariant across clones and lives at
 * the end of the header data, ie. at skb->end.
 */
struct skb_shared_info {
    unsigned char    nr_frags;
    __u8        tx_flags;
    unsigned short    gso_size;//生成GSO段时的MSS,因为GSO段的长度是与发送段的套接口中合适MSS的倍数
    /* Warning: this field is not always filled in (UFO)! */
    unsigned short    gso_segs;//GSo的长度是gso_size的倍数,即用gso_size 来分割大段时产生的段数
    unsigned short  gso_type;// skb 中数据支持的GSO类型
    struct sk_buff    *frag_list;
    struct skb_shared_hwtstamps hwtstamps;
    u32        tskey;
    __be32          ip6_frag_id;

    /*
     * Warning : all fields before dataref are cleared in __alloc_skb()
     */
    atomic_t    dataref;

    /* Intermediate layers must ensure that destructor_arg
     * remains valid until skb destructor */
    void *        destructor_arg;

    /* must be last field, see pskb_expand_head() */
    skb_frag_t    frags[MAX_SKB_FRAGS];
};

 

posted @ 2022-04-22 17:11  codestacklinuxer  阅读(182)  评论(0编辑  收藏  举报