Linux GSO逻辑分析(转载)

来源: https://www.cnblogs.com/lvyilong316/p/6818710.html

(注:kernel版本linux 2.6.32)

    GSO用来扩展之前的TSO,目前已经并入upstream内核。TSO只能支持tcp协议,而GSO可以支持tcpv4, tcpv6, udp等协议。在GSO之前,skb_shinfo(skb)有两个成员ufo_size, tso_size,分别表示udp fragmentation offloading支持的分片长度,以及tcp segmentation offloading支持的分段长度,现在都用skb_shinfo(skb)->gso_size代替。

skb_shinfo(skb)->ufo_segs, skb_shinfo(skb)->tso_segs也被替换成了skb_shinfo(skb)->gso_segs,表示分片的个数。 

gso用来delay 大包的分片,所以一直到dev_hard_start_xmit函数才会调用到。

  • l   dev_hard_start_xmit
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,

            struct netdev_queue *txq)

{

    const struct net_device_ops *ops = dev->netdev_ops;

    int rc;

 

    if (likely(!skb->next)) {

        if (!list_empty(&ptype_all))

            dev_queue_xmit_nit(skb, dev);

        //判断网卡是否需要协议栈负责gso

        if (netif_needs_gso(dev, skb)) {

//真正负责GSO操作的函数

            if (unlikely(dev_gso_segment(skb)))

                goto out_kfree_skb;

            if (skb->next)

                goto gso;

        }

//……

gso:

    do {

//指向GSO分片后的一个skb

        struct sk_buff *nskb = skb->next;

        skb->next = nskb->next;

        nskb->next = NULL;

        if (dev->priv_flags & IFF_XMIT_DST_RELEASE)

            skb_dst_drop(nskb);

//将通过GSO分片后的包逐个发出

        rc = ops->ndo_start_xmit(nskb, dev);

        if (unlikely(rc != NETDEV_TX_OK)) {

            nskb->next = skb->next;

            skb->next = nskb;

            return rc;

        }

        txq_trans_update(txq);

        if (unlikely(netif_tx_queue_stopped(txq) && skb->next))

            return NETDEV_TX_BUSY;

    } while (skb->next);

 

    skb->destructor = DEV_GSO_CB(skb)->destructor;

 

out_kfree_skb:

    kfree_skb(skb);

    return NETDEV_TX_OK;

}

    那是不是所有skb在发送时都要经过GSO的逻辑呢?显然不是,只有通过netif_needs_gso判断才会进入GSO的逻辑,下面我们看下netif_needs_gso是如何判断的。

static inline int netif_needs_gso(struct net_device *dev, struct sk_buff *skb)

{

    return skb_is_gso(skb) &&

           (!skb_gso_ok(skb, dev->features) ||

        unlikely(skb->ip_summed != CHECKSUM_PARTIAL));

}

 

注意这里最后用了一个unlikely,因为如果通过前面的判断,说明网卡是支持GSO的,而一般网卡支持GSO也就会支持CHECKSUM_PARTIAL。进入GSO处理的第一个前提是skb_is_gso函数返回真,看下skb_is_gso的逻辑:

static inline int skb_is_gso(const struct sk_buff *skb)

{   

    return skb_shinfo(skb)->gso_size;

}

 

skb_is_gso的逻辑很简单,返回skb_shinfo(skb)->gso_size,所以进入GSO处理逻辑的必要条件之一是skb_shinfo(skb)->gso_size不为0,那么这个字段的含义是什么呢?gso_size表示生产GSO大包时的数据包长度,一般时mss的整数倍。下面看skb_gso_ok,如果这个函数返回False,就可以进入GSO处理逻辑。

static inline int skb_gso_ok(struct sk_buff *skb, int features)

{

    return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&

           (!skb_has_frags(skb) || (features & NETIF_F_FRAGLIST));

}

 

skb_shinfo(skb)->gso_type包括SKB_GSO_TCPv4, SKB_GSO_UDPv4,同时NETIF_F_XXX的标志也增加了相应的bit,标识设备是否支持TSO, GSO, e.g. 

 NETIF_F_TSO = SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT

 NETIF_F_UFO = SKB_GSO_UDPV4 << NETIF_F_GSO_SHIFT
 
