转载自:http://www.bsdmap.com/2012/02/22/转tso、ufo、gso、lro、gro和rss介绍/

转载自:http://blog.chinaunix.net/uid-28541347-id-5763844.html

通过ethtoo命令来查看和设置

ethtool -k < 网络接口>,ethtool --show-offload < 网络接口>,或者可以看到很多网络接口的offload特性,例如:

$ sudo ethtool -k eth0
Offload parameters for eth0:
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp-segmentation-offload: on
udp-fragmentation-offload: off
generic-segmentation-offload: on
generic-receive-offload: on
large-receive-offload: off

这些offload特性都是为了提升网络收/发性能。TSO、UFO和GSO是对应网络发送,在接收方向上对应的是LRO、GRO。

可以使用如下命令来关闭对应的参数:

/usr/sbin/ethtool -K eth1 gro off
/usr/sbin/ethtool -K eth1 lro off
/usr/sbin/ethtool -K eth1 tso off

LRO

LRO(Large Receive Offload),通过将接收到的多个TCP数据聚合成一个大的数据包,然后传递给网络协议栈处理,以减少上层协议栈处理 开销,提高系统接收TCP数据包的能力。

GRO

GRO(Generic Receive Offload),基本思想跟LRO类似,克服了LRO的一些缺点,更通用。后续的驱动都使用GRO的接口,而不是LRO。

RSS

RSS(Receive Side Scaling),是一项网卡的新特性,俗称多队列。具备多个RSS队列的网卡,可以将不同的网络流分成不同的队列,再分别将这些队列分配到多个CPU核心上进行处理,从而将负荷分散,充分利用多核处理器的能力。

TSO

TSO(TCP Segmentation Offload),是一种利用网卡对TCP数据包分片,减轻CPU负荷的一种技术,有时也被叫做 LSO (Large segment offload) ,TSO是针对TCP的,UFO是针对UDP的。如果硬件支持 TSO功能,同时也需要硬件支持的TCP校验计算和分散/聚集 (Scatter Gather) 功能。

GSO

GSO(Generic Segmentation Offload),它比TSO更通用,基本思想就是尽可能的推迟数据分片直至发送到网卡驱动之前,此时会检查网卡是否支持分片功能(如TSO、UFO),如果支持直接发送到网卡,如果不支持就进行分片后再发往网卡。这样大数据包只需走一次协议栈,而不是被分割成几个数据包分别走,这就提高了效率。

如果TSO开启,GSO会自动开启。

以下是TSO和GSO的组合关系:

  • GSO开启, TSO开启: 协议栈推迟分段,并直接传递大数据包到网卡,让网卡自动分段
  • GSO开启, TSO关闭: 协议栈推迟分段,在最后发送到网卡前才执行分段
  • GSO关闭, TSO开启: 同GSO开启, TSO开启
  • GSO关闭, TSO关闭: 不推迟分段,在tcp_sendmsg中直接发送MSS大小的数据包

开启GSO/TSO

驱动程序在注册网卡设备的时候默认开启GSO: NETIF_F_GSO

驱动程序会根据网卡硬件是否支持来设置TSO: NETIF_F_TSO

网卡的netdev->features值,在include/linux/netdevice.h中:

#define NETIF_F_SG      1   /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM     2   /* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */

#define NETIF_F_FRAGLIST    64  /* Scatter/gather IO. */

#define NETIF_F_GSO     2048    /* Enable software GSO. */

#define NETIF_F_GSO_SHIFT   16
#define NETIF_F_GSO_MASK    0x00ff0000
#define NETIF_F_TSO     (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO     (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)

对于要支持TSO的网卡而言,需要有 NETIF_F_SG | NETIF_F_TSO | NETIF_F_IP_CSUM,

