Linux GSO逻辑分析

——lvyilong316

(注: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

 1 int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
 2 
 3             struct netdev_queue *txq)
 4 
 5 {
 6 
 7     const struct net_device_ops *ops = dev->netdev_ops;
 8 
 9     int rc;
10 
11  
12 
13     if (likely(!skb->next)) {
14 
15         if (!list_empty(&ptype_all))
16 
17             dev_queue_xmit_nit(skb, dev);
18 
19         //判断网卡是否需要协议栈负责gso
20 
21         if (netif_needs_gso(dev, skb)) {
22 
23 //真正负责GSO操作的函数
24 
25             if (unlikely(dev_gso_segment(skb)))
26 
27                 goto out_kfree_skb;
28 
29             if (skb->next)
30 
31                 goto gso;
32 
33         }
34 
35 //……
36 
37 gso:
38 
39     do {
40 
41 //指向GSO分片后的一个skb
42 
43         struct sk_buff *nskb = skb->next;
44 
45         skb->next = nskb->next;
46 
47         nskb->next = NULL;
48 
49         if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
50 
51             skb_dst_drop(nskb);
52 
53 //将通过GSO分片后的包逐个发出
54 
55         rc = ops->ndo_start_xmit(nskb, dev);
56 
57         if (unlikely(rc != NETDEV_TX_OK)) {
58 
59             nskb->next = skb->next;
60 
61             skb->next = nskb;
62 
63             return rc;
64 
65         }
66 
67         txq_trans_update(txq);
68 
69         if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
70 
71             return NETDEV_TX_BUSY;
72 
73     } while (skb->next);
74 
75  
76 
77     skb->destructor = DEV_GSO_CB(skb)->destructor;
78 
79  
80 
81 out_kfree_skb:
82 
83     kfree_skb(skb);
84 
85     return NETDEV_TX_OK;
86 
87 }

   
那是否负有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 {
 4 
 5     return skb_is_gso(skb) &&
 6 
 7            (!skb_gso_ok(skb, dev->features) ||
 8 
 9         unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
10 
11 }

只顾那里最后用了2个unlikely,因为借使由此前边的论断,表达网卡是援助GSO的,而相似网卡资助GSO也就会支持CHECKSUM_PA奇骏TIAL。进入GSO处理的率先个前提是skb_is_gso函数重返真,看下skb_is_gso的逻辑:

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

skb_is_gso的逻辑很粗略,重回skb_shinfo(skb)->gso_size,所以进入GSO处理逻辑的要求条件之一是skb_GSO逻辑分析。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 {
4 
5     return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&
6 
7            (!skb_has_frags(skb) || (features & NETIF_F_FRAGLIST));
8 
9 }

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 
3 NETIF_F_UFO = SKB_GSO_UDPV4 << NETIF_F_GSO_SHIFT
4 
5 #define NETIF_F_GSO_SHIFT 16

经过上述多少个函数分析,以下七个情景须要商谈栈负责GSO。

澳门金沙国际 1

    上边看GSO的情商栈处理逻辑,入口就是dev_gso_segment。

l   dev_gso_segment

   
协议栈的GSO逻辑是在dev_gso_segment中举办的。那些函数主要完了对skb的分片,并将分片存放在原始skb的skb->next中,那也是GSO的重中之重办事

 1 static int dev_gso_segment(struct sk_buff *skb)
 2 
 3 {
 4 
 5     struct net_device *dev = skb->dev;
 6 
 7     struct sk_buff *segs;
 8 
 9     int features = dev->features & ~(illegal_highdma(dev, skb) ?
10 
11                      NETIF_F_SG : 0);
12 
13  
14 
15     segs = skb_gso_segment(skb, features);
16 
17  
18 
19     /* Verifying header integrity only. */
20 
21     if (!segs)
22 
23         return 0;
24 
25  
26 
27     if (IS_ERR(segs))
28 
29         return PTR_ERR(segs);
30 
31  
32 
33     skb->next = segs;
34 
35     DEV_GSO_CB(skb)->destructor = skb->destructor;
36 
37     skb->destructor = dev_gso_skb_destructor;
38 
39  
40 
41     return 0;
42 
43 }

   
主要分片逻辑由skb_gso_segment来拍卖,那里我们根本看下析构进度,此时skb经过分片之后已经是一个skb
list,通过skb->next串在协同,此时把初阶的skb->destructor函数存到skb->cb中,然后把skb->destructor变更为dev_gso_skb_destructor。dev_gso_skb_destructor会把skb->next1个个通过kfree_skb释放掉,最终调用DEV_GSO_CB(skb)->destructor,即skb开始的析构函数做最后的清理。

l   skb_gso_segment

本条函数将skb分片,并重返1个skb list。如若skb不必要分片则赶回NULL。

 1 struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
 2 
 3 {
 4 
 5     struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
 6 
 7     struct packet_type *ptype;
 8 
 9     __be16 type = skb->protocol;
10 
11     int err;
12 
13  
14 
15     skb_reset_mac_header(skb);
16 
17     skb->mac_len = skb->network_header - skb->mac_header;
18 
19     __skb_pull(skb, skb->mac_len);
20 
21     //如果skb->ip_summed 不是 CHECKSUM_PARTIAL,那么报个warning,因为GSO类型的skb其ip_summed一般都是CHECKSUM_PARTIAL
22 
23     if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
24 
25         struct net_device *dev = skb->dev;
26 
27         struct ethtool_drvinfo info = {};
28 
29         WARN(……);
30 
31         if (skb_header_cloned(skb) &&
32 
33             (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
34 
35             return ERR_PTR(err);
36 
37     }
38 
39     rcu_read_lock();
40 
41     list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
42 
43         if (ptype->type == type && !ptype->dev && ptype->gso_segment) {
44 
45             if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
46 
47                 // 如果ip_summed != CHECKSUM_PARTIAL,则调用上层协议的gso_send_check
48 
49                 err = ptype->gso_send_check(skb);
50 
51                 segs = ERR_PTR(err);
52 
53                 if (err || skb_gso_ok(skb, features))
54 
55                     break;
56 
57                 __skb_push(skb, (skb->data -
58 
59                 skb_network_header(skb)));
60 
61         }
62 
63            //把skb->data指向network header,调用上层协议的gso_segment完成分片
64 
65             segs = ptype->gso_segment(skb, features);
66 
67             break;
68 
69         }
70 
71     }
72 
73     rcu_read_unlock();
74 
75 //把skb->data再次指向mac header
76 
77     __skb_push(skb, skb->data - skb_mac_header(skb));
78 
79  
80 
81     return segs;
82 
83 }

 

 
最后追调用上层协议的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逻辑

小结:对于GSO

   
UDP:全体分片ip尾部id都平等,设置IP_MF分片标志(除最终一片)

(等同于IP分片)

    TCP:分片后,逐个分片IP尾部中id加1,
(等同于TCP分段)

 

下边分别看对于TCP和UDP调用不通的GSO处理函数。对于TCP其GSO处理函数为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 {
  4 
  5     struct sk_buff *segs = ERR_PTR(-EINVAL);
  6 
  7     struct tcphdr *th;
  8 
  9     unsigned thlen;
 10 
 11     unsigned int seq;
 12 
 13     __be32 delta;
 14 
 15     unsigned int oldlen;
 16 
 17     unsigned int mss;
 18 
 19     //检测报文长度至少由tcp头部长度
 20 
 21     if (!pskb_may_pull(skb, sizeof(*th)))
 22 
 23         goto out;
 24 
 25  
 26 
 27     th = tcp_hdr(skb);
 28 
 29     thlen = th->doff * 4;//TCP头部的长度字段单位为4字节
 30 
 31     if (thlen < sizeof(*th))
 32 
 33         goto out;
 34 
 35    //再次通过首部中的长度字段检测skb长度是否有效
 36 
 37     if (!pskb_may_pull(skb, thlen))
 38 
 39         goto out;
 40 
 41     //把tcp header移到skb header里,把skb->len存到oldlen中,此时skb->len就只有ip payload的长度(包含TCP首部)
 42 
 43     oldlen = (u16)~skb->len;
 44 
 45     __skb_pull(skb, thlen); //data指向tcp payload
 46 
 47     //这里可以看出gso_size的含义就是mss
 48 
 49     mss = skb_shinfo(skb)->gso_size;
 50 
 51     if (unlikely(skb->len <= mss))//如果skb长度小于mss就不需要GSO分片处理了
 52 
 53         goto out;
 54 
 55     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
 56 
 57         /* Packet is from an untrusted source, reset gso_segs. */
 58 
 59         int type = skb_shinfo(skb)->gso_type;
 60 
 61         //校验待软GSO分段的的skb,其gso_tpye是否存在其他非法值
 62 
 63         if (unlikely(type &
 64 
 65                  ~(SKB_GSO_TCPV4 |
 66 
 67                    SKB_GSO_DODGY |
 68 
 69                    SKB_GSO_TCP_ECN |
 70 
 71                    SKB_GSO_TCPV6 |
 72 
 73                    0) ||
 74 
 75                  !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
 76 
 77             goto out;
 78 
 79         //计算出skb按照mss的长度需要分多少片,赋值给gso_segs
 80 
 81         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
 82 
 83  
 84 
 85         segs = NULL;
 86 
 87         goto out;
 88 
 89     }
 90 
 91     //skb_segment是真正的分段实现,后面再分析
 92 
 93     segs = skb_segment(skb, features);
 94 
 95     if (IS_ERR(segs))
 96 
 97         goto out;
 98 
 99  
100 
101     delta = htonl(oldlen + (thlen + mss));
102 
103  
104 
105     skb = segs;
106 
107     th = tcp_hdr(skb);
108 
109     seq = ntohl(th->seq);
110 
111     //下面是设置每个分片的tcp头部信息
112 
113     do {
114 
115         th->fin = th->psh = 0;
116 
117         //计算每个分片的校验和
118 
119         th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
120 
121                        (__force u32)delta));
122 
123         if (skb->ip_summed != CHECKSUM_PARTIAL)
124 
125             th->check =csum_fold(csum_partial(skb_transport_header(skb),
126 
127                             thlen, skb->csum));
128 
129         //重新初始化每个分片的序列号
130 
131         seq += mss;
132 
133         skb = skb->next;
134 
135         th = tcp_hdr(skb);
136 
137  
138 
139         th->seq = htonl(seq);
140 
141         th->cwr = 0;
142 
143     } while (skb->next);
144 
145  
146 
147     delta = htonl(oldlen + (skb->tail - skb->transport_header) +
148 
149               skb->data_len);
150 
151     th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
152 
153                 (__force u32)delta));
154 
155     if (skb->ip_summed != CHECKSUM_PARTIAL)
156 
157         th->check = csum_fold(csum_partial(skb_transport_header(skb),
158 
159                            thlen, skb->csum));
160 
161  
162 
163 out:
164 
165     return segs;
166 
167 }

 

   
从下边可以看看,各个TCP的GSO分片是带有了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 {
 4 
 5     struct sk_buff *segs = ERR_PTR(-EINVAL);
 6 
 7     unsigned int mss;
 8 
 9     int offset;
10 
11     __wsum csum;
12 
13  
14 
15     mss = skb_shinfo(skb)->gso_size;
16 
17     if (unlikely(skb->len <= mss))
18 
19         goto out;
20 
21  
22 
23     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
24 
25         /* Packet is from an untrusted source, reset gso_segs. */
26 
27         int type = skb_shinfo(skb)->gso_type;
28 
29  
30 
31         if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
32 
33                  !(type & (SKB_GSO_UDP))))
34 
35             goto out;
36 
37  
38 
39         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
40 
41  
42 
43         segs = NULL;
44 
45         goto out;
46 
47     }
48 
49  
50 
51     /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
52 
53      * do checksum of UDP packets sent as multiple IP fragments.
54 
55 */
56 
57     //计算udp的checksum
58 
59     offset = skb->csum_start - skb_headroom(skb);
60 
61     csum = skb_checksum(skb, offset, skb->len - offset, 0);
62 
63     offset += skb->csum_offset;
64 
65     *(__sum16 *)(skb->data + offset) = csum_fold(csum);
66 
67     skb->ip_summed = CHECKSUM_NONE;
68 
69     //这里传递给skb_segment做分片时是没有将UDP首部去除的
70 
71     segs = skb_segment(skb, features);
72 
73 out:
74 
75     return segs;
76 
77 }

 

