4.2 TCP Segmentation Offload(TSO)

  TSO (TCP Segmentation Offload) 是一种利用网卡替代CPU对大数据包进行分片,降低CPU负载的技术。如果数据包的类型只能是TCP,则被称之为TSO。此功能需要网卡提供支持。TSO 是使得网络协议栈能够将大块 buffer 推送至网卡,然后网卡执行分片工作,这样减轻了CPU的负荷,其本质实际是延缓分片。这种技术在Linux中被叫做GSO(Generic Segmentation Offload),它不需要硬件的支持分片就可使用。对于支持TSO功能的硬件,则先经过GSO功能处理,然后使用网卡的硬件分片能力进行分片;而当网卡不支持TSO功能时,则将分片的执行放在了将数据推送的网卡之前,也就是在调用网卡驱动注册的ndo_start_xmit函数之前。

    在TCP连接的建立阶段,需要开启TSO功能。SYN发送:

142 int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
 143 {
...
 234     sk->sk_gso_type = SKB_GSO_TCPV4;
 235     sk_setup_caps(sk, &rt->dst);
...
   三次握手完成:
 1642 struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
1643                   struct request_sock *req,      
1644                   struct dst_entry *dst)         
1645 {
...
1662     newsk->sk_gso_type = SKB_GSO_TCPV4;
...
1689     sk_setup_caps(newsk, dst);
...
   sk_setup_caps 函数:
1507 void sk_setup_caps(struct sock *sk, struct dst_entry *dst)
1508 {  
1509     __sk_dst_set(sk, dst);
1510     sk->sk_route_caps = dst->dev->features;//获取网卡功能特性
1511     if (sk->sk_route_caps & NETIF_F_GSO)//网卡支持GSO
1512         sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE;//添加所有GSO相关特性标志
1513     sk->sk_route_caps &= ~sk->sk_route_nocaps;//取消遭到禁用的特性
1514     if (sk_can_gso(sk)) {//网卡支持GSO且socket开启了GSO
1515         if (dst->header_len) {//在SKB的头部需要更多的空间
1516             sk->sk_route_caps &= ~NETIF_F_GSO_MASK;//禁用GSO相关功能
1517         } else {
1518             sk->sk_route_caps |= NETIF_F_SG | NETIF_F_HW_CSUM;//为网卡开启分散-聚集功能和计算检验和的功能
1519             sk->sk_gso_max_size = dst->dev->gso_max_size;
1520             sk->sk_gso_max_segs = dst->dev->gso_max_segs;
1521         }
1522     }
1523 }
  现在回顾数据发送过程,tcp_sendmsg函数:
 1016 int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
1017         size_t size)
1018 {   
1019     struct iovec *iov;
1020     struct tcp_sock *tp = tcp_sk(sk);
1021     struct sk_buff *skb;
1022     int iovlen, flags, err, copied = 0;
1023     int mss_now = 0, size_goal, copied_syn = 0, offset = 0;
1024     bool sg;
...
1067     mss_now = tcp_send_mss(sk, &size_goal, flags);//计算当前最大报文段大小,并将网卡能一次发送的最大数据长度的值放入size_goal中
...
1078     sg = !!(sk->sk_route_caps & NETIF_F_SG);//如果网卡开启分散-聚集功能,则sg为1
1079
1080     while (--iovlen >= 0) {
1081         size_t seglen = iov->iov_len;
...
1095         while (seglen > 0) {
1096             int copy = 0;
1097             int max = size_goal;
1098
1099             skb = tcp_write_queue_tail(sk);
1100             if (tcp_send_head(sk)) {
1101                 if (skb->ip_summed == CHECKSUM_NONE)//如果网卡不支持计算IP检验和
1102                     max = mss_now;//不能使用GSO功能,只发送一个MSS大小的报文
1103                 copy = max - skb->len;
1104             }
1105
1106             if (copy <= 0) {
...
1114                 skb = sk_stream_alloc_skb(sk,
1115                               select_size(sk, sg),
1116                               sk->sk_allocation);//根据select_size函数计算的大小申请SKB
1117                 if (!skb)
1118                     goto wait_for_memory;
...
1127                 /*
1128                  * Check whether we can use HW checksum.
1129                  */
1130                 if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
1131                     skb->ip_summed = CHECKSUM_PARTIAL;//网卡支持计算IP检验和
... 
<span style="color:#000000;">1142             /* Where to copy to? */
1143             if (skb_availroom(skb) > 0) {<span style="color:#337FE5;">//线性区还有空间</span>
1144                 /* We have some space in skb head. Superb! */
1145                 copy = min_t(int, copy, skb_availroom(skb));
1146                 err = skb_add_data_nocache(sk, skb, from, copy);<span style="color:#337FE5;">//将用户态内存中的数据copy到SKB中</span>
1147                 if (err)
1148                     goto do_fault;
1149             } else {
1150                 bool merge = true;
1151                 int i = skb_shinfo(skb)->nr_frags;<span style="color:#337FE5;">//已经分配非连续页的数量</span>
1152                 struct page_frag *pfrag = sk_page_frag(sk);
1153 
1154                 if (!sk_page_frag_refill(sk, pfrag))<span style="color:#337FE5;">//判断当前页是否有空间可写;如果没有则申请新页,申请不到则需要等待有内存可用</span>
1155                     goto wait_for_memory;
1156 
1157                 if (!skb_can_coalesce(skb, i, pfrag->page,
1158                               pfrag->offset)) {<span style="color:#000000;"><span style="color:#337FE5;">//判断当前页是否需要加入到</span><span style="color:#337FE5;">skb_shinfo(skb)->frags</span><span style="color:#337FE5;">数组中</span></span>
1159                     if (i == MAX_SKB_FRAGS || !sg) {
1160                         tcp_mark_push(tp, skb);
1161                         goto new_segment;
1162                     }
1163                     merge = false;<span style="color:#337FE5;">//不需要加入,因为当前页是skb_shinfo(skb)->frags数组最后的成员且有空间可写</span>
1164                 }
1165 
1166                 copy = min_t(int, copy, pfrag->size - pfrag->offset);
1167 
1168                 if (!sk_wmem_schedule(sk, copy))<span style="color:#337FE5;">//查看socket的内存限制</span>
1169                     goto wait_for_memory;
1170 
1171                 err = skb_copy_to_page_nocache(sk, from, skb,
1172                                    pfrag->page,
1173                                    pfrag->offset,
1174                                    copy);<span style="color:#337FE5;">//将用户态空间中的数据copy到页中</span>
1175                 if (err)
1176                     goto do_error;
1177 
1178                 /* Update the skb. */
1179                 if (merge) {<span style="color:#337FE5;">//没有申请新页</span>
1180                     skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);<span style="color:#337FE5;">//更新页大小</span>
1181                 } else {
1182                     skb_fill_page_desc(skb, i, pfrag->page,
1183                                pfrag->offset, copy);<span style="color:#337FE5;">//将新页加入到</span><span style="color:#337FE5;"><span style="color:#337FE5;">skb_shinfo(skb)->frags数组</span></span><span style="color:#337FE5;">中</span>
1184                     get_page(pfrag->page);
1185                 }
1186                 pfrag->offset += copy;
1187             }</span>
...

  1150-1163:线性区没有空间,但还允许向这个skb中写入数据,原因是mss变大了或者网卡支持分散-聚集IO,只能将数据保存在非连续空间中;如果网卡不支持分散-聚集IO,则系统会在将数据发送到驱动前将非线性区中的数据线性化

  tcp_send_mss函数:

780 static u
  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值