Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3445673
  • 博文数量: 198
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7256
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(198)

文章存档

2024年(1)

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2017-05-04 23:53:33

Linux GSO逻辑分析

——lvyilong316(转载请注明出处
(注:对应linux 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,表示分片的个

gsodelay 大包的分片,所以一直到dev_hard_start_xmit数才会调用到。

l   dev_hard_start_xmit


点击(此处)折叠或打开

  1. int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
  2.             struct netdev_queue *txq)
  3. {
  4.     const struct net_device_ops *ops = dev->netdev_ops;
  5.     int rc;
  6.  
  7.     if (likely(!skb->next)) {
  8.         if (!list_empty(&ptype_all))
  9.             dev_queue_xmit_nit(skb, dev);
  10.         //判断网卡是否需要协议栈负责gso
  11.         if (netif_needs_gso(dev, skb)) {
  12.             //真正负责GSO操作的函数
  13.             if (unlikely(dev_gso_segment(skb)))
  14.                 goto out_kfree_skb;
  15.             if (skb->next)
  16.                 goto gso;
  17.         }
  18. //……
  19. gso:
  20.     do {
  21.         //指向GSO分片后的一个skb
  22.         struct sk_buff *nskb = skb->next;
  23.         skb->next = nskb->next;
  24.         nskb->next = NULL;
  25.         if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
  26.             skb_dst_drop(nskb);
  27.         //将通过GSO分片后的包逐个发出
  28.         rc = ops->ndo_start_xmit(nskb, dev);
  29.         if (unlikely(rc != NETDEV_TX_OK)) {
  30.             nskb->next = skb->next;
  31.             skb->next = nskb;
  32.             return rc;
  33.         }
  34.         txq_trans_update(txq);
  35.         if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
  36.             return NETDEV_TX_BUSY;
  37.     } while (skb->next);
  38.  
  39.     skb->destructor = DEV_GSO_CB(skb)->destructor;
  40.  
  41. out_kfree_skb:
  42.     kfree_skb(skb);
  43.     return NETDEV_TX_OK;
  44. }


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


点击(此处)折叠或打开

  1. static inline int netif_needs_gso(struct net_device *dev, struct sk_buff *skb)
  2. {
  3.     return skb_is_gso(skb) &&
  4.            (!skb_gso_ok(skb, dev->features) ||
  5.         unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
  6. }


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


点击(此处)折叠或打开

  1. static inline int skb_is_gso(const struct sk_buff *skb)
  2. {
  3.     return skb_shinfo(skb)->gso_size;
  4. }


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


点击(此处)折叠或打开

  1. static inline int skb_gso_ok(struct sk_buff *skb, int features)
  2. {
  3.     return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&
  4.            (!skb_has_frags(skb) || (features & NETIF_F_FRAGLIST));
  5. }


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

点击(此处)折叠或打开

  1. NETIF_F_TSO = SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT
  2. NETIF_F_UFO = SKB_GSO_UDPV4 << NETIF_F_GSO_SHIFT
  3. #define NETIF_F_GSO_SHIFT 16


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


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

l   dev_gso_segment

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


点击(此处)折叠或打开

  1. static int dev_gso_segment(struct sk_buff *skb)
  2. {
  3.     struct net_device *dev = skb->dev;
  4.     struct sk_buff *segs;
  5.     int features = dev->features & ~(illegal_highdma(dev, skb) ?
  6.                      NETIF_F_SG : 0);
  7.  
  8.     segs = skb_gso_segment(skb, features);
  9.  
  10.     /* Verifying header integrity only. */
  11.     if (!segs)
  12.         return 0;
  13.  
  14.     if (IS_ERR(segs))
  15.         return PTR_ERR(segs);
  16.  
  17.     skb->next = segs;
  18.     DEV_GSO_CB(skb)->destructor = skb->destructor;
  19.     skb->destructor = dev_gso_skb_destructor;
  20.  
  21.     return 0;
  22. }


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

l   skb_gso_segment

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


点击(此处)折叠或打开

  1. struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
  2. {
  3.     struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
  4.     struct packet_type *ptype;
  5.     __be16 type = skb->protocol;
  6.     int err;
  7.  
  8.     skb_reset_mac_header(skb);
  9.     skb->mac_len = skb->network_header - skb->mac_header;
  10.     __skb_pull(skb, skb->mac_len);
  11.     //如果skb->ip_summed 不是 CHECKSUM_PARTIAL,那么报个warning,因为GSO类型的skb其ip_summed一般都是CHECKSUM_PARTIAL
  12.     if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
  13.         struct net_device *dev = skb->dev;
  14.         struct ethtool_drvinfo info = {};
  15.         WARN(……);
  16.         if (skb_header_cloned(skb) &&
  17.             (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
  18.             return ERR_PTR(err);
  19. }
  20.     rcu_read_lock();
  21.     list_for_each_entry_rcu(ptype&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
  22.         if (ptype->type == type && !ptype->dev && ptype->gso_segment) {
  23.             if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
  24.                 // 如果ip_summed != CHECKSUM_PARTIAL,则调用上层协议的gso_send_check
  25.                 err = ptype->gso_send_check(skb);
  26.                 segs = ERR_PTR(err);
  27.                 if (err || skb_gso_ok(skb, features))
  28.                     break;
  29.                 __skb_push(skb, (skb->data skb_network_header(skb)));
  30. }//把skb->data指向network header,调用上层协议的gso_segment完成分片
  31.             segs = ptype->gso_segment(skb, features);
  32.             break;
  33.         }
  34.     }
  35.     rcu_read_unlock();
  36.     //把skb->data再次指向mac header
  37.     __skb_push(skb, skb->data - skb_mac_header(skb));
  38.  
  39.     return segs;
  40. }


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

l   inet_gso_segment

   ./net/ipv4/af_inet.c

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


点击(此处)折叠或打开

  1. static struct sk_buff *inet_gso_segment(struct sk_buff *skb, int features)
  2. {
  3.     struct sk_buff *segs = ERR_PTR(-EINVAL);
  4.     struct iphdr *iph;
  5.     const struct net_protocol *ops;
  6.     int proto;
  7.     int ihl;
  8.     int id;
  9.     unsigned int offset = 0;
  10.  
  11.     if (!(features & NETIF_F_V4_CSUM))
  12.         features &= ~NETIF_F_SG;
  13.    //校验待软GSO分段的的skb,其gso_tpye是否存在其他非法值
  14.     if (unlikely(skb_shinfo(skb)->gso_type &
  15.              ~(SKB_GSO_TCPV4 |
  16.                SKB_GSO_UDP |
  17.                SKB_GSO_DODGY |
  18.                SKB_GSO_TCP_ECN |
  19.                0)))
  20.         goto out;
  21.     //分段数据至少大于IP首部长度
  22.     if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
  23.         goto out;
  24.     //检验首部中的长度字段是否有效
  25.     iph = ip_hdr(skb);
  26.     ihl = iph->ihl * 4;
  27.     if (ihl < sizeof(*iph))
  28.         goto out;
  29.     //再次通过首部中的长度字段检测skb长度是否有效
  30.     if (unlikely(!pskb_may_pull(skb, ihl)))
  31.         goto out;
  32.     //注意:这里已经将data偏移到了传送层头部了,去掉了IP头
  33.     __skb_pull(skb, ihl);
  34.     skb_reset_transport_header(skb);//设置传输层头部位置
  35.     iph = ip_hdr(skb);
  36.     id = ntohs(iph->id);//取出首部中的id字段
  37.     proto = iph->protocol & (MAX_INET_PROTOS - 1);//取出IP首部的协议值,用于定位与之对应的传输层接口(tcp还是udp)
  38.     segs = ERR_PTR(-EPROTONOSUPPORT);
  39.  
  40.     rcu_read_lock();
  41.     ops = rcu_dereference(inet_protos[proto]);//根据协议字段取得上层的协议接口
  42.     if (likely(ops && ops->gso_segment))
  43.         segs = ops->gso_segment(skb, features);//调用上册协议的GSO处理函数
  44.     rcu_read_unlock();
  45.  
  46.     if (!segs || IS_ERR(segs))
  47.         goto out;
  48.     //开始处理分段后的skb
  49.     skb = segs;
  50.     do {
  51.         iph = ip_hdr(skb);
  52.         if (proto == IPPROTO_UDP) {//对于UDP进行的IP分片的头部处理逻辑
  53.             iph->id = htons(id);//所有UDP的IP分片id都相同
  54.             iph->frag_off = htons(offset >> 3);//ip头部偏移字段单位为8字节
  55.             if (skb->next != NULL)
  56.                 iph->frag_off |= htons(IP_MF);//设置分片标识
  57.             offset += (skb->len - skb->mac_len - iph->ihl * 4);
  58.         } else
  59.         iph->id = htons(id++);//对于TCP报,分片后IP头部中id加1
  60.         iph->tot_len = htons(skb->len - skb->mac_len);
  61.         iph->check = 0;
  62.         //计算校验和,只是IP头部的
  63.         iph->check = ip_fast_csum(skb_network_header(skb), iph->ihl);
  64.     } while ((skb = skb->next));
  65.  
  66. out:
  67.     return segs;
  68. }


这里有个问题,UDP经过GSO分片后每个分片的IP头部id是一样的,这个符合IP分片的逻辑,但是为什么TCPGSO分片,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分段)


下面分别看对于TCPUDP调用不通的GSO处理函数。对于TCPGSO处理函数为tcp_tso_segment

l   tcp_tso_segment

./net/ipv4/tcp.c


点击(此处)折叠或打开

  1. struct sk_buff *tcp_tso_segment(struct sk_buff *skb, int features)
  2. {
  3.     struct sk_buff *segs = ERR_PTR(-EINVAL);
  4.     struct tcphdr *th;
  5.     unsigned thlen;
  6.     unsigned int seq;
  7.     __be32 delta;
  8.     unsigned int oldlen;
  9.     unsigned int mss;
  10.     //检测报文长度至少由tcp头部长度
  11.     if (!pskb_may_pull(skb, sizeof(*th)))
  12.         goto out;
  13.  
  14.     th = tcp_hdr(skb);
  15.     thlen = th->doff * 4;//TCP头部的长度字段单位为4字节
  16.     if (thlen < sizeof(*th))
  17.         goto out;
  18.     //再次通过首部中的长度字段检测skb长度是否有效
  19.     if (!pskb_may_pull(skb, thlen))
  20.         goto out;
  21.     //把tcp header移到skb header里,把skb->len存到oldlen中,此时skb->len就只有ip payload的长度(包含TCP首部)
  22.     oldlen = (u16)~skb->len;
  23.     __skb_pull(skb, thlen); //data指向tcp payload
  24.     //这里可以看出gso_size的含义就是mss
  25.     mss = skb_shinfo(skb)->gso_size;
  26.     if (unlikely(skb->len <= mss))//如果skb长度小于mss就不需要GSO分片处理了
  27.         goto out;
  28.     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
  29.         /* Packet is from an untrusted source, reset gso_segs. */
  30.         int type = skb_shinfo(skb)->gso_type;
  31.         //校验待软GSO分段的的skb,其gso_tpye是否存在其他非法值
  32.         if (unlikely(type &
  33.                  ~(SKB_GSO_TCPV4 |
  34.                    SKB_GSO_DODGY |
  35.                    SKB_GSO_TCP_ECN |
  36.                    SKB_GSO_TCPV6 |
  37.                    0) ||
  38.                  !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
  39.             goto out;
  40.         //计算出skb按照mss的长度需要分多少片,赋值给gso_segs
  41.         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
  42.  
  43.         segs = NULL;
  44.         goto out;
  45.     }
  46.     //skb_segment是真正的分段实现,后面再分析
  47.     segs = skb_segment(skb, features);
  48.     if (IS_ERR(segs))
  49.         goto out;
  50.  
  51.     delta = htonl(oldlen + (thlen + mss));
  52.  
  53.     skb = segs;
  54.     th = tcp_hdr(skb);
  55.     seq = ntohl(th->seq);
  56.     //下面是设置每个分片的tcp头部信息
  57.     do {
  58.         th->fin = th->psh = 0;
  59.         //计算每个分片的校验和
  60.         th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
  61.                        (__force u32)delta));
  62.         if (skb->ip_summed != CHECKSUM_PARTIAL)
  63.             th->check =csum_fold(csum_partial(skb_transport_header(skb),
  64.                             thlen, skb->csum));
  65.         //重新初始化每个分片的序列号
  66.         seq += mss;
  67.         skb = skb->next;
  68.         th = tcp_hdr(skb);
  69.  
  70.         th->seq = htonl(seq);
  71.         th->cwr = 0;
  72.     } while (skb->next);
  73.  
  74.     delta = htonl(oldlen + (skb->tail - skb->transport_header) +
  75.               skb->data_len);
  76.     th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
  77.                 (__force u32)delta));
  78.     if (skb->ip_summed != CHECKSUM_PARTIAL)
  79.         th->check = csum_fold(csum_partial(skb_transport_header(skb),
  80.                            thlen, skb->csum));
  81.  
  82. out:
  83.     return segs;
  84. }


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

