以前有过一篇TSO-GSO文章;目前再来回顾一下:
TSO与GSO的重要区别
1, TSO只有第一个分片有TCP头和IP头,接着的分段只有IP头。意味着第一个分段丢失,所有分段得重传。
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];
};
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子