關於網卡驅動的發送函數調用dev_kfree_skb的簡析


一、問題的由來

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()》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM