linux tcp GSO和TSO实现
linux tcp GSO和TSO实现
——lvyilong316(注:kernel版本:linux 2.6.32)
概念
TSO(TCP Segmentation Offload):是一种利用网卡来对大数据包进行自动分段,降低CPU负载的技术。其主要是延迟分段。
GSO(Generic Segmentation Offload): GSO是协议栈是否推迟分段,在发送到网卡之前判断网卡是否支持TSO,如果网卡支持TSO则让网卡分段,否则协议栈分完段再交给驱动。如果TSO开启,GSO会自动开启。
以下是TSO和GSO的组合关系:
lGSO开启,TSO开启:协议栈推迟分段,并直接传递大数据包到网卡,让网卡自动分段
lGSO开启,TSO关闭:协议栈推迟分段,在最后发送到网卡前才执行分段
lGSO关闭,TSO开启:同GSO开启,TSO开启
lGSO关闭,TSO关闭:不推迟分段,在tcp_sendmsg中直接发送MSS大小的数据包
开启GSO/TSO
驱动程序在注册网卡设备的时候默认开启GSO: NETIF_F_GSO
驱动程序会根据网卡硬件是否支持来设置TSO: NETIF_F_TSO
可以通过ethtool -K来开关GSO/TSO
- #define NETIF_F_SOFT_FEATURES(NETIF_F_GSO|NETIF_F_GRO)
- intregister_netdevice(struct net_device*dev)
- {
- ...
- /*Transfer changeable featurestowanted_featuresandenable
- *software offloads(GSOandGRO).
- */
- dev->hw_features|=NETIF_F_SOFT_FEATURES;
- dev->features|=NETIF_F_SOFT_FEATURES;//默认开启GRO/GSO
- dev->wanted_features=dev->features&dev->hw_features;
- ...
- }
- staticintixgbe_probe(struct pci_dev*pdev,conststruct pci_device_id*ent)
- {
- ...
- netdev->features=NETIF_F_SG|
- NETIF_F_TSO|
- NETIF_F_TSO6|
- NETIF_F_RXHASH|
- NETIF_F_RXCSUM|
- NETIF_F_HW_CSUM;
- register_netdev(netdev);
- ...
- }
是否推迟分段
从上面我们知道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
lsk_setup_caps
- #define NETIF_F_GSO_SOFTWARE(NETIF_F_TSO|NETIF_F_TSO_ECN|NETIF_F_TSO6)
- #define NETIF_F_TSO(SKB_GSO_TCPV4<
- void sk_setup_caps(struct sock*sk,struct dst_entry*dst)
- {
- __sk_dst_set(sk,dst);
- sk->sk_route_caps=dst->dev->features;
- if(sk->sk_route_caps&NETIF_F_GSO)/*GSO默认都会开启*/
- sk->sk_route_caps|=NETIF_F_GSO_SOFTWARE;/*打开TSO*/
- if(sk_can_gso(sk)){/*对于tcp这里会成立*/
- if(dst->header_len){
- sk->sk_route_caps&=~NETIF_F_GSO_MASK;
- }else{
- sk->sk_route_caps|=NETIF_F_SG|NETIF_F_HW_CSUM;
- sk->sk_gso_max_size=dst->dev->gso_max_size;/*GSO_MAX_SIZE=65536*/
- }
- }
- }
从上面可以看出,如果设备开启了GSO,sock都会将TSO标志打开,但是注意这和硬件是否开启TSO无关,硬件的TSO取决于硬件自身特性的支持。下面看下sk_can_gso的逻辑。
lsk_can_gso
- static inlineintsk_can_gso(conststruct sock*sk)
- {
- /*对于tcp,在tcp_v4_connect中被设置:sk->sk_gso_type=SKB_GSO_TCPV4*/
- return net_gso_ok(sk->sk_route_caps,sk->sk_gso_type);
- }
lnet_gso_ok
- static inlineintnet_gso_ok(intfeatures,intgso_type)
- {
- intfeature=gso_type<
- return(features&feature)==feature;
- }
由于对于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的整数倍。
ltcp_send_mss
- staticinttcp_send_mss(struct sock*sk,int*size_goal,intflags)
- {
- intmss_now;
- mss_now=tcp_current_mss(sk);/*通过ip option,SACKs及pmtu确定当前的mss*/
- *size_goal=tcp_xmit_size_goal(sk,mss_now,!(flags&MSG_OOB));
- return mss_now;
- }
ltcp_xmit_size_goal
- static unsignedinttcp_xmit_size_goal(struct sock*sk,u32 mss_now,
- intlarge_allowed)
- {
- struct tcp_sock*tp=tcp_sk(sk);
- u32 xmit_size_goal,old_size_goal;
- xmit_size_goal=mss_now;
- /*这里large_allowed表示是否是紧急数据*/
- if(large_allowed&&sk_can_gso(sk)){/*如果不是紧急数据且支持GSO*/
- xmit_size_goal=((sk->sk_gso_max_size-1)-
- inet_csk(sk)->icsk_af_ops->net_header_len-
- inet_csk(sk)->icsk_ext_hdr_len-
- tp->tcp_header_len);/*xmit_size_goal为gso最大分段大小减去tcp和ip头部长度*/
- xmit_size_goal=tcp_bound_to_half_wnd(tp,xmit_size_goal);/*最多达到收到的最大rwnd窗口通告的一半*/
- /*We try hardtoavoid divides here*/
- old_size_goal=tp->xmit_size_goal_segs*mss_now;
- if(likely(old_size_goal<=xmit_size_goal&&
- old_size_goal+mss_now>xmit_size_goal)){
- xmit_size_goal=old_size_goal;/*使用老的xmit_size*/
- }else{
- tp->xmit_size_goal_segs=xmit_size_goal/mss_now;
- xmit_size_goal=tp->xmit_size_goal_segs*mss_now;/*使用新的xmit_size*/
- }
- }
- return max(xmit_size_goal,mss_now);
- }
ltcp