l   udp4_ufo_fragment

./net/ipv4/udp.c


点击(此处)折叠或打开

  1. struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb, int features)
  2. {
  3.     struct sk_buff *segs = ERR_PTR(-EINVAL);
  4.     unsigned int mss;
  5.     int offset;
  6.     __wsum csum;
  7.  
  8.     mss = skb_shinfo(skb)->gso_size;
  9.     if (unlikely(skb->len <= mss))
  10.         goto out;
  11.  
  12.     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
  13.         /* Packet is from an untrusted source, reset gso_segs. */
  14.         int type = skb_shinfo(skb)->gso_type;
  15.  
  16.         if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
  17.                  !(type & (SKB_GSO_UDP))))
  18.             goto out;
  19.  
  20.         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
  21.  
  22.         segs = NULL;
  23.         goto out;
  24.     }
  25.  
  26.     /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
  27.      * do checksum of UDP packets sent as multiple IP fragments.
  28.      */
  29.     //计算udp的checksum
  30.     offset = skb->csum_start - skb_headroom(skb);
  31.     csum = skb_checksum(skb, offset, skb->len - offset, 0);
  32.     offset += skb->csum_offset;
  33.     *(__sum16 *)(skb->data + offset) = csum_fold(csum);
  34.     skb->ip_summed = CHECKSUM_NONE;
  35.     //这里传递给skb_segment做分片时是没有将UDP首部去除的
  36.     segs = skb_segment(skb, features);
  37. out:
  38.     return segs;
  39. }


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