 #define NETIF_F_GSO_SHIFT 16
 

通过以上三个函数分析,以下三个情况需要协议栈负责GSO。

    下面看GSO的协议栈处理逻辑,入口就是dev_gso_segment。

  • l   dev_gso_segment

    协议栈的GSO逻辑是在dev_gso_segment中进行的。这个函数主要完成对skb的分片,并将分片存放在原始skb的skb->next中,这也是GSO的主要工作

static int dev_gso_segment(struct sk_buff *skb)

{

    struct net_device *dev = skb->dev;

    struct sk_buff *segs;

    int features = dev->features & ~(illegal_highdma(dev, skb) ?

                     NETIF_F_SG : 0);

 

    segs = skb_gso_segment(skb, features);

 

    /* Verifying header integrity only. */

    if (!segs)

        return 0;

 

    if (IS_ERR(segs))

        return PTR_ERR(segs);

 

    skb->next = segs;

    DEV_GSO_CB(skb)->destructor = skb->destructor;

    skb->destructor = dev_gso_skb_destructor;

 

    return 0;

}

 

    主要分片逻辑由skb_gso_segment来处理,这里我们主要看下析构过程,此时skb经过分片之后已经是一个skb list,通过skb->next串在一起,此时把初始的skb->destructor函数存到skb->cb中,然后把skb->destructor变更为dev_gso_skb_destructor。dev_gso_skb_destructor会把skb->next一个个通过kfree_skb释放掉,最后调用DEV_GSO_CB(skb)->destructor,即skb初始的析构函数做最后的清理。

  • l   skb_gso_segment

这个函数将skb分片,并返回一个skb list。如果skb不需要分片则返回NULL。

struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)

{

    struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);

    struct packet_type *ptype;

    __be16 type = skb->protocol;

    int err;

 

    skb_reset_mac_header(skb);

    skb->mac_len = skb->network_header - skb->mac_header;

    __skb_pull(skb, skb->mac_len);

    //如果skb->ip_summed 不是 CHECKSUM_PARTIAL,那么报个warning,因为GSO类型的skb其ip_summed一般都是CHECKSUM_PARTIAL

    if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {

        struct net_device *dev = skb->dev;

        struct ethtool_drvinfo info = {};

        WARN(……);

        if (skb_header_cloned(skb) &&

            (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))

            return ERR_PTR(err);

    }

    rcu_read_lock();

    list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {

        if (ptype->type == type && !ptype->dev && ptype->gso_segment) {

            if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {

                // 如果ip_summed != CHECKSUM_PARTIAL,则调用上层协议的gso_send_check

                err = ptype->gso_send_check(skb);

                segs = ERR_PTR(err);

                if (err || skb_gso_ok(skb, features))

                    break;

                __skb_push(skb, (skb->data -

                skb_network_header(skb)));

        }

           //把skb->data指向network header,调用上层协议的gso_segment完成分片

            segs = ptype->gso_segment(skb, features);

            break;

        }

    }

    rcu_read_unlock();

//把skb->data再次指向mac header

    __skb_push(skb, skb->data - skb_mac_header(skb));

 

    return segs;

}

 

  最终追调用上层协议的gso处理函数,对于IP协议,在注册IP的packet_type时,其gso处理函数被初始化为inet_gso_segment。下面我们看inet_gso_segment的处理流程。

  • l   inet_gso_segment

   ./net/ipv4/af_inet.c

IP层GSO操作只是提供接口给链路层来访问传输层(TCP、UDP),因此IP层实现的接口只是根据分段数据报获取对应的传输层接口,并对完成GSO分段后的IP数据报重新计算校验和。

static struct sk_buff *inet_gso_segment(struct sk_buff *skb, int features)

{

    struct sk_buff *segs = ERR_PTR(-EINVAL);

    struct iphdr *iph;

    const struct net_protocol *ops;

    int proto;

    int ihl;

    int id;

    unsigned int offset = 0;

 

    if (!(features & NETIF_F_V4_CSUM))

        features &= ~NETIF_F_SG;

   //校验待软GSO分段的的skb,其gso_tpye是否存在其他非法值