相应如果要支持UFO,应该就需要 NETIF_F_SG | NETIF_F_UFO | NETIF_F_IP_CSUM

  1. #define NETIF_F_SOFT_FEATURES (NETIF_F_GSO | NETIF_F_GRO)
  2. int register_netdevice(struct net_device *dev)
  3. {
  4. ...
  5. /* Transfer changeable features to wanted_features and enable
  6. * software offloads (GSO and GRO).
  7. */
  8. dev->hw_features |= NETIF_F_SOFT_FEATURES;
  9. dev->features |= NETIF_F_SOFT_FEATURES; //默认开启GRO/GSO
  10. dev->wanted_features = dev->features & dev->hw_features;
  11. ...
  12. }
  13. static int ixgbe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
  14. {
  15. ...
  16. netdev->features = NETIF_F_SG |
  17. NETIF_F_TSO |
  18. NETIF_F_TSO6 |
  19. NETIF_F_RXHASH |
  20. NETIF_F_RXCSUM |
  21. NETIF_F_HW_CSUM;
  22. register_netdev(netdev);
  23. ...
  24. }

是否推迟分段

从上面我们知道GSO/TSO是否开启是保存在dev->features中,而设备和路由关联,当我们查询到路由后就可以把配置保存在sock中。

比如在tcp_v4_connect和tcp_v4_syn_recv_sock都会调用sk_setup_caps来设置GSO/TSO配置。

需要注意的是,只要开启了GSO,即使硬件不支持TSO,也会设置NETIF_F_TSO,使得sk_can_gso(sk)在GSO开启或者TSO开启的时候都返回true

l  sk_setup_caps

  1. #define NETIF_F_GSO_SOFTWARE (NETIF_F_TSO | NETIF_F_TSO_ECN | NETIF_F_TSO6)
  2. #define NETIF_F_TSO (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
  3. void sk_setup_caps(struct sock *sk, struct dst_entry *dst)
  4. {
  5. __sk_dst_set(sk, dst);
  6. sk->sk_route_caps = dst->dev->features;
  7. if (sk->sk_route_caps & NETIF_F_GSO) /*GSO默认都会开启*/
  8. sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE; /*打开TSO*/
  9. if (sk_can_gso(sk)) { /*对于tcp这里会成立*/
  10. if (dst->header_len) {
  11. sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
  12. } else {
  13. sk->sk_route_caps |= NETIF_F_SG | NETIF_F_HW_CSUM;
  14. sk->sk_gso_max_size = dst->dev->gso_max_size; /*GSO_MAX_SIZE=65536*/
  15. }
  16. }
  17. }

从上面可以看出,如果设备开启了GSO,sock都会将TSO标志打开,但是注意这和硬件是否开启TSO无关,硬件的TSO取决于硬件自身特性的支持。下面看下sk_can_gso的逻辑。

l  sk_can_gso

  1. static inline int sk_can_gso(const struct sock *sk)
  2. {
  3. /*对于tcp,在tcp_v4_connect中被设置:sk->sk_gso_type = SKB_GSO_TCPV4*/
  4. return net_gso_ok(sk->sk_route_caps, sk->sk_gso_type);
  5. }

l  net_gso_ok

  1. static inline int net_gso_ok(int features, int gso_type)
  2. {
  3. int feature = gso_type << NETIF_F_GSO_SHIFT;
  4. return (features & feature) == feature;
  5. }

由于对于tcp 在sk_setup_caps中sk->sk_route_caps也被设置有SKB_GSO_TCPV4,所以整个sk_can_gso成立。

GSO的数据包长度

对紧急数据包或GSO/TSO都不开启的情况,才不会推迟发送, 默认使用当前MSS。开启GSO后,tcp_send_mss返回mss和单个skb的GSO大小,为mss的整数倍。

l  tcp_send_mss

  1. static int tcp_send_mss(struct sock *sk, int *size_goal, int flags)
  2. {
  3. int mss_now;
  4. mss_now = tcp_current_mss(sk);/*通过ip option,SACKs及pmtu确定当前的mss*/
  5. *size_goal = tcp_xmit_size_goal(sk, mss_now, !(flags & MSG_OOB));
  6. return mss_now;
  7. }

