关于网卡驱动的发送函数调用dev_kfree_skb的简析

发布时间:2024-07-12  

一、问题的由来


1、现象


    在linux4.3.2 的网卡驱动程序cs89x0.c的net_send_packet()里,有:


  1 /* Test to see if the chip has allocated memory for the packet */

  2 if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == 0) {

  3 /*

  4 * Gasp!  It hasn't.  But that shouldn't happen since

  5 * we're waiting for TxOk, so return 1 and requeue this packet.

  6 */

  7 

  8 spin_unlock_irq(&lp->lock);

  9 if (net_debug) printk("cs89x0: Tx buffer not free!n");

 10 //当检测到网卡暂时无法发送数据时,会直接return 1,而没有调dev_kfree_skb (skb)。

 11 return 1;//这里似乎应该return NETDEV_TX_BUSY,理由见:注

 12 }

 13 /* Write the contents of the packet */

 14 writewords(dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1);

 15 spin_unlock_irq(&lp->lock);

 16 lp->stats.tx_bytes += skb->len;

 17 dev->trans_start = jiffies;

 18 dev_kfree_skb (skb); //当发送完数据后,会释放掉skb

 19 


2、提出的疑问


问题1) 为什么当发送完数据后,要由网卡驱动(而不是上层函数)释放掉skb?


问题2) 为什么当检测到网卡暂时无法发送数据时,却没有调dev_kfree_skb (skb)?



二、背景知识


1、TCP的分段(TCP Segmentation)以及IP的分片(IP Fragmentation)


    简言之,就是TCP会对传输的数据报大小有个限制(称为MSS)。如果超出了此限制,则会把数据包分段传输。IP分片的概念与此类似。


2、TCP的出错重传机制


    TCP是可靠的数据传输协议,所以为了保证所传数据的正确性和完整性,会对出错(出错的原因包括网卡故障等)的数据报进行重传。并且TCP在检测到出错(包括一个分段出错)后会重发整个TCP报文段(因为TCP数据报分段依赖于下层IP层的数据包,而IP层没有出错重传机制)。



三、结合背景知识,解答疑问(不一定完全正确)


1、对问题1的解答


    我推测原因之一(可能还有其它更深层的原因)是:在内核里,除了dev_hard_start_xmit外,还有其它地方也会调网卡驱动的发送函数,所以如果把释放skb的工作从驱动层提升到上层的话,会造成不必要的代码膨胀。


2、对问题2的解答


    根据内核代码对于dev_kfree_skb(其实是consume_skb的包装)的注释:


  1 /**

  2  * consume_skb - free an skbuff

  3  * @skb: buffer to free

  4  *

  5  * Drop a ref to the buffer and free it if the usage count has hit zero

  6  * Functions identically to kfree_skb, but kfree_skb assumes that the frame

  7  * is being dropped after a failure and notes that

  8  */

  9 

大致意思是:发送成功的数据,称为被consumed(消费)掉了,而发送失败的数据,称为被dropped(丢弃)掉了。所以当网卡发送数据失败时,不应该调dev_kfree_skb。


(那么问题来了,既然不能调dev_kfree_skb,那就调kfree_skb也可以释放skb啊?但驱动里为啥不释放skb,而是直接return 1了呢?看来故事还没完。。。)


3、进一步的分析(可结合下文对dev_hard_start_xmit的注释来看)


    由于协议栈所传输的数据,可能是分段(或者分片)的数据,这些数据在内核里,会通过skb_segment()把对应的skb进行分片处理(简单的说,会把skb分片成许多nskb,然后通过next、prev指针链接成一个链表,而表头就是那个被分片的skb),然后经过九转十八弯,最终调用dev_hard_start_xmit,它会在while循环里遍历skb链表里所有的分片,调用底层的网卡驱动的net_device_ops.ndo_start_xmit发送这些分片。


    如果由于网卡故障导致分片发送不成功(这时驱动应该返回不等于NETDEV_TX_BUSY和NETDEV_TX_LOCKED的非零值 注),则dev_hard_start_xmit会退出while循环,然后调用kfree_skb,释放掉整个skb链表(因为这种情况下,上层要重传整个数据包而不是单个分片)。因此,网卡驱动里不需要单独释放分片。


    而如果由于其它原因(比如网卡发送忙)导致发送不成功(这时驱动应该返回NETDEV_TX_BUSY或者NETDEV_TX_LOCKED 注),则dev_hard_start_xmit会保留此分片下次重试,所以网卡驱动就不应该过早的释放此分片。


    综合以上两种情况,得出结论是:当网卡发送不成功时,不要释放skb。


注:根据netdevice.h里对于ndo_start_xmit的注释:


  1 /* netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,

  2  *                               struct net_device *dev);

  3  * Called when a packet needs to be transmitted.

  4  * Must return NETDEV_TX_OK , NETDEV_TX_BUSY.

  5  *        (can also return NETDEV_TX_LOCKED iff NETIF_F_LLTX)

  6  * Required can not be NULL.

  7 */ 


可以看出:

- 如果网卡发送忙,则应该返回NETDEV_TX_BUSY,以保留此分片待下次重传

- 如果网卡故障,则可以返回不等于NETDEV_TX_BUSY和NETDEV_TX_LOCKED的 非零值(从下文对dev_hard_start_xmit的注释可知,这样会使整个数

  据包被释放掉,下次会重传整个数据包)


