Linux GSO逻辑分析
Linux GSO逻辑分析
——lvyilong316(转载请注明出处)(注:对应linux kernel 代码为linux 2.6.32)
GSO用来扩展之前的TSO,目前已经并入upstream内核。TSO只能支持tcp协议,而GSO可以支持tcpv4,tcpv6, udp等协议。在GSO之前,skb_shinfo(skb)有两个成员ufo_size, tso_size,分别表示udpfragmentation offloading支持的分片长度,以及tcp segmentation offloading支持的分段长度,现在都用skb_shinfo(skb)->gso_size代替。
skb_shinfo(skb)->ufo_segs,skb_shinfo(skb)->tso_segs也被替换成了skb_shinfo(skb)->gso_segs,表示分片的个数。
gso用来delay 大包的分片,所以一直到dev_hard_start_xmit函数才会调用到。
l dev_hard_start_xmit
- int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
- struct netdev_queue *txq)
- {
- const struct net_device_ops *ops = dev->netdev_ops;
- int rc;
- if (likely(!skb->next)) {
- if (!list_empty(&ptype_all))
- dev_queue_xmit_nit(skb, dev);
- //判断网卡是否需要协议栈负责gso
- if (netif_needs_gso(dev, skb)) {
- //真正负责GSO操作的函数
- if (unlikely(dev_gso_segment(skb)))
- goto out_kfree_skb;
- if (skb->next)
- goto gso;
- }
- //……
- gso:
- do {
- //指向GSO分片后的一个skb
- struct sk_buff *nskb = skb->next;
- skb->next = nskb->next;
- nskb->next = NULL;
- if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
- skb_dst_drop(nskb);
- //将通过GSO分片后的包逐个发出
- rc = ops->ndo_start_xmit(nskb, dev);
- if (unlikely(rc != NETDEV_TX_OK)) {
- nskb->next = skb->next;
- skb->next = nskb;
- return rc;
- }
- txq_trans_update(txq);
- if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
- return NETDEV_TX_BUSY;
- } while (skb->next);
- skb->destructor = DEV_GSO_CB(skb)->destructor;
- out_kfree_skb:
- kfree_skb(skb);
- return NETDEV_TX_OK;
- }
那是不是所有skb在发送时都要经过GSO的逻辑呢?显然不是,只有通过netif_needs_gso判断才会进入GSO的逻辑,下面我们看下netif_needs_gso是如何判断的。
- static inline int netif_needs_gso(struct net_device *dev, struct sk_buff *skb)
- {
- return skb_is_gso(skb) &&
- (!skb_gso_ok(skb, dev->features) ||
- unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
- }
注意这里最后用了一个unlikely,因为如果通过前面的判断,说明网卡是支持GSO的,而一般网卡支持GSO也就会支持CHECKSUM_PARTIAL。进入GSO处理的第一个前提是skb_is_gso函数返回真,看下skb_is_gso的逻辑:
- static inline int skb_is_gso(const struct sk_buff *skb)
- {
- return skb_shinfo(skb)->gso_size;
- }
skb_is_gso的逻辑很简单,返回skb_shinfo(skb)->gso_size,所以进入GSO处理逻辑的必要条件之一是skb_shinfo(skb)->gso_size不为0,那么这个字段的含义是什么呢?gso_size表示生产GSO大包时的数据包长度,一般时mss的整数倍。下面看skb_gso_ok,如果这个函数返回False,就可以进入GSO处理逻辑。
- static inline int skb_gso_ok(struct sk_buff *skb, int features)
- {
- return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&
- (!skb_has_frags(skb) || (features & NETIF_F_FRAGLIST));
- }
skb_shinfo(skb)->gso_type包括SKB_GSO_TCPv4, SKB_GSO_UDPv4,同时NETIF_F_XXX的标志也增加了相应的bit,标识设备是否支持TSO, GSO, e.g.
- NETIF_F_TSO = SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT
- NETIF_F_UFO = SKB_GSO_UDPV4 << NETIF_F_GSO_SHIFT
- #define NETIF_F_GSO_SHIFT 16
通过以上三个函数分析,以下三个情况需要协议栈负责GSO。

下面看GSO的协议栈处理逻辑,入口就是dev_gso_segment。
l dev_gso_segment
协议栈的GSO逻辑是在dev_gso_segment中进行的。这个函数主要完成对skb的分片,并将分片存放在原始skb的skb->next中,这也是GSO的主要工作。
- static int dev_gso_segment(struct sk_buff *skb)
- {
- struct net_device *dev = skb->dev;
- struct sk_buff *segs;
- int features = dev->features & ~(illegal_highdma(dev, skb) ?
- NETIF_F_SG : 0);
- segs = skb_gso_segment(skb, features);
- /* Verifying header integrity only. */
- if (!segs)
- return 0;
- if (IS_ERR(segs))
- return PTR_ERR(segs);
- skb->next = segs;
- DEV_GSO_CB(skb)->destructor = skb->destructor;
- skb->destructor = dev_gso_skb_destructor;
- return 0;
- }
主要分片逻辑由skb_gso_segment来处理,这里我们主要看下析构过程,此时skb经过分片之后已经是一个skb list,通过skb->next串在一起,此时把初始的skb->destructor函数存到skb->cb中,然后把skb->destructor变更为dev_gso_skb_destructor。dev_gso_skb_destructor会把skb->next一个个通过kfree_skb释放掉,最后调用DEV_GSO_CB(skb)->destructor,即skb初始的析构函数做最后的清理。
l skb_gso_segment
这个函数将skb分片,并返回一个skb list。如果skb不需要分片则返回NULL。
- struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
- {
- struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
- struct packet_type *ptype;
- __be16 type = skb->protocol;
- int err;
- skb_reset_mac_header(skb);
- skb->mac_len = skb->network_header - skb->mac_header;
- __skb_pull(skb, skb->mac_len);
- //如果skb->ip_summed 不是 CHECKSUM_PARTIAL,那么报个warning,因为GSO类型的skb其ip_summed一般都是CHECKSUM_PARTIAL
- if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
- struct net_device *dev = skb->dev;
- struct ethtool_drvinfo info = {};
- WARN(……);
- if (skb_header_cloned(skb) &&
- (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
- return ERR_PTR(err);
- }
- rcu_read_lock();
- list_for_each_entry_rcu(ptype,&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
- if (ptype->type == type && !ptype->dev && ptype->gso_segment) {
- if (unlikely(skb->ip_summed != CHECKSUM_P