l  tcp_xmit_size_goal

  1. static unsigned int tcp_xmit_size_goal(struct sock *sk, u32 mss_now,
  2. int large_allowed)
  3. {
  4. struct tcp_sock *tp = tcp_sk(sk);
  5. u32 xmit_size_goal, old_size_goal;
  6. xmit_size_goal = mss_now;
  7. /*这里large_allowed表示是否是紧急数据*/
  8. if (large_allowed && sk_can_gso(sk)) { /*如果不是紧急数据且支持GSO*/
  9. xmit_size_goal = ((sk->sk_gso_max_size - 1) -
  10. inet_csk(sk)->icsk_af_ops->net_header_len -
  11. inet_csk(sk)->icsk_ext_hdr_len -
  12. tp->tcp_header_len);/*xmit_size_goal为gso最大分段大小减去tcp和ip头部长度*/
  13. xmit_size_goal = tcp_bound_to_half_wnd(tp, xmit_size_goal);/*最多达到收到的最大rwnd窗口通告的一半*/
  14. /* We try hard to avoid divides here */
  15. old_size_goal = tp->xmit_size_goal_segs * mss_now;
  16. if (likely(old_size_goal <= xmit_size_goal &&
  17. old_size_goal + mss_now > xmit_size_goal)) {
  18. xmit_size_goal = old_size_goal; /*使用老的xmit_size*/
  19. } else {
  20. tp->xmit_size_goal_segs = xmit_size_goal / mss_now;
  21. xmit_size_goal = tp->xmit_size_goal_segs * mss_now; /*使用新的xmit_size*/
  22. }
  23. }
  24. return max(xmit_size_goal, mss_now);
  25. }

l  tcp_sendmsg