l   skb_segment

/net/core/skbuff.c


点击(此处)折叠或打开

  1. struct sk_buff *skb_segment(struct sk_buff *skb, int features)
  2. {
  3.     struct sk_buff *segs = NULL;
  4.     struct sk_buff *tail = NULL;
  5.     struct sk_buff *fskb = skb_shinfo(skb)->frag_list;
  6.     unsigned int mss = skb_shinfo(skb)->gso_size;
  7.     unsigned int doffset = skb->data - skb_mac_header(skb);//mac头+ip头+tcp头 或mac头+ip头(对于UDP传入时没有将头部偏移过去)
  8.     unsigned int offset = doffset;
  9.     unsigned int headroom;
  10.     unsigned int len;
  11.     int sg = features & NETIF_F_SG;
  12.     int nfrags = skb_shinfo(skb)->nr_frags;
  13.     int err = -ENOMEM;
  14.     int i = 0;
  15.     int pos;
  16.  
  17.     __skb_push(skb, doffset);
  18.     headroom = skb_headroom(skb);
  19.     pos = skb_headlen(skb);//pos初始化为线性区长度
  20.  
  21.     do {
  22.         struct sk_buff *nskb;
  23.         skb_frag_t *frag;
  24.         int hsize;
  25.         int size;
  26.         // offset为分片已处理的长度,len为skb->len减去直到offset的部分。开始时,offset只是mac header + ip header + tcp header的长度,len即tcp payload的长度。随着segment增加, offset每次都增加mss长度。因此len的定义是每个segment的payload长度(最后一个segment的payload可能小于一个mss长度)
  27.         len = skb->len - offset;
  28.         if (len > mss)//len为本次要创建的新分片的长度
  29.             len = mss;
  30.         // hsize为线性区部分的payload减去offset后的大小,如果hsize小于0,那么说明payload在skb的frags或frag_list中。随着offset一直增长,必定会有hsize一直<0的情况开始出现,除非skb是一个完全linearize化的skb
  31.         hsize = skb_headlen(skb) - offset;
  32.         //这种情况说明线性区已经没有tcp payload的部分,需要pull数据过来
  33.         if (hsize < 0)
  34. hsize = 0;
  35.        //如果不支持NETIF_F_SG或者hsize大于len,那么hsize就为len(本次新分片的长度),此时说明segment的payload还在skb 线性区中
  36.         if (hsize > len || !sg)
  37.             hsize = len;
  38.  
  39.         if (!hsize && i >= nfrags) {// hsize为0,表示需要从frags数组或者frag_list链表中拷贝出数据,i >= nfrags说明frags数组中的数据也拷贝完了,下面需要从frag_list链表中拷贝数据了
  40.             BUG_ON(fskb->len != len);
  41.  
  42.             pos += len;
  43.             //frag_list的数据不用真的拷贝,只需要拷贝其skb描述符,就可以复用其数据区
  44.             nskb = skb_clone(fskb, GFP_ATOMIC);//拷贝frag_list中的skb的描述符
  45.             fskb = fskb->next;//指向frag_list的下一个skb元素
  46.  
  47.             if (unlikely(!nskb))
  48.                 goto err;
  49.  
  50.             hsize = skb_end_pointer(nskb) - nskb->head;
  51.             //保证新的skb的headroom有mac header+ip header+tcp/udp+header的大小
  52.             if (skb_cow_head(nskb, doffset + headroom)) {
  53.                 kfree_skb(nskb);
  54.                 goto err;
  55.             }
  56.             //调整truesize,使其包含本次已分片的数据部分长度(hsize)
  57.             nskb->truesize += skb_end_pointer(nskb) - nskb->head hsize;
  58.             skb_release_head_state(nskb);
  59.             __skb_push(nskb, doffset);
  60.         } else {//数据从线性区或者frags数组中取得
  61.             //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区
  62.             nskb = alloc_skb(hsize + doffset + headroom,GFP_ATOMIC);
  63.  
  64.             if (unlikely(!nskb))
  65.                 goto err;
  66.             skb_reserve(nskb, headroom);
  67.             __skb_put(nskb, doffset);
  68.         }
  69.  
  70.         if (segs)
  71.             tail->next = nskb;
  72.         else
  73.             segs = nskb;
  74.         tail = nskb;
  75.         //拷贝skb结构中的成员
  76.         __copy_skb_header(nskb, skb);
  77.         nskb->mac_len = skb->mac_len;
  78.  
  79.         /* nskb and skb might have different headroom */
  80.         if (nskb->ip_summed == CHECKSUM_PARTIAL)
  81.             nskb->csum_start += skb_headroom(nskb) - headroom;
  82.  
  83.         skb_reset_mac_header(nskb);
  84.         skb_set_network_header(nskb, skb->mac_len);
  85.         nskb->transport_header = (nskb->network_header +
  86.         skb_network_header_len(skb));
  87.         //把skb->data开始doffset长度的内容拷贝到nskb->data中
  88.         skb_copy_from_linear_data(skb, nskb->data, doffset);
  89.         // fskb被初始化为skb_shinfo(skb)->frag_list,现在如果不再相等,说明已经开始拷贝frag_list链表中的数据,不用继续后面的逻辑了(后面的逻辑是从线性区或者frags数组中拷贝的逻辑)
  90.         if (fskb != skb_shinfo(skb)->frag_list)
  91.             continue;
  92.         //如果不支持NETIF_F_SG,说明frags数组中没有数据,只考虑从线性区中拷贝数据
  93.         if (!sg) {
  94.             nskb->ip_summed = CHECKSUM_NONE;
  95.             //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区
  96.             nskb->csum = skb_copy_and_csum_bits(skb, offset, skb_put(nskb, len), len, 0);
  97.             continue;
  98.         }
  99.  
  100.         frag = skb_shinfo(nskb)->frags;
  101.         //如果hsize不为0,那么拷贝hsize的内容到nskb的线性区中
  102.         skb_copy_from_linear_data_offset(skb, offset,skb_put(nskb, hsize), hsize);
  103.         //注意:每次要拷贝的数据长度是len,其中hsize是位于线性区中,但是随着线性区数据逐渐被处理,hsize可能不够len,这时剩下的(len-hsize)长度就要从frags数组中拷贝了
  104.        while (pos < offset + len && i < nfrags) { //从frags数组中拷贝数据
  105.             *frag = skb_shinfo(skb)->frags[i];
  106.             get_page(frag->page);
  107.             size = frag->size;
  108.             //pos初始为线性区长度,后来表示已经被拷贝的长度
  109.             if (pos < offset) {
  110.                 frag->page_offset += offset - pos;
  111.                 frag->size -= offset - pos;
  112.             }
  113.             //frags数组中的数据并不是真的拷贝,而是nskb的frags数组直接指向相应的page
  114.             skb_shinfo(nskb)->nr_frags++;
  115.  
  116.             if (pos + size <= offset + len) {
  117.                 i++;
  118.                 pos += size;
  119.             } else {
  120.                 frag->size -= pos + size - (offset + len);
  121.                 goto skip_fraglist;
  122.             }
  123.             frag++;
  124.         }
  125.         //如果把frags数组中的数据拷贝完还不够len长度,则需要从frag_list中拷贝了
  126.         if (pos < offset + len) {
  127.             struct sk_buff *fskb2 = fskb;//指向frag_list
  128.  
  129.             BUG_ON(pos + fskb->len != offset + len);
  130.  
  131.             pos += fskb->len;
  132.             fskb = fskb->next;
  133.  
  134.             if (fskb2->next) {
  135.                 fskb2 = skb_clone(fskb2, GFP_ATOMIC);
  136.                 if (!fskb2)
  137.                     goto err;
  138.             } else
  139.                 skb_get(fskb2);
  140.  
  141.             SKB_FRAG_ASSERT(nskb);
  142.             //这里也不是真的拷贝数据,而是nskb的frag_list直接链上老的frag_list中的元素
  143.             skb_shinfo(nskb)->frag_list = fskb2;
  144.         }
  145. skip_fraglist:
  146.         nskb->data_len = len - hsize;
  147.         nskb->len += nskb->data_len;
  148.         nskb->truesize += nskb->data_len;
  149.     } while ((offset += len) < skb->len);//完成一个nskb之后,继续下一个seg,一直到offset >= skb->len
  150.     return segs;
  151.  
  152. err:
  153.     while ((skb = segs)) {
  154.         segs = skb->next;
  155.         kfree_skb(skb);
  156.     }
  157.     return ERR_PTR(err);
  158. }


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

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


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


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


  
阅读(10306) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~