    if (unlikely(skb_shinfo(skb)->gso_type &

             ~(SKB_GSO_TCPV4 |

               SKB_GSO_UDP |

               SKB_GSO_DODGY |

               SKB_GSO_TCP_ECN |

               0)))

        goto out;

    //分段数据至少大于IP首部长度

    if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))

        goto out;

    //检验首部中的长度字段是否有效

    iph = ip_hdr(skb);

    ihl = iph->ihl * 4;

    if (ihl < sizeof(*iph))

        goto out;

   //再次通过首部中的长度字段检测skb长度是否有效

  if (unlikely(!pskb_may_pull(skb, ihl)))

        goto out;

    //注意:这里已经将data偏移到了传送层头部了,去掉了IP头

    __skb_pull(skb, ihl);

    skb_reset_transport_header(skb);//设置传输层头部位置

    iph = ip_hdr(skb);

    id = ntohs(iph->id);//取出首部中的id字段

    proto = iph->protocol & (MAX_INET_PROTOS - 1);//取出IP首部的协议值,用于定位与之对应的传输层接口(tcp还是udp)

    segs = ERR_PTR(-EPROTONOSUPPORT);

 

    rcu_read_lock();

    ops = rcu_dereference(inet_protos[proto]);//根据协议字段取得上层的协议接口

    if (likely(ops && ops->gso_segment))

        segs = ops->gso_segment(skb, features);//调用上册协议的GSO处理函数

    rcu_read_unlock();

 

    if (!segs || IS_ERR(segs))

        goto out;

    //开始处理分段后的skb

    skb = segs;

    do {

        iph = ip_hdr(skb);

        if (proto == IPPROTO_UDP) {//对于UDP进行的IP分片的头部处理逻辑

            iph->id = htons(id);//所有UDP的IP分片id都相同

            iph->frag_off = htons(offset >> 3);//ip头部偏移字段单位为8字节

            if (skb->next != NULL)

                iph->frag_off |= htons(IP_MF);//设置分片标识

            offset += (skb->len - skb->mac_len - iph->ihl * 4);

        } else

        iph->id = htons(id++);//对于TCP报,分片后IP头部中id加1

        iph->tot_len = htons(skb->len - skb->mac_len);

        iph->check = 0;

        //计算校验和,只是IP头部的

        iph->check = ip_fast_csum(skb_network_header(skb), iph->ihl);

    } while ((skb = skb->next));

 

out:

    return segs;

}

复制代码

 

这里有个问题,UDP经过GSO分片后每个分片的IP头部id是一样的,这个符合IP分片的逻辑,但是为什么TCP的GSO分片,IP头部的id会依次加1呢?原因是: tcp建立三次握手的过程中产生合适的mss(具体的处理机制参见TCP/IP详解P257),这个mss肯定是<=网络层的最大路径MTU,然后tcp数据封装成ip数据包通过网络层发送,当服务器端传输层接收到tcp数据之后进行tcp重组。所以正常情况下tcp产生的ip数据包在程中是不会发生分片的!由于GSO应该保证对外透明,所以其效果应该也和在TCP层直接分片的效果是一样的,所以这里UDP的处理是IP分片逻辑,但对TCP的处理是构造新的skb逻辑

 