应用程序send()数据后,会在tcp_sendmsg中尝试在同一个skb,保存size_goal大小的数据,然后再通过tcp_push把这些包通过tcp_write_xmit发出去

  1. int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
  2. size_t size)
  3. {
  4. struct sock *sk = sock->sk;
  5. struct iovec *iov;
  6. struct tcp_sock *tp = tcp_sk(sk);
  7. struct sk_buff *skb;
  8. int iovlen, flags;
  9. int mss_now, size_goal;
  10. int err, copied;
  11. long timeo;
  12. lock_sock(sk);
  13. TCP_CHECK_TIMER(sk);
  14. flags = msg->msg_flags;
  15. timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
  16. /* Wait for a connection to finish. */
  17. if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
  18. if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
  19. goto out_err;
  20. /* This should be in poll */
  21. clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
  22. /* size_goal表示GSO支持的大小,为mss的整数倍,不支持GSO时则和mss相等 */
  23. mss_now = tcp_send_mss(sk, &size_goal, flags);/*返回值mss_now为真实mss*/
  24. /* Ok commence sending. */
  25. iovlen = msg->msg_iovlen;
  26. iov = msg->msg_iov;
  27. copied = 0;
  28. err = -EPIPE;
  29. if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
  30. goto out_err;
  31. while (--iovlen >= 0) {
  32. size_t seglen = iov->iov_len;
  33. unsigned char __user *from = iov->iov_base;
  34. iov++;
  35. while (seglen > 0) {
  36. int copy = 0;
  37. int max = size_goal; /*每个skb中填充的数据长度初始化为size_goal*/
  38. /* 从sk->sk_write_queue中取出队尾的skb,因为这个skb可能还没有被填满 */
  39. skb = tcp_write_queue_tail(sk);
  40. if (tcp_send_head(sk)) { /*如果之前还有未发送的数据*/
  41. if (skb->ip_summed == CHECKSUM_NONE) /*比如路由变更,之前的不支持TSO,现在的支持了*/
  42. max = mss_now; /*上一个不支持GSO的skb,继续不支持*/
  43. copy = max - skb->len; /*copy为每次想skb中拷贝的数据长度*/
  44. }
  45. /*copy<=0表示不能合并到之前skb做GSO*/
  46. if (copy <= 0) {
  47. new_segment:
  48. /* Allocate new segment. If the interface is SG,
  49. * allocate skb fitting to single page.
  50. */
  51. /* 内存不足,需要等待 */
  52. if (!sk_stream_memory_free(sk))
  53. goto wait_for_sndbuf;
  54. /* 分配新的skb */
  55. skb = sk_stream_alloc_skb(sk, select_size(sk), sk->sk_allocation);
  56. if (!skb)
  57. goto wait_for_memory;
  58. /*
  59. * Check whether we can use HW checksum.
  60. */
  61. /*如果硬件支持checksum,则将skb->ip_summed设置为CHECKSUM_PARTIAL,表示由硬件计算校验和*/
  62. if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
  63. skb->ip_summed = CHECKSUM_PARTIAL;
  64. /*将skb加入sk->sk_write_queue队尾, 同时去掉skb的TCP_NAGLE_PUSH标记*/
  65. skb_entail(sk, skb);
  66. copy = size_goal; /*这里将每次copy的大小设置为size_goal,即GSO支持的大小*/
  67. max = size_goal;
  68. }
  69. /* Try to append data to the end of skb. */
  70. if (copy > seglen)
  71. copy = seglen;
  72. /* Where to copy to? */
  73. if (skb_tailroom(skb) > 0) { /*如果skb的线性区还有空间,则先填充skb的线性区*/
  74. /* We have some space in skb head. */
  75. if (copy > skb_tailroom(skb))
  76. copy = skb_tailroom(skb);
  77. if ((err = skb_add_data(skb, from, copy)) != 0) /*copy用户态数据到skb线性区*/
  78. goto do_fault;
  79. } else { /*否则尝试向SG的frags中拷贝*/
  80. int merge = 0;
  81. int i = skb_shinfo(skb)->nr_frags;
  82. struct page *page = TCP_PAGE(sk);
  83. int off = TCP_OFF(sk);
  84. if (skb_can_coalesce(skb, i, page, off) && off != PAGE_SIZE) {/*pfrag->page和frags[i-1]是否使用相同页,并且page_offset相同*/
  85. /* We can extend the last page
  86. * fragment. */
  87. merge = 1; /*说明和之前frags中是同一个page,需要merge*/
  88. } else if (i == MAX_SKB_FRAGS || (!i && !(sk->sk_route_caps & NETIF_F_SG))) {
  89. /* Need to add new fragment and cannot
  90. * do this because interface is non-SG,
  91. * or because all the page slots are
  92. * busy. */
  93. /*如果设备不支持SG,或者非线性区frags已经达到最大,则创建新的skb分段*/
  94. tcp_mark_push(tp, skb); /*标记push flag*/
  95. goto new_segment;
  96. } else if (page) {
  97. if (off == PAGE_SIZE) {
  98. put_page(page); /*增加page引用计数*/
  99. TCP_PAGE(sk) = page = NULL;
  100. off = 0;
  101. }
  102. } else
  103. off = 0;
  104. if (copy > PAGE_SIZE - off)
  105. copy = PAGE_SIZE - off;
  106. if (!sk_wmem_schedule(sk, copy))
  107. goto wait_for_memory;
  108. if (!page) {
  109. /* Allocate new cache page. */
  110. if (!(page = sk_stream_alloc_page(sk)))
  111. goto wait_for_memory;
  112. }
  113. err = skb_copy_to_page(sk, from, skb, page, off, copy); /*拷贝数据到page中*/
  114. if (err) {
  115. /* If this page was new, give it to the
  116. * socket so it does not get leaked.
  117. */
  118. if (!TCP_PAGE(sk)) {
  119. TCP_PAGE(sk) = page;
  120. TCP_OFF(sk) = 0;
  121. }
  122. goto do_error;
  123. }
  124. /* Update the skb. */
  125. if (merge) { /*pfrag和frags[i - 1]是相同的*/
  126. skb_shinfo(skb)->frags[i - 1].size += copy;
  127. } else {
  128. skb_fill_page_desc(skb, i, page, off, copy);
  129. if (TCP_PAGE(sk)) {
  130. get_page(page);
  131. } else if (off + copy < PAGE_SIZE) {
  132. get_page(page);
  133. TCP_PAGE(sk) = page;
  134. }
  135. }
  136. TCP_OFF(sk) = off + copy;
  137. }
  138. if (!copied)
  139. TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;
  140. tp->write_seq += copy;
  141. TCP_SKB_CB(skb)->end_seq += copy;
  142. skb_shinfo(skb)->gso_segs = 0; /*清零tso分段数,让tcp_write_xmit去计算*/
  143. from += copy;
  144. copied += copy;
  145. if ((seglen -= copy) == 0 && iovlen == 0)
  146. goto out;
  147. /* 还有数据没copy,并且没有达到最大可拷贝的大小(注意这里max之前被赋值为size_goal,即GSO支持的大小), 尝试往该skb继续添加数据*/
  148. if (skb->len < max || (flags & MSG_OOB))
  149. continue;
  150. /*下面的逻辑就是:还有数据没copy,但是当前skb已经满了,所以可以发送了(但不是一定要发送)*/
  151. if (forced_push(tp)) { /*超过最大窗口的一半没有设置push了*/
  152. tcp_mark_push(tp, skb); /*设置push标记,更新pushed_seq*/
  153. __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH); /*调用tcp_write_xmit马上发送*/
  154. } else if (skb == tcp_send_head(sk)) /*第一个包,直接发送*/
  155. tcp_push_one(sk, mss_now);
  156. continue; /*说明发送队列前面还有skb等待发送,且距离之前push的包还不是非常久*/
  157. wait_for_sndbuf:
  158. set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
  159. wait_for_memory:
  160. if (copied)/*先把copied的发出去再等内存*/
  161. tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
  162. /*阻塞等待内存*/
  163. if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
  164. goto do_error;
  165. mss_now = tcp_send_mss(sk, &size_goal, flags);
  166. }
  167. }
  168. out:
  169. if (copied) /*所有数据都放到发送队列中了,调用tcp_push发送*/
  170. tcp_push(sk, flags, mss_now, tp->nonagle);
  171. TCP_CHECK_TIMER(sk);
  172. release_sock(sk);
  173. return copied;
  174. do_fault:
  175. if (!skb->len) {
  176. tcp_unlink_write_queue(skb, sk);
  177. /* It is the one place in all of TCP, except connection
  178. * reset, where we can be unlinking the send_head.
  179. */
  180. tcp_check_send_head(sk, skb);
  181. sk_wmem_free_skb(sk, skb);
  182. }
  183. do_error:
  184. if (copied)
  185. goto out;
  186. out_err:
  187. err = sk_stream_error(sk, flags, err);
  188. TCP_CHECK_TIMER(sk);
  189. release_sock(sk);
  190. return err;
  191. }

