关于网卡驱动的发送函数调用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()》

原文地址:https://www.cnblogs.com/normalmanzhao2003/p/11349372.html

时间: 08-13

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

Linux网卡驱动架构分析

一.网卡驱动架构 由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动. 二.重要数据结构 1.Linux内核中每一个网卡由一个net_device结构来描述. 2.网卡操作函数集:net_device_ops,这个数据结构是上面net_device的一个成员. 3.网络数据包:sk_buff. 三.网卡驱动代码分析 所用文件为cs89x0.c,主要分析三个部分:网卡初始化.发送数据.接收数据. ㈠网卡初始化 网卡驱动初始化主要在函数init_module

【驱动】网卡驱动·linux内核网络分层结构

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://liucw.blog.51cto.com/6751239/1221140 Preface   Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护. 内核网络结构 在Linux内核中,对网络部分按照网络协议层.网络设备层.设备驱动功能层

网卡驱动

Platform架构的驱动程序便于移植和管理 易变得内容都放在了device Driver如果需要使用这些易变得内容,统统从device获得. ------- 网卡设备驱动 1.内核中关于网卡设备相关的框架 网卡设备驱动工作于网络接口层 1.1核心数据结构:structnet_device{ base_addr   I/O基地址 Irq /**/中断号 Net_device_ops  /*网卡设备操作函数集合*/ { Ndo_start_xmit  发送函数 } Alloc_etherdev(.

[国嵌攻略][136][DM9000网卡驱动深度分析]

网卡初始化 1.分配描述结构,alloc_etherdev 2.获取平台资源,platform_get_resource 2.1.在s3c_dm9k_resource中有相关的资源 2.2.add地址由CS4和ADD2决定,是20000000 2.3.dat地址由CS4和ADD2决定,是20000004 2.4.中断资源是EINT7 3.虚拟地址映射,ioremap 4.读取芯片类型 5.设置操作函数集 6.读取MAC地址 7.注册网卡驱动,register_netdev 8.启动发送队列,ne

Linux 网卡驱动学习之(八)(基于 MAC 地址转发数据)

1.构建MAC地址表 交换机技术在转发数据前必须知道它的每一个端口所连接的主机的MAC地址,构建出一个MAC地址表.当交换机从某个端口收到数据帧后,读取数据帧中封装的目的地MAC地址信息,然后查阅事先构建的MAC地址表,找出和目的地地址相对应的端口,从该端口把数据转发出去,其他端口则不受影响,这样避免了与其它端口上的数据发生碰撞.因此构建MAC地址表是交换机的首要工作.下面举例说明交换机建立地址表的过程. [分析]假设主机A向主机C发送一个数据帧(每一个数据帧中都包含有源MAC地址和目的MAC地

Linux 网卡驱动学习(net_device 等数据结构)

[摘要]前文对网络驱动例子进行一个简单的梳理总结,本文贴出 net_device 的数据结构以及一些驱动中常用的数据结构. 1.网络设备驱动结构 下图摘自http://blog.chinaunix.net/uid-20672257-id-3147768.html 1).网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据.这一层的存在使得上层协议独立于具体的设备. 2).网

Linux e1000e网卡驱动

目录 识别网卡 命令行参数 附加配置 技术支持 一.识别网卡e1000e驱动支持Intel所有的GbE PCIe网卡,除了82575,82576,基于82580系列的网卡.提示:Intel(R) PRO/1000 P Dual网卡是支持e1000,但不支持e1000e,因为82546部分支持PCIe. 更多信息关于怎么识别你的网卡,去官网的 Adapter & Driver ID Guide: http://support.intel.com/support/go/network/adapter

SylixOS网卡驱动实现篇

1. 开发环境  操作系统:SylixOS 编程环境:RealEvo-IDE3.1 硬件平台:IMX6Q实验箱 2. 技术实现 网卡驱动的收发功能,是通过管理收发描述符的方式实现的.因此,在MAC初始化的时候需要对描述符也进行相应的初始化操作.初始化内容会因CPU的不同而有所区别.当描述符初始化完毕之后,就可以用他们来进行网络报文的收发. 2.1 网络发送函数的实现 网络驱动的发送函数通过enetCoreTx函数实现,具体实现如程序清单 2-1. 程序清单 2-1 发送函数  /********

linux下安装编译网卡驱动的方法

安装linux操作系统后发现没有网卡驱动,表现为 system → Administration → Network下Hardware列表为空. 以下为安装编译网卡驱动的过程,本人是菜鸟,以下是我从网上找的资料进行整理,并实际操作的过程,仅供借鉴.  一.检测linux系统内核版本和网卡类型,相关命令如下: uname -r                    查看linux内核版本 (uname -a 可显示所有信息)lsmod                        设备加载情况 l

VMware 升级esxi网卡驱动的几个相关命令

1.将下载的驱动上传到要升级的esxi storage 2.开启esxi主机的SSH功能 3. 列出当前ESXi主机上所有NICs的状态 esxcli network nic list 4.查看网卡驱动版本及固件版本 esxcli network nic get -n vmnic2 Advertised Auto Negotiation: false Advertised Link Modes: 10000baseT/Full Auto Negotiation: false Cable Type