在意那里传递给skb_segment
做分片是含有udp首部的,分片将udp首部作为一般数据切分,那也意味着对于udp的GSO分片,唯有首先片有UDP首部。udp的分支其实和ip的分片没什么不同,只是多1个乘除checksum的手续,上面看形元素片的主要函数skb_segment。

l   skb_segment

/net/core/skbuff.c

  1 struct sk_buff *skb_segment(struct sk_buff *skb, int features)
  2 
  3 {
  4 
  5     struct sk_buff *segs = NULL;
  6 
  7     struct sk_buff *tail = NULL;
  8 
  9     struct sk_buff *fskb = skb_shinfo(skb)->frag_list;
 10 
 11     unsigned int mss = skb_shinfo(skb)->gso_size;
 12 
 13     unsigned int doffset = skb->data - skb_mac_header(skb);//mac头+ip头+tcp头 或mac头+ip头(对于UDP传入时没有将头部偏移过去)
 14 
 15     unsigned int offset = doffset;
 16 
 17     unsigned int headroom;
 18 
 19     unsigned int len;
 20 
 21     int sg = features & NETIF_F_SG;
 22 
 23     int nfrags = skb_shinfo(skb)->nr_frags;
 24 
 25     int err = -ENOMEM;
 26 
 27     int i = 0;
 28 
 29     int pos;
 30 
 31  
 32 
 33     __skb_push(skb, doffset);
 34 
 35     headroom = skb_headroom(skb);
 36 
 37     pos = skb_headlen(skb);//pos初始化为线性区长度
 38 
 39  
 40 
 41     do {
 42 
 43         struct sk_buff *nskb;
 44 
 45         skb_frag_t *frag;
 46 
 47         int hsize;
 48 
 49         int size;
 50 
 51         // offset为分片已处理的长度,len为skb->len减去直到offset的部分。开始时,offset只是mac header + ip header + tcp header的长度,len即tcp payload的长度。随着segment增加, offset每次都增加mss长度。因此len的定义是每个segment的payload长度(最后一个segment的payload可能小于一个mss长度)
 52 
 53         len = skb->len - offset;
 54 
 55         if (len > mss)//len为本次要创建的新分片的长度
 56 
 57             len = mss;
 58 
 59        // hsize为线性区部分的payload减去offset后的大小,如果hsize小于0,那么说明payload在skb的frags或frag_list中。随着offset一直增长,必定会有hsize一直<0的情况开始出现,除非skb是一个完全linearize化的skb
 60 
 61         hsize = skb_headlen(skb) - offset;
 62 
 63         //这种情况说明线性区已经没有tcp payload的部分,需要pull数据过来
 64  
 65         if (hsize < 0)
 66 
 67 hsize = 0;
 68 
 69        //如果不支持NETIF_F_SG或者hsize大于len,那么hsize就为len(本次新分片的长度),此时说明segment的payload还在skb 线性区中
 70 
 71         if (hsize > len || !sg)
 72 
 73             hsize = len;
 74 
 75  
 76 
 77         if (!hsize && i >= nfrags) {// hsize为0,表示需要从frags数组或者frag_list链表中拷贝出数据,i >= nfrags说明frags数组中的数据也拷贝完了,下面需要从frag_list链表中拷贝数据了
 78 
 79             BUG_ON(fskb->len != len);
 80 
 81  
 82 
 83 pos += len;
 84 
 85             //frag_list的数据不用真的拷贝,只需要拷贝其skb描述符,就可以复用其数据区
 86 
 87             nskb = skb_clone(fskb, GFP_ATOMIC);//拷贝frag_list中的skb的描述符
 88 
 89             fskb = fskb->next;//指向frag_list的下一个skb元素
 90 
 91  
 92 
 93             if (unlikely(!nskb))
 94 
 95                 goto err;
 96 
 97  
 98 
 99              hsize = skb_end_pointer(nskb) - nskb->head;
100 
101             //保证新的skb的headroom有mac header+ip header+tcp/udp+header的大小
102 
103             if (skb_cow_head(nskb, doffset + headroom)) {
104 
105                 kfree_skb(nskb);
106 
107                 goto err;
108 
109             }
110 
111            //调整truesize,使其包含本次已分片的数据部分长度(hsize)
112 
113             nskb->truesize += skb_end_pointer(nskb) - nskb->head -
114 
115                       hsize;
116 
117             skb_release_head_state(nskb);
118 
119             __skb_push(nskb, doffset);
120 
121         } else {//数据从线性区或者frags数组中取得
122 
123             //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区
124 
125             nskb = alloc_skb(hsize + doffset + headroom,GFP_ATOMIC);
126 
127  
128 
129             if (unlikely(!nskb))
130 
131                 goto err;
132 
133             skb_reserve(nskb, headroom);
134 
135             __skb_put(nskb, doffset);
136 
137         }
138 
139  
140 
141         if (segs)
142 
143             tail->next = nskb;
144 
145         else
146 
147             segs = nskb;
148 
149         tail = nskb;
150 
151         //拷贝skb结构中的成员
152 
153         __copy_skb_header(nskb, skb);
154 
155         nskb->mac_len = skb->mac_len;
156 
157  
158 
159         /* nskb and skb might have different headroom */
160 
161         if (nskb->ip_summed == CHECKSUM_PARTIAL)
162 
163             nskb->csum_start += skb_headroom(nskb) - headroom;
164 
165  
166 
167         skb_reset_mac_header(nskb);
168 
169         skb_set_network_header(nskb, skb->mac_len);
170 
171         nskb->transport_header = (nskb->network_header +
172 
173         skb_network_header_len(skb));
174 
175        //把skb->data开始doffset长度的内容拷贝到nskb->data中
176 
177         skb_copy_from_linear_data(skb, nskb->data, doffset);
178 
179         // fskb被初始化为skb_shinfo(skb)->frag_list,现在如果不再相等,说明已经开始拷贝frag_list链表中的数据,不用继续后面的逻辑了(后面的逻辑是从线性区或者frags数组中拷贝的逻辑)
180 
181         if (fskb != skb_shinfo(skb)->frag_list)
182 
183             continue;
184 
185         //如果不支持NETIF_F_SG,说明frags数组中没有数据,只考虑从线性区中拷贝数据
186 
187         if (!sg) {
188 
189             nskb->ip_summed = CHECKSUM_NONE;
190 
191            //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区
192 
193             nskb->csum = skb_copy_and_csum_bits(skb, offset,
194 
195                                 skb_put(nskb, len), len, 0);
196 
197             continue;
198 
199         }
200 
201  
202 
203         frag = skb_shinfo(nskb)->frags;
204 
205         //如果hsize不为0,那么拷贝hsize的内容到nskb的线性区中
206 
207         skb_copy_from_linear_data_offset(skb, offset,skb_put(nskb, hsize), hsize);
208 
209         //注意:每次要拷贝的数据长度是len,其中hsize是位于线性区中,但是随着线性区数据逐渐被处理,hsize可能不够len,这时剩下的(len-hsize)长度就要从frags数组中拷贝了
210 
211        while (pos < offset + len && i < nfrags) { //从frags数组中拷贝数据
212 
213             *frag = skb_shinfo(skb)->frags[i];
214 
215             get_page(frag->page);
216 
217             size = frag->size;
218 
219            //pos初始为线性区长度,后来表示已经被拷贝的长度
220 
221             if (pos < offset) {
222 
223                 frag->page_offset += offset - pos;
224 
225                 frag->size -= offset - pos;
226 
227             }
228 
229             //frags数组中的数据并不是真的拷贝,而是nskb的frags数组直接指向相应的page
230 
231             skb_shinfo(nskb)->nr_frags++;
232 
233  
234 
235             if (pos + size <= offset + len) {
236 
237                 i++;
238 
239                 pos += size;
240 
241             } else {
242 
243                 frag->size -= pos + size - (offset + len);
244 
245                 goto skip_fraglist;
246 
247             }
248 
249             frag++;
250 
251         }
252 
253          //如果把frags数组中的数据拷贝完还不够len长度,则需要从frag_list中拷贝了
254 
255         if (pos < offset + len) {
256 
257             struct sk_buff *fskb2 = fskb;//指向frag_list
258 
259  
260 
261             BUG_ON(pos + fskb->len != offset + len);
262 
263  
264 
265             pos += fskb->len;
266 
267             fskb = fskb->next;
268 
269  
270 
271             if (fskb2->next) {
272 
273                 fskb2 = skb_clone(fskb2, GFP_ATOMIC);
274 
275                 if (!fskb2)
276 
277                     goto err;
278 
279             } else
280 
281                 skb_get(fskb2);
282 
283  
284 
285 SKB_FRAG_ASSERT(nskb);
286 
287             //这里也不是真的拷贝数据,而是nskb的frag_list直接链上老的frag_list中的元素
288 
289             skb_shinfo(nskb)->frag_list = fskb2;
290 
291         }
292 
293 skip_fraglist:
294 
295         nskb->data_len = len - hsize;
296 
297         nskb->len += nskb->data_len;
298 
299         nskb->truesize += nskb->data_len;
300 
301     } while ((offset += len) < skb->len);//完成一个nskb之后,继续下一个seg,一直到offset >= skb->len
302 
303     return segs;
304 
305  
306 
307 err:
308 
309     while ((skb = segs)) {
310 
311         segs = skb->next;
312 
313         kfree_skb(skb);
314 
315     }
316 
317     return ERR_PTR(err);
318 
319 }

 
  从下面的分片进程可以看出,分成的小skb并不一定都以线性话的,固然从前的skb存在frags数组可能frag_list,则分为的小skb也说不定有指向非线性区域。并不用担心网卡不协助分散聚合IO,因为在此之前假若能发生这么些非线性数据,就印证网卡一定是永葆的。