l  小结:对于GSO

    UDP:所有分片ip头部id都相同,设置IP_MF分片标志(除最后一片(等同于IP分片)

    TCP:分片后,每个分片IP头部中id1, (等同于TCP分段)

 

下面分别看对于TCP和UDP调用不通的GSO处理函数。对于TCP其GSO处理函数为tcp_tso_segment。

  • l   tcp_tso_segment

./net/ipv4/tcp.c

struct sk_buff *tcp_tso_segment(struct sk_buff *skb, int features)

{

    struct sk_buff *segs = ERR_PTR(-EINVAL);

    struct tcphdr *th;

    unsigned thlen;

    unsigned int seq;

    __be32 delta;

    unsigned int oldlen;

    unsigned int mss;

    //检测报文长度至少由tcp头部长度

    if (!pskb_may_pull(skb, sizeof(*th)))

        goto out;

 

    th = tcp_hdr(skb);

    thlen = th->doff * 4;//TCP头部的长度字段单位为4字节

    if (thlen < sizeof(*th))

        goto out;

   //再次通过首部中的长度字段检测skb长度是否有效

    if (!pskb_may_pull(skb, thlen))

        goto out;

    //把tcp header移到skb header里,把skb->len存到oldlen中,此时skb->len就只有ip payload的长度(包含TCP首部)

    oldlen = (u16)~skb->len;

    __skb_pull(skb, thlen); //data指向tcp payload

    //这里可以看出gso_size的含义就是mss

    mss = skb_shinfo(skb)->gso_size;

    if (unlikely(skb->len <= mss))//如果skb长度小于mss就不需要GSO分片处理了

        goto out;

    if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {

        /* Packet is from an untrusted source, reset gso_segs. */

        int type = skb_shinfo(skb)->gso_type;

        //校验待软GSO分段的的skb,其gso_tpye是否存在其他非法值

        if (unlikely(type &

                 ~(SKB_GSO_TCPV4 |

                   SKB_GSO_DODGY |

                   SKB_GSO_TCP_ECN |

                   SKB_GSO_TCPV6 |

                   0) ||

                 !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))

            goto out;

        //计算出skb按照mss的长度需要分多少片,赋值给gso_segs

        skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);

 

        segs = NULL;

        goto out;

    }

    //skb_segment是真正的分段实现,后面再分析

    segs = skb_segment(skb, features);

    if (IS_ERR(segs))

        goto out;

 

    delta = htonl(oldlen + (thlen + mss));

 

    skb = segs;

    th = tcp_hdr(skb);

    seq = ntohl(th->seq);

    //下面是设置每个分片的tcp头部信息

    do {

        th->fin = th->psh = 0;

        //计算每个分片的校验和

        th->check = ~csum_fold((__force __wsum)((__force u32)th->check +

                       (__force u32)delta));

        if (skb->ip_summed != CHECKSUM_PARTIAL)

            th->check =csum_fold(csum_partial(skb_transport_header(skb),

                            thlen, skb->csum));

        //重新初始化每个分片的序列号

        seq += mss;

        skb = skb->next;

        th = tcp_hdr(skb);

 

        th->seq = htonl(seq);

        th->cwr = 0;

    } while (skb->next);

 

    delta = htonl(oldlen + (skb->tail - skb->transport_header) +

              skb->data_len);

    th->check = ~csum_fold((__force __wsum)((__force u32)th->check +

                (__force u32)delta));

    if (skb->ip_summed != CHECKSUM_PARTIAL)

        th->check = csum_fold(csum_partial(skb_transport_header(skb),

                           thlen, skb->csum));

 

out:

    return segs;

}

 

    从上面可以看出,每个TCP的GSO分片是包含了TCP头部信息的,这也符合TCP层的分段逻辑。另外注意这里传递给skb_segment做分段时是不带TCP首部的。对于UDP,其GSO处理函数为udp4_ufo_fragment。

  • l   udp4_ufo_fragment

./net/ipv4/udp.c

​
struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb, int features)

{

    struct sk_buff *segs = ERR_PTR(-EINVAL);

    unsigned int mss;

    int offset;

    __wsum csum;

 

    mss = skb_shinfo(skb)->gso_size;

    if (unlikely(skb->len <= mss))

        goto out;

 

    if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {

        /* Packet is from an untrusted source, reset gso_segs. */

        int type = skb_shinfo(skb)->gso_type;

 

        if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||

                 !(type & (SKB_GSO_UDP))))

            goto out;

 

        skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);

 

        segs = NULL;

        goto out;

    }

 

    /* Do software UFO. Complete and fill in the UDP checksum as HW cannot

     * do checksum of UDP packets sent as multiple IP fragments.

*/

    //计算udp的checksum

    offset = skb->csum_start - skb_headroom(skb);

    csum = skb_checksum(skb, offset, skb->len - offset, 0);

    offset += skb->csum_offset;

    *(__sum16 *)(skb->data + offset) = csum_fold(csum);

    skb->ip_summed = CHECKSUM_NONE;

    //这里传递给skb_segment做分片时是没有将UDP首部去除的

    segs = skb_segment(skb, features);