最终会调用tcp_push发送skb,而tcp_push又会调用tcp_write_xmit。tcp_sendmsg已经把数据按照GSO最大的size,放到一个个的skb中, 最终调用tcp_write_xmit发送这些GSO包。tcp_write_xmit会检查当前的拥塞窗口,还有nagle测试,tsq检查来决定是否能发送整个或者部分的skb, 如果只能发送一部分,则需要调用tso_fragment做切分。最后通过tcp_transmit_skb发送, 如果发送窗口没有达到限制,skb中存放的数据将达到GSO最大值。

l  tcp_write_xmit

  1. static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
  2. int push_one, gfp_t gfp)
  3. {
  4. struct tcp_sock *tp = tcp_sk(sk);
  5. struct sk_buff *skb;
  6. unsigned int tso_segs, sent_pkts;
  7. int cwnd_quota;
  8. int result;
  9. sent_pkts = 0;
  10. if (!push_one) {
  11. /* Do MTU probing. */
  12. result = tcp_mtu_probe(sk);
  13. if (!result) {
  14. return 0;
  15. } else if (result > 0) {
  16. sent_pkts = 1;
  17. }
  18. }
  19. /*遍历发送队列*/
  20. while ((skb = tcp_send_head(sk))) {
  21. unsigned int limit;
  22. tso_segs = tcp_init_tso_segs(sk, skb, mss_now); /*skb->len/mss,重新设置tcp_gso_segs,因为在tcp_sendmsg中被清零了*/
  23. BUG_ON(!tso_segs);
  24. cwnd_quota = tcp_cwnd_test(tp, skb);
  25. if (!cwnd_quota)
  26. break;
  27. if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
  28. break;
  29. if (tso_segs == 1) { /*tso_segs=1表示无需tso分段*/
  30. /* 根据nagle算法,计算是否需要推迟发送数据 */
  31. if (unlikely(!tcp_nagle_test(tp, skb, mss_now, (tcp_skb_is_last(sk, skb) ? /*last skb就直接发送*/
  32. nonagle : TCP_NAGLE_PUSH))))
  33. break;
  34. } else {/*有多个tso分段*/
  35. if (!push_one /*push所有skb*/
  36. && tcp_tso_should_defer(sk, skb))/*/如果发送窗口剩余不多,并且预计下一个ack将很快到来(意味着可用窗口会增加),则推迟发送*/
  37. break;
  38. }
  39. /*下面的逻辑是:不用推迟发送,马上发送的情况*/
  40. limit = mss_now;
  41. /*由于tso_segs被设置为skb->len/mss_now,所以开启gso时一定大于1*/
  42. if (tso_segs > 1 && !tcp_urg_mode(tp)) /*tso分段大于1且非urg模式*/
  43. limit = tcp_mss_split_point(sk, skb, mss_now, cwnd_quota);/*返回当前skb中可以发送的数据大小,通过mss和cwnd*/
  44. /* 当skb的长度大于限制时,需要调用tso_fragment分片,如果分段失败则暂不发送 */
  45. if (skb->len > limit &&
  46. unlikely(tso_fragment(sk, skb, limit, mss_now))) /*/按limit切割成多个skb*/
  47. break;
  48. TCP_SKB_CB(skb)->when = tcp_time_stamp;
  49. /*发送,如果包被qdisc丢了,则退出循环,不继续发送了*/
  50. if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
  51. break;
  52. /* Advance the send_head. This one is sent out.
  53. * This call will increment packets_out.
  54. */
  55. /*更新sk_send_head和packets_out*/
  56. tcp_event_new_data_sent(sk, skb);
  57. tcp_minshall_update(tp, mss_now, skb);
  58. sent_pkts++;
  59. if (push_one)
  60. break;
  61. }
  62. if (likely(sent_pkts)) {
  63. tcp_cwnd_validate(sk);
  64. return 0;
  65. }
  66. return !tp->packets_out && tcp_send_head(sk);
  67. }