4、对dev_hard_start_xmit的注释


  1 int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq)

  2 {

  3 const struct net_device_ops *ops = dev->netdev_ops;

  4 int rc = NETDEV_TX_OK;

  5 unsigned int skb_len;

  6 if (likely(!skb->next)) { //如果skb没有被分段

  7 netdev_features_t features;

  8 /*

  9 * If device doesn't need skb->dst, release it right now while

 10 * its hot in this cpu cache

 11 */

 12 if (dev->priv_flags & IFF_XMIT_DST_RELEASE)

 13 skb_dst_drop(skb);

 14 /* 这里把将要发给驱动的数据包提供给上层嗅探程序进行分析监控 */

 15 if (!list_empty(&ptype_all))

 16 dev_queue_xmit_nit(skb, dev);

 17 skb_orphan_try(skb);  //孤儿化skb? 作用不明

 18 features = netif_skb_features(skb);

 19 /* 上层协议要求驱动进行VLAN插入加速,但是当前网络设备不支持该功能时,则需要手动完成数据包的VLAN字段插入 */

 20 if (vlan_tx_tag_present(skb) &&

 21     !(features & NETIF_F_HW_VLAN_TX)) {

 22 skb = __vlan_put_tag(skb, vlan_tx_tag_get(skb));

 23 if (unlikely(!skb))

 24 goto out;

 25 skb->vlan_tci = 0;

 26 }

 27 /* 如果上层协议需要底层驱动执行数据包硬件分片操作,但是底层硬件不支持该功能时,则需要手动完成分片操作 */

 28 if (netif_needs_gso(skb, features)) {

 29 if (unlikely(dev_gso_segment(skb, features)))

 30 goto out_kfree_skb;

 31 if (skb->next)

 32 goto gso;

 33 } else {

 34 /* 如果硬件不支持分离集合DMA操作而SKB带有非线性碎片数据的话,则需要对数据进行线性化拼接操作 */

 35 if (skb_needs_linearize(skb, features) &&

 36     __skb_linearize(skb))

 37 goto out_kfree_skb;

 38 /* If packet is not checksummed and device does not

 39 * support checksumming for this protocol, complete

 40 * checksumming here.

 41  * 上层协议要求底层计算校验,而底层硬件不支持校验时,需要手动计算校验值

 42 */

 43 if (skb->ip_summed == CHECKSUM_PARTIAL) {

 44 skb_set_transport_header(skb,

 45 skb_checksum_start_offset(skb));

 46 if (!(features & NETIF_F_ALL_CSUM) &&

 47      skb_checksum_help(skb))

 48 goto out_kfree_skb;

 49 }

 50 }

 51 skb_len = skb->len;

 52 /* 调用网卡驱动的ndo_start_xmit函数(即cs89x0.c的net_send_packet) */

 53 rc = ops->ndo_start_xmit(skb, dev);

 54 trace_net_dev_xmit(skb, rc, dev, skb_len);

 55 /*如果发送成功,则更新发送队列的统计信息,然后返回*/

 56 if (rc == NETDEV_TX_OK)

 57 txq_trans_update(txq);

 58 return rc;

 59 }//eo if(likely(!skb->next))

 60 //如果skb是被分段的话

 61 gso:

 62 do {

 63    /* 从skb链表中取出一个nskb */

 64 struct sk_buff *nskb = skb->next;

 65 skb->next = nskb->next;

 66 nskb->next = NULL;

 67 /*

 68 * If device doesn't need nskb->dst, release it right now while

 69 * its hot in this cpu cache

 70 */

 71 if (dev->priv_flags & IFF_XMIT_DST_RELEASE)

 72 skb_dst_drop(nskb);

 73 skb_len = nskb->len;

 74 /* 对此nskb调用网卡驱动的ndo_start_xmit函数 */

 75 rc = ops->ndo_start_xmit(nskb, dev);

 76 trace_net_dev_xmit(nskb, rc, dev, skb_len);

 77 /* 如果发送不成功的话 */

 78 if (unlikely(rc != NETDEV_TX_OK)) {

 79 /* 如果是因为网卡故障导致发送不成功,则跳出循环,释放掉整个skb链表*/

 80 if (rc & ~NETDEV_TX_MASK)

 81     goto out_kfree_gso_skb;

 82 /* 如果是其它原因(比如网卡忙,暂时无法发送),则nskb留待下次重试,然后直接返回*/

 83     nskb->next = skb->next;

 84     skb->next = nskb;

 85     return rc;

 86 }

 87 txq_trans_update(txq);

 88 /*如果数据包未全部发送完就停止了(比如可能上层协议栈暂停了发送),则直接返回,剩下未发送的分片下次再试 */

 89 if (unlikely(netif_xmit_stopped(txq) && skb->next))

 90 return NETDEV_TX_BUSY;

 91 } while (skb->next);

 92 out_kfree_gso_skb:

 93 if (likely(skb->next == NULL))

 94 skb->destructor = DEV_GSO_CB(skb)->destructor;

 95 /* 如果是分片的skb,且全部发送成功,或者网卡故障导致分片未发送成功,则释放掉整个skb */

 96 out_kfree_skb:

 97 kfree_skb(skb);

 98 out:

 99 return rc;

100 }


四、结论


    sk_buff作为贯穿Linux网络系统的数据载体,会牵扯到许多其它的网络子模块,所以它的生命周期管理非常复杂,内核有既定的套路。不能简单的套用“谁分配谁释放”。


    对于sk_buff的理解,如果只聚焦到网卡驱动这块,很可能会没有头绪,需要我们把视野扩大到其它网络子模块,甚至从网络系统整体的角度来理解(当然代价是时间和精力 )。


   话说回来,对于驱动工程师来说,只要按照套路把驱动给整出来不就行了,需要这么费劲的去理解这些原理么?可能是吧,不好说。。。


五、参考资料


1、《TCP分段与IP分片》


2、《consume_skb 和 kfree_skb的区别》


3、《linux tcp GSO和TSO实现》


4、《网络数据包发送之dev_hard_start_xmit()》


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>