out:

    return segs;

}
​

 

注意这里传递给skb_segment 做分片是带有udp首部的,分片将udp首部作为普通数据切分,这也意味着对于udp的GSO分片,只有第一片有UDP首部。udp的分段其实和ip的分片没什么区别,只是多一个计算checksum的步骤,下面看完成分片的关键函数skb_segment。

  • l   skb_segment

/net/core/skbuff.c

struct sk_buff *skb_segment(struct sk_buff *skb, int features)

{

    struct sk_buff *segs = NULL;

    struct sk_buff *tail = NULL;

    struct sk_buff *fskb = skb_shinfo(skb)->frag_list;

    unsigned int mss = skb_shinfo(skb)->gso_size;

    unsigned int doffset = skb->data - skb_mac_header(skb);//mac头+ip头+tcp头 或mac头+ip头(对于UDP传入时没有将头部偏移过去)

    unsigned int offset = doffset;

    unsigned int headroom;

    unsigned int len;

    int sg = features & NETIF_F_SG;

    int nfrags = skb_shinfo(skb)->nr_frags;

    int err = -ENOMEM;

    int i = 0;

    int pos;

 

    __skb_push(skb, doffset);

    headroom = skb_headroom(skb);

    pos = skb_headlen(skb);//pos初始化为线性区长度

 