末尾记忆下一切协议栈的GSO处理逻辑,如下图:

 澳门金沙国际 2

我们再看一下skb社团格局在GSO前后的转变:

澳门金沙国际 3

    GSO之后如下,注意GSO之后也是大概包罗frags的。

澳门金沙国际 4

Linux GSO逻辑分析,linuxgso逻辑

Linux GSO逻辑分析

Linux GSO逻辑分析

Linux GSO逻辑分析

——lvyilong316

(注: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

 1 int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
 2 
 3             struct netdev_queue *txq)
 4 
 5 {
 6 
 7     const struct net_device_ops *ops = dev->netdev_ops;
 8 
 9     int rc;
10 
11  
12 
13     if (likely(!skb->next)) {
14 
15         if (!list_empty(&ptype_all))
16 
17             dev_queue_xmit_nit(skb, dev);
18 
19         //判断网卡是否需要协议栈负责gso
20 
21         if (netif_needs_gso(dev, skb)) {
22 
23 //真正负责GSO操作的函数
24 
25             if (unlikely(dev_gso_segment(skb)))
26 
27                 goto out_kfree_skb;
28 
29             if (skb->next)
30 
31                 goto gso;
32 
33         }
34 
35 //……
36 
37 gso:
38 
39     do {
40 
41 //指向GSO分片后的一个skb
42 
43         struct sk_buff *nskb = skb->next;
44 
45         skb->next = nskb->next;
46 
47         nskb->next = NULL;
48 
49         if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
50 
51             skb_dst_drop(nskb);
52 
53 //将通过GSO分片后的包逐个发出
54 
55         rc = ops->ndo_start_xmit(nskb, dev);
56 
57         if (unlikely(rc != NETDEV_TX_OK)) {
58 
59             nskb->next = skb->next;
60 
61             skb->next = nskb;
62 
63             return rc;
64 
65         }
66 
67         txq_trans_update(txq);
68 
69         if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
70 
71             return NETDEV_TX_BUSY;
72 
73     } while (skb->next);
74 
75  
76 
77     skb->destructor = DEV_GSO_CB(skb)->destructor;
78 
79  
80 
81 out_kfree_skb:
82 
83     kfree_skb(skb);
84 
85     return NETDEV_TX_OK;
86 
87 }

   
那是还是不是兼备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 {
 4 
 5     return skb_is_gso(skb) &&
 6 
 7            (!skb_gso_ok(skb, dev->features) ||
 8 
 9         unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
10 
11 }

小心那里最终用了二个unlikely,因为尽管经过前面的论断,表达网卡是永葆GSO的,而相似网卡帮衬GSO也就会支撑CHECKSUM_PATiggoTIAL。进入GSO处理的首先个前提是skb_is_gso函数再次回到真,看下skb_is_gso的逻辑:

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

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 {
4 
5     return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&
6 
7            (!skb_has_frags(skb) || (features & NETIF_F_FRAGLIST));
8 
9 }

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 
3 NETIF_F_UFO = SKB_GSO_UDPV4 << NETIF_F_GSO_SHIFT
4 
5 #define NETIF_F_GSO_SHIFT 16

经过上述多个函数分析,以下三个状态需求商谈栈负责GSO。

    下边看GSO的说道栈处理逻辑,入口就是dev_gso_segment。

l   dev_gso_segment

   
协议栈的GSO逻辑是在dev_gso_segment中举行的。那个函数首要形成对skb的分片,并将分片存放在原始skb的skb->next中,那也是GSO的机要办事

 1 static int dev_gso_segment(struct sk_buff *skb)
 2 
 3 {
 4 
 5     struct net_device *dev = skb->dev;
 6 
 7     struct sk_buff *segs;
 8 
 9     int features = dev->features & ~(illegal_highdma(dev, skb) ?
10 
11                      NETIF_F_SG : 0);
12 
13  
14 
15     segs = skb_gso_segment(skb, features);
16 
17  
18 
19     /* Verifying header integrity only. */
20 
21     if (!segs)
22 
23         return 0;
24 
25  
26 
27     if (IS_ERR(segs))
28 
29         return PTR_ERR(segs);
30 
31  
32 
33     skb->next = segs;
34 
35     DEV_GSO_CB(skb)->destructor = skb->destructor;
36 
37     skb->destructor = dev_gso_skb_destructor;
38 
39  
40 
41     return 0;
42 
43 }

   
紧要分片逻辑由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。

 1 struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
 2 
 3 {
 4 
 5     struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
 6 
 7     struct packet_type *ptype;
 8 
 9     __be16 type = skb->protocol;
10 
11     int err;
12 
13  
14 
15     skb_reset_mac_header(skb);
16 
17     skb->mac_len = skb->network_header - skb->mac_header;
18 
19     __skb_pull(skb, skb->mac_len);
20 
21     //如果skb->ip_summed 不是 CHECKSUM_PARTIAL,那么报个warning,因为GSO类型的skb其ip_summed一般都是CHECKSUM_PARTIAL
22 
23     if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
24 
25         struct net_device *dev = skb->dev;
26 
27         struct ethtool_drvinfo info = {};
28 
29         WARN(……);
30 
31         if (skb_header_cloned(skb) &&
32 
33             (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
34 
35             return ERR_PTR(err);
36 
37     }
38 
39     rcu_read_lock();
40 
41     list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
42 
43         if (ptype->type == type && !ptype->dev && ptype->gso_segment) {
44 
45             if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
46 
47                 // 如果ip_summed != CHECKSUM_PARTIAL,则调用上层协议的gso_send_check
48 
49                 err = ptype->gso_send_check(skb);
50 
51                 segs = ERR_PTR(err);
52 
53                 if (err || skb_gso_ok(skb, features))
54 
55                     break;
56 
57                 __skb_push(skb, (skb->data -
58 
59                 skb_network_header(skb)));
60 
61         }
62 
63            //把skb->data指向network header,调用上层协议的gso_segment完成分片
64 
65             segs = ptype->gso_segment(skb, features);
66 
67             break;
68 
69         }
70 
71     }
72 
73     rcu_read_unlock();
74 
75 //把skb->data再次指向mac header
76 
77     __skb_push(skb, skb->data - skb_mac_header(skb));
78 
79  
80 
81     return segs;
82 
83 }

 

 
最后追调用上层协议的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逻辑

小结:对于GSO

    UDP:全数分片ip尾部id都相同,设置IP_MF分片标志(除最终一片)
(等同于IP分片)

    TCP:分片后,逐个分片IP尾部中id加1, (等同于TCP分段)

 

上边分别看对于TCP和UDP调用不通的GSO处理函数。对于TCP其GSO处理函数为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 {
  4 
  5     struct sk_buff *segs = ERR_PTR(-EINVAL);
  6 
  7     struct tcphdr *th;
  8 
  9     unsigned thlen;
 10 
 11     unsigned int seq;
 12 
 13     __be32 delta;
 14 
 15     unsigned int oldlen;
 16 
 17     unsigned int mss;
 18 
 19     //检测报文长度至少由tcp头部长度
 20 
 21     if (!pskb_may_pull(skb, sizeof(*th)))
 22 
 23         goto out;
 24 
 25  
 26 
 27     th = tcp_hdr(skb);
 28 
 29     thlen = th->doff * 4;//TCP头部的长度字段单位为4字节
 30 
 31     if (thlen < sizeof(*th))
 32 
 33         goto out;
 34 
 35    //再次通过首部中的长度字段检测skb长度是否有效
 36 
 37     if (!pskb_may_pull(skb, thlen))
 38 
 39         goto out;
 40 
 41     //把tcp header移到skb header里,把skb->len存到oldlen中,此时skb->len就只有ip payload的长度(包含TCP首部)
 42 
 43     oldlen = (u16)~skb->len;
 44 
 45     __skb_pull(skb, thlen); //data指向tcp payload
 46 
 47     //这里可以看出gso_size的含义就是mss
 48 
 49     mss = skb_shinfo(skb)->gso_size;
 50 
 51     if (unlikely(skb->len <= mss))//如果skb长度小于mss就不需要GSO分片处理了
 52 
 53         goto out;
 54 
 55     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
 56 
 57         /* Packet is from an untrusted source, reset gso_segs. */
 58 
 59         int type = skb_shinfo(skb)->gso_type;
 60 
 61         //校验待软GSO分段的的skb,其gso_tpye是否存在其他非法值
 62 
 63         if (unlikely(type &
 64 
 65                  ~(SKB_GSO_TCPV4 |
 66 
 67                    SKB_GSO_DODGY |
 68 
 69                    SKB_GSO_TCP_ECN |
 70 
 71                    SKB_GSO_TCPV6 |
 72 
 73                    0) ||
 74 
 75                  !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
 76 
 77             goto out;
 78 
 79         //计算出skb按照mss的长度需要分多少片,赋值给gso_segs
 80 
 81         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
 82 
 83  
 84 
 85         segs = NULL;
 86 
 87         goto out;
 88 
 89     }
 90 
 91     //skb_segment是真正的分段实现,后面再分析
 92 
 93     segs = skb_segment(skb, features);
 94 
 95     if (IS_ERR(segs))
 96 
 97         goto out;
 98 
 99  
100 
101     delta = htonl(oldlen + (thlen + mss));
102 
103  
104 
105     skb = segs;
106 
107     th = tcp_hdr(skb);
108 
109     seq = ntohl(th->seq);
110 
111     //下面是设置每个分片的tcp头部信息
112 
113     do {
114 
115         th->fin = th->psh = 0;
116 
117         //计算每个分片的校验和
118 
119         th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
120 
121                        (__force u32)delta));
122 
123         if (skb->ip_summed != CHECKSUM_PARTIAL)
124 
125             th->check =csum_fold(csum_partial(skb_transport_header(skb),
126 
127                             thlen, skb->csum));
128 
129         //重新初始化每个分片的序列号
130 
131         seq += mss;
132 
133         skb = skb->next;
134 
135         th = tcp_hdr(skb);
136 
137  
138 
139         th->seq = htonl(seq);
140 
141         th->cwr = 0;
142 
143     } while (skb->next);
144 
145  
146 
147     delta = htonl(oldlen + (skb->tail - skb->transport_header) +
148 
149               skb->data_len);
150 
151     th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
152 
153                 (__force u32)delta));
154 
155     if (skb->ip_summed != CHECKSUM_PARTIAL)
156 
157         th->check = csum_fold(csum_partial(skb_transport_header(skb),
158 
159                            thlen, skb->csum));
160 
161  
162 
163 out:
164 
165     return segs;
166 
167 }

 

   
从地点可以看出,各种TCP的GSO分片是含有了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 {
 4 
 5     struct sk_buff *segs = ERR_PTR(-EINVAL);
 6 
 7     unsigned int mss;
 8 
 9     int offset;
10 
11     __wsum csum;
12 
13  
14 
15     mss = skb_shinfo(skb)->gso_size;
16 
17     if (unlikely(skb->len <= mss))
18 
19         goto out;
20 
21  
22 
23     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
24 
25         /* Packet is from an untrusted source, reset gso_segs. */
26 
27         int type = skb_shinfo(skb)->gso_type;
28 
29  
30 
31         if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
32 
33                  !(type & (SKB_GSO_UDP))))
34 
35             goto out;
36 
37  
38 
39         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
40 
41  
42 
43         segs = NULL;
44 
45         goto out;
46 
47     }
48 
49  
50 
51     /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
52 
53      * do checksum of UDP packets sent as multiple IP fragments.
54 
55 */
56 
57     //计算udp的checksum
58 
59     offset = skb->csum_start - skb_headroom(skb);
60 
61     csum = skb_checksum(skb, offset, skb->len - offset, 0);
62 
63     offset += skb->csum_offset;
64 
65     *(__sum16 *)(skb->data + offset) = csum_fold(csum);
66 
67     skb->ip_summed = CHECKSUM_NONE;
68 
69     //这里传递给skb_segment做分片时是没有将UDP首部去除的
70 
71     segs = skb_segment(skb, features);
72 
73 out:
74 
75     return segs;
76 
77 }

 