其中tcp_init_tso_segs会设置skb的gso信息后文分析。我们看到tcp_write_xmit 会调用tso_fragment进行“tcp分段”。而分段的条件是skb->len > limit。这里的关键就是limit的值,我们看到在tso_segs > 1时,也就是开启gso的时候,limit的值是由tcp_mss_split_point得到的,也就是min(skb->len, window),即发送窗口允许的最大值。在没有开启gso时limit就是当前的mss。

l  tcp_init_tso_segs

  1. static int tcp_init_tso_segs(struct sock *sk, struct sk_buff *skb, unsigned int mss_now)
  2. {
  3. int tso_segs = tcp_skb_pcount(skb); /*skb_shinfo(skb)->gso_seg之前被初始化为0*/
  4. if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
  5. tcp_set_skb_tso_segs(sk, skb, mss_now);
  6. tso_segs = tcp_skb_pcount(skb);
  7. }
  8. return tso_segs;
  9. }
  1. static void tcp_set_skb_tso_segs(struct sock *sk, struct sk_buff *skb, unsigned int mss_now)
  2. {
  3. /* Make sure we own this skb before messing gso_size/gso_segs */
  4. WARN_ON_ONCE(skb_cloned(skb));
  5. if (skb->len <= mss_now || !sk_can_gso(sk) ||
  6. skb->ip_summed == CHECKSUM_NONE) {/*不支持gso的情况*/
  7. /* Avoid the costly divide in the normal
  8. * non-TSO case.
  9. */
  10. skb_shinfo(skb)->gso_segs = 1;
  11. skb_shinfo(skb)->gso_size = 0;
  12. skb_shinfo(skb)->gso_type = 0;
  13. } else {
  14. skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss_now); /*被设置为skb->len/mss_now*/
  15. skb_shinfo(skb)->gso_size = mss_now; /*注意mss_now为真实的mss,这里保存以供gso分段使用*/
  16. skb_shinfo(skb)->gso_type = sk->sk_gso_type;
  17. }
  18. }

tcp_write_xmit最后会调用ip_queue_xmit发送skb,进入ip层。

ip分片,tcp分段,GSO,TSO

下面我们看下整个协议栈中ip分片,tcp分段,GSO,TSO的关系。原作者将这个流程由下图表示。

