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