只顾那里传递给skb_segment
做分片是富含udp首部的,分片将udp首部作为一般数据切分,那也象征对于udp的GSO分片,唯有首先片有UDP首部。udp的分段其实和ip的分片没什么不一样,只是多1个盘算checksum的步骤,上边看形成分片的关键函数skb_segment。

l   skb_segment

/net/core/skbuff.c

  1 struct sk_buff *skb_segment(struct sk_buff *skb, int features)
  2 
  3 {
  4 
  5     struct sk_buff *segs = NULL;
  6 
  7     struct sk_buff *tail = NULL;
  8 
  9     struct sk_buff *fskb = skb_shinfo(skb)->frag_list;
 10 
 11     unsigned int mss = skb_shinfo(skb)->gso_size;
 12 
 13     unsigned int doffset = skb->data - skb_mac_header(skb);//mac头+ip头+tcp头 或mac头+ip头(对于UDP传入时没有将头部偏移过去)
 14 
 15     unsigned int offset = doffset;
 16 
 17     unsigned int headroom;
 18 
 19     unsigned int len;
 20 
 21     int sg = features & NETIF_F_SG;
 22 
 23     int nfrags = skb_shinfo(skb)->nr_frags;
 24 
 25     int err = -ENOMEM;
 26 
 27     int i = 0;
 28 
 29     int pos;
 30 
 31  
 32 
 33     __skb_push(skb, doffset);
 34 
 35     headroom = skb_headroom(skb);
 36 
 37     pos = skb_headlen(skb);//pos初始化为线性区长度
 38 
 39  
 40 
 41     do {
 42 
 43         struct sk_buff *nskb;
 44 
 45         skb_frag_t *frag;
 46 
 47         int hsize;
 48 
 49         int size;
 50 
 51         // offset为分片已处理的长度,len为skb->len减去直到offset的部分。开始时,offset只是mac header + ip header + tcp header的长度,len即tcp payload的长度。随着segment增加, offset每次都增加mss长度。因此len的定义是每个segment的payload长度(最后一个segment的payload可能小于一个mss长度)
 52 
 53         len = skb->len - offset;
 54 
 55         if (len > mss)//len为本次要创建的新分片的长度
 56 
 57             len = mss;
 58 
 59        // hsize为线性区部分的payload减去offset后的大小,如果hsize小于0,那么说明payload在skb的frags或frag_list中。随着offset一直增长,必定会有hsize一直<0的情况开始出现,除非skb是一个完全linearize化的skb
 60 
 61         hsize = skb_headlen(skb) - offset;
 62 
 63         //这种情况说明线性区已经没有tcp payload的部分,需要pull数据过来
 64  
 65         if (hsize < 0)
 66 
 67 hsize = 0;
 68 
 69        //如果不支持NETIF_F_SG或者hsize大于len,那么hsize就为len(本次新分片的长度),此时说明segment的payload还在skb 线性区中
 70 
 71         if (hsize > len || !sg)
 72 
 73             hsize = len;
 74 
 75  
 76 
 77         if (!hsize && i >= nfrags) {// hsize为0,表示需要从frags数组或者frag_list链表中拷贝出数据,i >= nfrags说明frags数组中的数据也拷贝完了,下面需要从frag_list链表中拷贝数据了
 78 
 79             BUG_ON(fskb->len != len);
 80 
 81  
 82 
 83 pos += len;
 84 
 85             //frag_list的数据不用真的拷贝,只需要拷贝其skb描述符,就可以复用其数据区
 86 
 87             nskb = skb_clone(fskb, GFP_ATOMIC);//拷贝frag_list中的skb的描述符
 88 
 89             fskb = fskb->next;//指向frag_list的下一个skb元素
 90 
 91  
 92 
 93             if (unlikely(!nskb))
 94 
 95                 goto err;
 96 
 97  
 98 
 99              hsize = skb_end_pointer(nskb) - nskb->head;
100 
101             //保证新的skb的headroom有mac header+ip header+tcp/udp+header的大小
102 
103             if (skb_cow_head(nskb, doffset + headroom)) {
104 
105                 kfree_skb(nskb);
106 
107                 goto err;
108 
109             }
110 
111            //调整truesize,使其包含本次已分片的数据部分长度(hsize)
112 
113             nskb->truesize += skb_end_pointer(nskb) - nskb->head -
114 
115                       hsize;
116 
117             skb_release_head_state(nskb);
118 
119             __skb_push(nskb, doffset);
120 
121         } else {//数据从线性区或者frags数组中取得
122 
123             //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区
124 
125             nskb = alloc_skb(hsize + doffset + headroom,GFP_ATOMIC);
126 
127  
128 
129             if (unlikely(!nskb))
130 
131                 goto err;
132 
133             skb_reserve(nskb, headroom);
134 
135             __skb_put(nskb, doffset);
136 
137         }
138 
139  
140 
141         if (segs)
142 
143             tail->next = nskb;
144 
145         else
146 
147             segs = nskb;
148 
149         tail = nskb;
150 
151         //拷贝skb结构中的成员
152 
153         __copy_skb_header(nskb, skb);
154 
155         nskb->mac_len = skb->mac_len;
156 
157  
158 
159         /* nskb and skb might have different headroom */
160 
161         if (nskb->ip_summed == CHECKSUM_PARTIAL)
162 
163             nskb->csum_start += skb_headroom(nskb) - headroom;
164 
165  
166 
167         skb_reset_mac_header(nskb);
168 
169         skb_set_network_header(nskb, skb->mac_len);
170 
171         nskb->transport_header = (nskb->network_header +
172 
173         skb_network_header_len(skb));
174 
175        //把skb->data开始doffset长度的内容拷贝到nskb->data中
176 
177         skb_copy_from_linear_data(skb, nskb->data, doffset);
178 
179         // fskb被初始化为skb_shinfo(skb)->frag_list,现在如果不再相等,说明已经开始拷贝frag_list链表中的数据,不用继续后面的逻辑了(后面的逻辑是从线性区或者frags数组中拷贝的逻辑)
180 
181         if (fskb != skb_shinfo(skb)->frag_list)
182 
183             continue;
184 
185         //如果不支持NETIF_F_SG,说明frags数组中没有数据,只考虑从线性区中拷贝数据
186 
187         if (!sg) {
188 
189             nskb->ip_summed = CHECKSUM_NONE;
190 
191            //注意,每次要拷贝出的数据长度为len,其中hsize位于线性区
192 
193             nskb->csum = skb_copy_and_csum_bits(skb, offset,
194 
195                                 skb_put(nskb, len), len, 0);
196 
197             continue;
198 
199         }
200 
201  
202 
203         frag = skb_shinfo(nskb)->frags;
204 
205         //如果hsize不为0,那么拷贝hsize的内容到nskb的线性区中
206 
207         skb_copy_from_linear_data_offset(skb, offset,skb_put(nskb, hsize), hsize);
208 
209         //注意:每次要拷贝的数据长度是len,其中hsize是位于线性区中,但是随着线性区数据逐渐被处理,hsize可能不够len,这时剩下的(len-hsize)长度就要从frags数组中拷贝了
210 
211        while (pos < offset + len && i < nfrags) { //从frags数组中拷贝数据
212 
213             *frag = skb_shinfo(skb)->frags[i];
214 
215             get_page(frag->page);
216 
217             size = frag->size;
218 
219            //pos初始为线性区长度,后来表示已经被拷贝的长度
220 
221             if (pos < offset) {
222 
223                 frag->page_offset += offset - pos;
224 
225                 frag->size -= offset - pos;
226 
227             }
228 
229             //frags数组中的数据并不是真的拷贝,而是nskb的frags数组直接指向相应的page
230 
231             skb_shinfo(nskb)->nr_frags++;
232 
233  
234 
235             if (pos + size <= offset + len) {
236 
237                 i++;
238 
239                 pos += size;
240 
241             } else {
242 
243                 frag->size -= pos + size - (offset + len);
244 
245                 goto skip_fraglist;
246 
247             }
248 
249             frag++;
250 
251         }
252 
253          //如果把frags数组中的数据拷贝完还不够len长度,则需要从frag_list中拷贝了
254 
255         if (pos < offset + len) {
256 
257             struct sk_buff *fskb2 = fskb;//指向frag_list
258 
259  
260 
261             BUG_ON(pos + fskb->len != offset + len);
262 
263  
264 
265             pos += fskb->len;
266 
267             fskb = fskb->next;
268 
269  
270 
271             if (fskb2->next) {
272 
273                 fskb2 = skb_clone(fskb2, GFP_ATOMIC);
274 
275                 if (!fskb2)
276 
277                     goto err;
278 
279             } else
280 
281                 skb_get(fskb2);
282 
283  
284 
285 SKB_FRAG_ASSERT(nskb);
286 
287             //这里也不是真的拷贝数据,而是nskb的frag_list直接链上老的frag_list中的元素
288 
289             skb_shinfo(nskb)->frag_list = fskb2;
290 
291         }
292 
293 skip_fraglist:
294 
295         nskb->data_len = len - hsize;
296 
297         nskb->len += nskb->data_len;
298 
299         nskb->truesize += nskb->data_len;
300 
301     } while ((offset += len) < skb->len);//完成一个nskb之后,继续下一个seg,一直到offset >= skb->len
302 
303     return segs;
304 
305  
306 
307 err:
308 
309     while ((skb = segs)) {
310 
311         segs = skb->next;
312 
313         kfree_skb(skb);
314 
315     }
316 
317     return ERR_PTR(err);
318 
319 }

 
  从地点的分片进程能够看来,分成的小skb并不一定都是线性话的,若是在此之前的skb存在frags数组可能frag_list,则分为的小skb也说不定有针对非线性区域。并不用担心网卡不协助分散聚合IO,因为前边若是能生出那些非线性数据,就证实网卡一定是支撑的。