TSO、GSO介绍与实现相关推荐

  1. TSO/GSO GRO/LRO 从入门到精通

    目录 概念介绍 功能与用途 使用场景 在协议栈各个层次如何实现 参考资料 一.概念介绍: TSO/GSO TSO 是(TCP segmentation offload )的缩写,主要把TCP分段这个o ...

  2. TCP TSO/GSO初步探索

    参考:https://blog.csdn.net/quqi99/article/details/51066800            https://www.ibm.com/developerwor ...

  3. linux tso gso关系,1.3.1 TSO/GSO

    1.3.1  TSO/GSO TSO是通过网络设备进行TCP段的分割,从而来提高网络性能的一种技术.较大的数据包(超过标准1518B的帧)可以使用该技术,使操作系统减少必须处理的数据数量以提高性能.通 ...

  4. linux内核协议栈 TCP层数据发送之TSO/GSO

    目录 1 基本概念 2 TCP延迟分段判定 2.1 客户端初始化 2.2 服务器端初始化 2.3 sk_setup_caps() 3 整体结构 4. TCP发送路径TSO处理 4.1 tcp_send ...

  5. TCP数据发送之TSO/GSO

    TSO相关的内容充斥着TCP的整个发送过程,弄明白其机制对理解TCP的发送过程至关重要,这篇笔记就来看看TSO相关内容. 1. 基本概念 我们知道,网络设备一次能够传输的最大数据量就是MTU,即IP传 ...

  6. 以太网卡TSO、GSO、LRO、GRO描述及相关配置

    文章目录 以太网卡TSO.GSO.LRO.GRO描述及相关配置 硬件包拆分与合并 TSO(TCP Segmentation Offload ) UFO(UDP Fragmentation Offloa ...

  7. 网络协议栈TSO/UFO/GSO/LRO/GRO/RSS特性

    作者 QQ群:852283276 微信:arm80x86 微信公众号:青儿创客基地 B站:主页 https://space.bilibili.com/208826118 参考 网卡多队列技术与RSS功 ...

  8. GSO/TSO/GRO等对VirtIO虚机的网络性能影响分析(by quqi99)

    作者:张华  发表于:2016-04-05 版权声明:能够随意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 ( http://blog.csdn.net/quqi99 ) IP ...

  9. 计算机网络 网络性能优化技术 数据发送TSO/USO/GSO

    目录 概念介绍 如何查看网卡是否启用了TSO 概念介绍 我们知道,网络设备一次能够传输的最大数据量就是MTU,即IP传递给网络设备的每一个数据包不能超过MTU个字节,IP层的分段和重组功能就是为了适配 ...

最新文章

  1. 绘制多边形_PS学习教程!教你绘制低多边形星空效果熊猫头像
  2. 计算机硬盘有usb借口吗,为什么移动硬盘不建议插在台式机前置USB接口上
  3. wxWidgets:可用类概述
  4. 计算机有残留office,电脑中无法安装Office2013删除残留文件的方法
  5. 第三次学JAVA再学不好就吃翔(part79)--并发修改异常产生的原因及解决方案
  6. MYSQL复制的几种模式
  7. io流技术java_技术文章-java中的IO流
  8. Exceptionless服务端本地化部署
  9. C语言 单链表查找出倒数第,查找单链表倒数第k个元素
  10. cdh-5.10.0搭建安装
  11. 通过内网穿透 将本地端口 使其外网可以进行访问 使用花生壳内网穿透 网站访问
  12. html dom之iframe对象
  13. 厦门大学信息计算机学院,厦门大学信息科学与技术学院计算机科学系导师介绍:刘向荣...
  14. Java小项目——家庭记账软件
  15. 什么是聚合支付?聚合支付的优势?
  16. Word域的应用和详解
  17. 与同行的对比,M-DAO的崛起已成定局
  18. 平克四部曲之《白板》
  19. python qrcode库生成二维码的代码
  20. 基于autojs7的亚丁号成语大家族辅助

热门文章

  1. 2021年一级注册消防工程师的教材出了吗?
  2. Capture One Pro12.0.3.22 中文版l飞思RAW软件 【含教程】
  3. Vue(四十一)、Vuex的应用
  4. 秋招干货|应届生毕业生求职简历模板
  5. oracle存储过程调用sql文件,oracle存储过程的sql调用
  6. [下载]黑莓BlackBerry开发官方文档系列
  7. javascript之bind使用介绍
  8. Returnil Virtual System Personal/Business Beta 1.70.6160
  9. onethink-1.1开发版安装
  10. 共享软件为何要走向国际--月光博客