    do {

        struct sk_buff *nskb;

        skb_frag_t *frag;

        int hsize;

        int size;

        // offset为分片已处理的长度,len为skb->len减去直到offset的部分。开始时,offset只是mac header + ip header + tcp header的长度,len即tcp payload的长度。随着segment增加, offset每次都增加mss长度。因此len的定义是每个segment的payload长度(最后一个segment的payload可能小于一个mss长度)

        len = skb->len - offset;

        if (len > mss)//len为本次要创建的新分片的长度

            len = mss;

       // hsize为线性区部分的payload减去offset后的大小,如果hsize小于0,那么说明payload在skb的frags或frag_list中。随着offset一直增长,必定会有hsize一直<0的情况开始出现,除非skb是一个完全linearize化的skb

        hsize = skb_headlen(skb) - offset;

        //这种情况说明线性区已经没有tcp payload的部分,需要pull数据过来
 
        if (hsize < 0)

hsize = 0;

       //如果不支持NETIF_F_SG或者hsize大于len,那么hsize就为len(本次新分片的长度),此时说明segment的payload还在skb 线性区中

        if (hsize > len || !sg)

            hsize = len;

 

        if (!hsize && i >= nfrags) {// hsize为0,表示需要从frags数组或者frag_list链表中拷贝出数据,i >= nfrags说明frags数组中的数据也拷贝完了,下面需要从frag_list链表中拷贝数据了

            BUG_ON(fskb->len != len);

 

pos += len;

            //frag_list的数据不用真的拷贝,只需要拷贝其skb描述符,就可以复用其数据区

            nskb = skb_clone(fskb, GFP_ATOMIC);//拷贝frag_list中的skb的描述符

            fskb = fskb->next;//指向frag_list的下一个skb元素

 

            if (unlikely(!nskb))

                goto err;

 

             hsize = skb_end_pointer(nskb) - nskb->head;

            //保证新的skb的headroom有mac header+ip header+tcp/udp+header的大小

            if (skb_cow_head(nskb, doffset + headroom)) {

                kfree_skb(nskb);

                goto err;

            }

           //调整truesize,使其包含本次已分片的数据部分长度(hsize)

            nskb->truesize += skb_end_pointer(nskb) - nskb->head -

                      hsize;

            skb_release_head_state(nskb);

            __skb_push(nskb, doffset);

        } else {//数据从线性区或者frags数组中取得

            //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区

            nskb = alloc_skb(hsize + doffset + headroom,GFP_ATOMIC);

 

            if (unlikely(!nskb))

                goto err;

            skb_reserve(nskb, headroom);

            __skb_put(nskb, doffset);

        }

 

        if (segs)

            tail->next = nskb;

        else

            segs = nskb;

        tail = nskb;

        //拷贝skb结构中的成员

        __copy_skb_header(nskb, skb);

        nskb->mac_len = skb->mac_len;

 

        /* nskb and skb might have different headroom */

        if (nskb->ip_summed == CHECKSUM_PARTIAL)

            nskb->csum_start += skb_headroom(nskb) - headroom;

 

        skb_reset_mac_header(nskb);

        skb_set_network_header(nskb, skb->mac_len);

        nskb->transport_header = (nskb->network_header +

        skb_network_header_len(skb));

       //把skb->data开始doffset长度的内容拷贝到nskb->data中

        skb_copy_from_linear_data(skb, nskb->data, doffset);

        // fskb被初始化为skb_shinfo(skb)->frag_list,现在如果不再相等,说明已经开始拷贝frag_list链表中的数据,不用继续后面的逻辑了(后面的逻辑是从线性区或者frags数组中拷贝的逻辑)

        if (fskb != skb_shinfo(skb)->frag_list)

            continue;

        //如果不支持NETIF_F_SG,说明frags数组中没有数据,只考虑从线性区中拷贝数据

        if (!sg) {

            nskb->ip_summed = CHECKSUM_NONE;

           //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区

            nskb->csum = skb_copy_and_csum_bits(skb, offset,

                                skb_put(nskb, len), len, 0);

            continue;

        }

 

        frag = skb_shinfo(nskb)->frags;

        //如果hsize不为0,那么拷贝hsize的内容到nskb的线性区中

        skb_copy_from_linear_data_offset(skb, offset,skb_put(nskb, hsize), hsize);

        //注意:每次要拷贝的数据长度是len,其中hsize是位于线性区中,但是随着线性区数据逐渐被处理,hsize可能不够len,这时剩下的(len-hsize)长度就要从frags数组中拷贝了

       while (pos < offset + len && i < nfrags) { //从frags数组中拷贝数据

            *frag = skb_shinfo(skb)->frags[i];

            get_page(frag->page);

            size = frag->size;

           //pos初始为线性区长度,后来表示已经被拷贝的长度

            if (pos < offset) {

                frag->page_offset += offset - pos;

                frag->size -= offset - pos;

            }

            //frags数组中的数据并不是真的拷贝,而是nskb的frags数组直接指向相应的page

            skb_shinfo(nskb)->nr_frags++;

 

            if (pos + size <= offset + len) {

                i++;

                pos += size;

            } else {

                frag->size -= pos + size - (offset + len);

                goto skip_fraglist;

            }

            frag++;

        }

         //如果把frags数组中的数据拷贝完还不够len长度,则需要从frag_list中拷贝了

        if (pos < offset + len) {

            struct sk_buff *fskb2 = fskb;//指向frag_list

 

            BUG_ON(pos + fskb->len != offset + len);

 

            pos += fskb->len;

            fskb = fskb->next;

 

            if (fskb2->next) {

                fskb2 = skb_clone(fskb2, GFP_ATOMIC);

                if (!fskb2)

                    goto err;

            } else

                skb_get(fskb2);

 

SKB_FRAG_ASSERT(nskb);

            //这里也不是真的拷贝数据,而是nskb的frag_list直接链上老的frag_list中的元素

            skb_shinfo(nskb)->frag_list = fskb2;

        }

skip_fraglist:

        nskb->data_len = len - hsize;

        nskb->len += nskb->data_len;

        nskb->truesize += nskb->data_len;

    } while ((offset += len) < skb->len);//完成一个nskb之后,继续下一个seg,一直到offset >= skb->len

    return segs;

 

err:

    while ((skb = segs)) {

        segs = skb->next;

        kfree_skb(skb);

    }

    return ERR_PTR(err);

}

    从上面的分片过程可以看出,分成的小skb并不一定都是线性话的,如果之前的skb存在frags数组或者frag_list,则分成的小skb也可能有指向非线性区域。并不用担心网卡不支持分散聚合IO,因为之前如果能产生这些非线性数据,就说明网卡一定是支持的。

最后回顾下整个协议栈的GSO处理逻辑,如下图:

 

我们再看一下skb组织形式在GSO前后的变化:

    GSO之后如下,注意GSO之后也是可能带有frags的。

 

转载自博客: https://www.cnblogs.com/lvyilong316/p/6818710.html

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值