说到底记忆下任何协议栈的GSO处理逻辑,如下图:

 

咱俩再看一下skb社团方式在GSO前后的扭转:

    GSO之后如下,注意GSO之后也是唯恐含有frags的。

GSO逻辑分析,linuxgso逻辑 Linux GSO逻辑分析
——lvyilong316 (注:kernel版本linux 2.6.32)
GSO用来增加从前的TSO,近期早已集成upstream内核。…

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,分别代表udpfragmentation 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_PA哈弗TIAL。进入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。

澳门金沙国际 5

上边看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来拍卖,那里大家第1看下析构进度,此时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底部中id加1,(等同于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处理逻辑,如下图:

澳门金沙国际 6

大家再看一下skb协会情势在GSO前后的变化:

澳门金沙国际 7

GSO之后如下,注意GSO之后也是恐怕含有frags的。

澳门金沙国际 8

GSO逻辑分析 Linux GSO逻辑分析
——lvyilong316(转载请申明出处) (注:对应linux kernel 代码为linux
2.6.32) GSO用来增添此前的TSO,方今曾经…

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,分别代表udpfragmentation 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_PA奇骏TIAL。进入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。

澳门金沙国际 9

下边看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底部中id加1,(等同于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处理逻辑,如下图:

澳门金沙国际 10

咱俩再看一下skb协会格局在GSO前后的更动:

澳门金沙国际 11

GSO之后如下,注意GSO之后也是唯恐包含frags的。

澳门金沙国际 12

GSO逻辑分析 Linux GSO逻辑分析
——lvyilong316(转发请表明出处) (注:对应linux kernel 代码为linux
2.6.32) GSO用来扩充以前的TSO,方今早已…

相关文章