TSO GSO
以前有过一篇TSO-GSO文章;目前再来回顾一下:
TSO与GSO的重要区别
1, TSO只有第一个分片有TCP头和IP头,接着的分段只有IP头。硬件tso由ixgbe_tso(ixgbe网卡)等函数来处理tso ,然后在请求DMA,写寄存器,通知网卡发送数据。
2, GSO在分段时会调用TCP或UDP的回调函数(udp4_ufo_fragment)为每个分段都加上IP头,由于分段是通过mss设置的(mss由发送端设置),所以长度仍然可能超过mtu值,所以在IP层还得再分片(代码位于dev_hard_start_xmit)。
收数据
LRO(Large Receive Offload),TSO是发,LRO是收。将多个TCP分段聚合成一个skb结构,以减小上层协议栈的skb的开销。skb的数据保存在skb->data中,分段的数据保存在skb_shared_info->frag_list中。
GRO(Generic Receive Offloading),GSO是发,GRO是收。LRO使用发送方和目的地IP地址,IP封包ID,L4协议三者来区分段,对于从同一个SNAT域的两个机器发向同一目的IP的两个封包可能会存在相同的IP封包ID(因为不是全局分配的ID),这样会有所谓的卷绕的bug。GRO采用发送方和目的地IP地址,源/目的端口,L4协议三者来区分作为改进。所以对于后续的驱动都应该使用GRO的接口,而不是LRO。另外,GRO也支持多协议。
- 1, 物理网卡不支持GRO时, 使用LRO在驱动处合并了多个skb一次性通过网络栈,对CPU负荷的减轻是显然的。
- 2, 物理网卡不支持LRO时,使用GRO在从驱动接收数据那一刻合并了多个skb一次性通过网络栈,对CPU负荷的减轻是显然的
关于GRO的相关分析见此篇文章
ip_queue_xmit--->ip_output--->ip_finish_output
--->dev_queue_xmit
继续IP层的GSO分片
static int ip_finish_output_gso(struct net *net, struct sock *sk, struct sk_buff *skb, unsigned int mtu) { netdev_features_t features; struct sk_buff *segs; int ret = 0; /* common case: locally created skb or seglen is <= mtu */ //只有ip forward流程该条件才会不成立,否则该条件成 也就是本地发包是会直接走 ip_finish_output2 发包; if (((IPCB(skb)->flags & IPSKB_FORWARDED) == 0) || skb_gso_network_seglen(skb) <= mtu) return ip_finish_output2(net, sk, skb); /* Slowpath - GSO segment length is exceeding the dst MTU. * * This can happen in two cases: * 1) TCP GRO packet, DF bit not set * 2) skb arrived via virtio-net, we thus get TSO/GSO skbs directly * from host network stack. */ features = netif_skb_features(skb); BUILD_BUG_ON(sizeof(*IPCB(skb)) > SKB_SGO_CB_OFFSET); //调用回调函数, 对于TCP则是调用TCP的gso_segment回调函数进行TCP GSO分段 inet_gso_segment--->tcp4_gso_segment segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK);//skb gso报文分段 ------>此处可以参考GRO的实现 if (IS_ERR_OR_NULL(segs)) { kfree_skb(skb); return -ENOMEM; } consume_skb(skb); do { struct sk_buff *nskb = segs->next; int err; segs->next = NULL; //分段报文经过ip分片后通过ip_finish_output2发送 err = ip_fragment(net, sk, segs, mtu, ip_finish_output2); if (err && ret == 0) ret = err; segs = nskb; } while (segs); return ret; }
IP层发包后进入二层发包!!
二层发包GSO
IP层发包调用__dev_queue_xmit--->sch_direct_xmit--->validate_xmit_skb_list() 检测报文是否需要进行GSO 等分段:
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) { ///... if (netif_needs_gso(skb, features)) { if (unlikely(dev_gso_segment(skb, features))) ///GSO(software offload) goto out_kfree_skb; ------------------------------------- }
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, struct net_device *dev, struct netdev_queue *txq, spinlock_t *root_lock, bool validate) { int ret = NETDEV_TX_BUSY; --------------------------------------- /* Note that we validate skb (GSO, checksum, ...) outside of locks */ if (validate) skb = validate_xmit_skb_list(skb, dev); ----------------------------------- }
static inline bool skb_gso_ok(struct sk_buff *skb, netdev_features_t features) { return net_gso_ok(features, skb_shinfo(skb)->gso_type) && (!skb_has_frag_list(skb) || (features & NETIF_F_FRAGLIST)); // //feature包含gso_type 并且skb没有frag_list(---->有frag_list 表示不支持SG)或者feature包含NETIF_F_FRAGLIST(表示SG Scatter/gather IO. ) } static inline bool netif_needs_gso(struct sk_buff *skb, netdev_features_t features) { //判断是否需要进行GSO分段,主要 两个参数features(硬件支持的特性)和skb报文。 //主要判断条件是features是否包含skb->gso_type。那么我们来看下features是怎么得到的, //其实是通过netif_skb_features得到的。 //skb 为gso报文 && (gso 没有准备好---且feature不包含skb->gso_type) //skb 为gso报文,且skb_ipsummed不为CHECKSUM_PARTIAL和CHECKSUM_UNNECESSARY return skb_is_gso(skb) && (!skb_gso_ok(skb, features) || unlikely((skb->ip_summed != CHECKSUM_PARTIAL) && (skb->ip_summed != CHECKSUM_UNNECESSARY))); }
对于GSO相关逻辑可以参考:mac-ip-tcp-GSO
/* This data is invariant across clones and lives at * the end of the header data, ie. at skb->end. */ struct skb_shared_info { unsigned char nr_frags; __u8 tx_flags; unsigned short gso_size;//生成GSO段时的MSS,因为GSO段的长度是与发送段的套接口中合适MSS的倍数 /* Warning: this field is not always filled in (UFO)! */ unsigned short gso_segs;//GSo的长度是gso_size的倍数,即用gso_size 来分割大段时产生的段数 unsigned short gso_type;// skb 中数据支持的GSO类型 struct sk_buff *frag_list; struct skb_shared_hwtstamps hwtstamps; u32 tskey; __be32 ip6_frag_id; /* * Warning : all fields before dataref are cleared in __alloc_skb() */ atomic_t dataref; /* Intermediate layers must ensure that destructor_arg * remains valid until skb destructor */ void * destructor_arg; /* must be last field, see pskb_expand_head() */ skb_frag_t frags[MAX_SKB_FRAGS]; };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
2021-04-22 摄像头驱动--mmap