网络驱动学习杂记

  • 网络驱动学习杂记已关闭评论
  • 28 次浏览
  • A+
所属分类:linux技术
摘要

不带选项,默认输出协商速率、最大速率、连接状态等信息-i | –driver 打印驱动信息


ethtool

ethtool的使用

不带选项,默认输出协商速率、最大速率、连接状态等信息

-i | --driver 打印驱动信息

--set-priv-flags 设置网卡的私有属性,比如将link-down-on-close置为true后可以使用ifconfig down去关闭网卡连接

-a |--show-pause 查看以太网是否启用暂停帧(Pause Frame),暂停帧主要用于MAC层的流控。

-A |--pause DEVNAME 设置tx、rx和自动协商模式是否开启暂停帧

-K 是否开启TCP的offload机制

-m 获取网卡的EEPROM信息

ethtool数据获取流程

ethtool和内核的通信流程如下图所示:

网络驱动学习杂记

ethtool使用ioctl去操作网卡,需要指定ioctl的request code为SIOCETHTOOL。可以对网卡进行的操作都定义在ethtool.h中,常见的操作码有

ETHTOOL_GLINKSETTINGS(获取link状态,协商速率)、ETHTOOL_GDRVINFO(获取驱动信息)。用户和内核使用struct ifreq交换通信数据,

在用户态,ethtool的主要工作是

1、构造传入内核的结构体,在源码中使用了自定义的命令格式去封装了和内核一致的结构体,比如struct ethtool_link_settings,这个结构体在ethtool源码和内核的include/uapi/linux/ethtool.h中都存在一份。给struct ifreq的ifru_data赋值为struct ecmd

2、调用ioctl,把struct ifreq传入内核

3、解析获取的数据,构造命令输出

在内核,ethtool的主要经过了通用层和驱动层,其中一些操作能够在通用层完成,不能在通用层完成,就继续把请求传递到驱动层。net_device中的ethtool_ops包含了所有能够通过ethtool执行的操作,这个结构体在内核模块插入的时候通过 SET_ETHTOOL_OPS 进行初始化。

//用户态主要代码 int ioctl(int fd, unsigned long request, ...);  struct { 	struct ethtool_link_settings req; 	__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; } ecmd;   struct ethtool_value { 	__u32	cmd; 	__u32	data; };  //内核态主要代码 struct ifreq { 	void __user *	ifru_data; }   struct ethtool_ops { 	int	(*get_settings)(struct net_device *, struct ethtool_cmd *); 	int	(*set_settings)(struct net_device *, struct ethtool_cmd *); 	void	(*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *); 	int	(*get_regs_len)(struct net_device *); 	void	(*get_regs)(struct net_device *, struct ethtool_regs *, void *); 	void	(*get_wol)(struct net_device *, struct ethtool_wolinfo *); 	int	(*set_wol)(struct net_device *, struct ethtool_wolinfo *); 	u32	(*get_msglevel)(struct net_device *); 	void	(*set_msglevel)(struct net_device *, u32); 	int	(*nway_reset)(struct net_device *); 	u32	(*get_link)(struct net_device *); 	int	(*get_eeprom_len)(struct net_device *); 	int	(*get_eeprom)(struct net_device *, 			      struct ethtool_eeprom *, u8 *); 	int	(*set_eeprom)(struct net_device *, 			      struct ethtool_eeprom *, u8 *); 	int	(*get_coalesce)(struct net_device *, struct ethtool_coalesce *); 	int	(*set_coalesce)(struct net_device *, struct ethtool_coalesce *); 	void	(*get_ringparam)(struct net_device *, 				 struct ethtool_ringparam *); 	int	(*set_ringparam)(struct net_device *, 				 struct ethtool_ringparam *); 	void	(*get_pauseparam)(struct net_device *, 				  struct ethtool_pauseparam*); 	int	(*set_pauseparam)(struct net_device *, 				  struct ethtool_pauseparam*); 	void	(*self_test)(struct net_device *, struct ethtool_test *, u64 *); 	void	(*get_strings)(struct net_device *, u32 stringset, u8 *); 	int	(*set_phys_id)(struct net_device *, enum ethtool_phys_id_state); 	void	(*get_ethtool_stats)(struct net_device *, 				     struct ethtool_stats *, u64 *); 	int	(*begin)(struct net_device *); 	void	(*complete)(struct net_device *); 	u32	(*get_priv_flags)(struct net_device *); 	int	(*set_priv_flags)(struct net_device *, u32); 	int	(*get_sset_count)(struct net_device *, int); 	int	(*get_rxnfc)(struct net_device *, 			     struct ethtool_rxnfc *, u32 *rule_locs); 	int	(*set_rxnfc)(struct net_device *, struct ethtool_rxnfc *); 	int	(*flash_device)(struct net_device *, struct ethtool_flash *); 	int	(*reset)(struct net_device *, u32 *); 	u32	(*get_rxfh_key_size)(struct net_device *); 	u32	(*get_rxfh_indir_size)(struct net_device *); 	int	(*get_rxfh)(struct net_device *, u32 *indir, u8 *key, 			    u8 *hfunc); 	int	(*set_rxfh)(struct net_device *, const u32 *indir, 			    const u8 *key, const u8 hfunc); 	int	(*get_rxfh_context)(struct net_device *, u32 *indir, u8 *key, 				    u8 *hfunc, u32 rss_context); 	int	(*set_rxfh_context)(struct net_device *, const u32 *indir, 				    const u8 *key, const u8 hfunc, 				    u32 *rss_context, bool delete); 	void	(*get_channels)(struct net_device *, struct ethtool_channels *); 	int	(*set_channels)(struct net_device *, struct ethtool_channels *); 	int	(*get_dump_flag)(struct net_device *, struct ethtool_dump *); 	int	(*get_dump_data)(struct net_device *, 				 struct ethtool_dump *, void *); 	int	(*set_dump)(struct net_device *, struct ethtool_dump *); 	int	(*get_ts_info)(struct net_device *, struct ethtool_ts_info *); 	int     (*get_module_info)(struct net_device *, 				   struct ethtool_modinfo *); 	int     (*get_module_eeprom)(struct net_device *, 				     struct ethtool_eeprom *, u8 *); 	int	(*get_eee)(struct net_device *, struct ethtool_eee *); 	int	(*set_eee)(struct net_device *, struct ethtool_eee *); 	int	(*get_tunable)(struct net_device *, 			       const struct ethtool_tunable *, void *); 	int	(*set_tunable)(struct net_device *, 			       const struct ethtool_tunable *, const void *); 	int	(*get_per_queue_coalesce)(struct net_device *, u32, 					  struct ethtool_coalesce *); 	int	(*set_per_queue_coalesce)(struct net_device *, u32, 					  struct ethtool_coalesce *); 	int	(*get_link_ksettings)(struct net_device *, 				      struct ethtool_link_ksettings *); 	int	(*set_link_ksettings)(struct net_device *, 				      const struct ethtool_link_ksettings *); 	int	(*get_fecparam)(struct net_device *, 				      struct ethtool_fecparam *); 	int	(*set_fecparam)(struct net_device *, 				      struct ethtool_fecparam *); 	void	(*get_ethtool_phy_stats)(struct net_device *, 					 struct ethtool_stats *, u64 *); }; 

数据收发流程

包传输流程

在网卡层面,数据的传输使用transmission(通信领域的专有名词)表达。驱动的包传输过程,宏观上讲就是把内核协议栈构造好的IP报文向下传递给驱动层,然后

再次封装成以太网帧并通过介质传播出去的过程。封装以太网帧头的过程通过dev_hard_header调用header_ops->create完成(在协议栈层),传输过程在驱动

中完成。

启动传输的接口时net_device中的ndo_start_xmit(老版本是hard_start_xmit)。

netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev); 

这个接口需要由驱动程序进行实现,通用的实现逻辑是:

1、给skb->data填充padding

2、设置dev->trans_start

3、设备的私有数据引用skb

4、执行设备相关的发送逻辑(设置寄存器等)

sk_buff

struct sk_buff是整个内核网络数据交换、传输的核心数据结构。

网络驱动学习杂记

重要属性

struct sk_buff {     //发送或者接收该BUFFER的设备 	struct net_device *dev;           //操作buffer的指针     unsigned char *head;      unsigned char *data;     unsigned char *tail;     unsigned char *end;          //len是整个sk_buff的长度,等于tail-head;     //data_len是sk_buff存储的有效数据的长度,等于end-data     unsigned int len; 	unsigned int data_len; }; 

重要方法

skb_put给buffer尾部增加数据

skb_push给buffer头部增加数据

skb_pull删除buffer头部的数据

skb_reserve给buffer头部预留len字节的空间

unsigned char *skb_put(struct sk_buff *skb, int len); unsigned char *skb_push(struct sk_buff *skb, int len); unsigned char *skb_pull(struct sk_buff *skb, int len); void skb_reserve(struct sk_buff *skb, int len); 

网络驱动学习杂记

流量控制

网卡的传播队列大小有限,当队列达到上限的时候,驱动需要通知上层不要暂停发送新的数据包。这个工作由netif_stop_queue完成。

需要注意的是,如果在stop队列之后,在之后的某个时间点(具体这个时间点怎么确定是设备相关的)需要重启传播队列。重启队列使用netif_wake_queue完成。netif_tx_disable 主要在ndo_start_xmit之外的场景使用,它可以保证在disable函

数生效之后,内核不会在别的CPU上再次调用ndo_start_xmit使得disable失效。

static inline void netif_stop_queue(struct net_device *dev); static inline void netif_wake_queue(struct net_device *dev); static inline void netif_tx_disable(struct net_device *dev) 

数据包传播超时

当系统流量比较大时,数据包的发送可能会超时,当数据包传播超时的时候会调用net_device->ndo_tx_timeout函数。在ndo_tx_timeout函数中,一般会执行一下操作:

1、补全缺失的中断

2、重启传播队列:netif_wake_queue

包接收流程

驱动接收包的过程是从PHY接收数据然后构造成sk_buf传递给上层的过程。接收包的方式有中断和NAPI两种方式,NAPI 是 Linux 上采用的一种提高网络处理效率

的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据。

常规的中断处理程序流程:

1、给设备加锁

2、从寄存器获取中断状态

3、判断中断类型,tx中断还是rx中断

4、释放设备锁和sk_buffer占用的资源

/*  * The typical interrupt entry point  */ static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs) { 	int statusword; 	struct snull_priv *priv; 	struct snull_packet *pkt = NULL;  	struct net_device *dev = (struct net_device *)dev_id;  	/* Lock the device */ 	priv = netdev_priv(dev); 	spin_lock(&priv->lock);      	/* retrieve statusword: real netdevices use I/O instructions */ 	statusword = priv->status; 	priv->status = 0; 	if (statusword & SNULL_RX_INTR) { 		/* send it to snull_rx for handling */ 		pkt = priv->rx_queue; 		if (pkt) { 			priv->rx_queue = pkt->next; 			snull_rx(dev, pkt); 		} 	} 	if (statusword & SNULL_TX_INTR) { 		/* a transmission is over: free the skb */ 		priv->stats.tx_packets++; 		priv->stats.tx_bytes += priv->tx_packetlen; 		dev_kfree_skb(priv->skb); 	}  	/* Unlock the device and we are done */ 	spin_unlock(&priv->lock); 	if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */ 	return; } 

中断处理函数

接受包的中断处理

1、从接收队列中获取一个包

2、创建一个skb_buff,长度是datalen+2,2是给预留空间的

3、调用skb_reserve(2),给skb_buff中的data在头部预留2两个字节。以太网头部是14字节,保留两个字节就IP头部的起始地址就刚好16字节对齐。

​ 对齐之后,可以减少CPU访问硬件Cache的次数。

CPUs often take a performance hit when accessing unaligned memory locations. The actual performance hit varies, it can be small if the hardware

handles it or large if we have to take an exception and fix it in software. Since an ethernet header is 14 bytes network drivers often end up with the

IP header at an unaligned offset. The IP header can be aligned by shifting the start of the packet by 2 bytes. Drivers should do this with

skb_reserve(skb, NET_IP_ALIGN);

3、并将上一步获取到的包拷贝到skb中

4、设置skb结构

(1)dev:表示该包是由那个设备接收的

(2)protocol: sk_buff的protocol字段是一个 __be16 类型的变量,表示每一层所用的协议,并且每一层该字段的值都不同。在驱动中使用帮助函数

​ eth_type_trans去给该字段赋值。

skb->protocol = eth_type_trans(skb, dev); 

(3)ip_summed:该字段表示驱动接收的包是否经过校验。可以取的值有:

CHECKSUM_HW : 表示包已经有硬件做过校验,上层(网络层)可以跳过校验。

CHECKSUM_NONE : 表示包没有做过校验,上层需要做校验和

CHECKSUM_UNNECESSARY:表示不执行校验,loopback设备执行这个选项

(4)更新统计数据,stats.rx_packets加1,stats.rx_bytes增加skb_buff中有效数据的长度

5、调用 netif_rx 通知内核已经接收了一个数据包并构造成了sk_buff,值得注意的是这是一个异步的过程

void netif_rx(struct sk_buff *skb);   

包传播成功的中断处理

在数据包成功从网卡传播到介质上时,网卡会发送一个成功的中断信号,对于这种中断的处理比较简单,主要的工作是

1、更新统计数据

2、释放给sk_buff分配的空间

NAPI

NAPI是为了针对传统中断在处理大流量场景时,占用CPU太多导致的网络吞吐慢的问题,而提出的一种新的包接收模式。其核心思想是只使用中断去通知CPU一个包接收阶段的开始,一旦开始真正进行包传输就切换到轮询(POLL)模式,轮询在大流量(批处理)场景由于不涉及到中断,具备较高的数据吞吐。

使用NAPI,我们需要在网卡驱动初始化的时候,初始化napi_struct结构。所有

/*  * Structure for NAPI scheduling similar to tasklet but with weighting  */ struct napi_struct { 	/* The poll_list must only be managed by the entity which 	 * changes the state of the NAPI_STATE_SCHED bit.  This means 	 * whoever atomically sets that bit can add this napi_struct 	 * to the per-CPU poll_list, and whoever clears that bit 	 * can remove from the list right before clearing the bit. 	 */ 	struct list_head	poll_list;  	unsigned long		state; 	int			weight; 	unsigned long		gro_bitmask; 	int			(*poll)(struct napi_struct *, int); #ifdef CONFIG_NETPOLL 	int			poll_owner; #endif 	struct net_device	*dev; 	struct gro_list		gro_hash[GRO_HASH_BUCKETS]; 	struct sk_buff		*skb; 	struct hrtimer		timer; 	struct list_head	dev_list; 	struct hlist_node	napi_hash_node; 	unsigned int		napi_id; }; 

使用NAPI的中断处理函数:

1、关闭中断,停止接收新的中断

2、使用napi_schedule调度poll函数执行

一个典型的poll函数的流程

1、获取设备私有数据结构

struct snull_priv { 	struct net_device_stats stats; 	int status; 	struct snull_packet *ppool; 	struct snull_packet *rx_queue; 	int rx_int_enabled; 	int tx_packetlen; 	u8 *tx_packetdata; 	struct sk_buff *skb; 	spinlock_t lock; }; 

2、loop接收队列,当接收的包超过系统的budget的时候退出循环

3、loop内逻辑
(1)从队列中获取包
(2)和常规中断一致的流程

源码

__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev) { 	unsigned short _service_access_point; 	const unsigned short *sap; 	const struct ethhdr *eth;  	skb->dev = dev; 	skb_reset_mac_header(skb);  	eth = (struct ethhdr *)skb->data; 	skb_pull_inline(skb, ETH_HLEN);  	if (unlikely(is_multicast_ether_addr_64bits(eth->h_dest))) { 		if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast)) 			skb->pkt_type = PACKET_BROADCAST; 		else 			skb->pkt_type = PACKET_MULTICAST; 	} 	else if (unlikely(!ether_addr_equal_64bits(eth->h_dest, 						   dev->dev_addr))) 		skb->pkt_type = PACKET_OTHERHOST;  	/* 	 * Some variants of DSA tagging don't have an ethertype field 	 * at all, so we check here whether one of those tagging 	 * variants has been configured on the receiving interface, 	 * and if so, set skb->protocol without looking at the packet. 	 */ 	if (unlikely(netdev_uses_dsa(dev))) 		return htons(ETH_P_XDSA);  	if (likely(eth_proto_is_802_3(eth->h_proto))) 		return eth->h_proto;  	/* 	 *      This is a magic hack to spot IPX packets. Older Novell breaks 	 *      the protocol design and runs IPX over 802.3 without an 802.2 LLC 	 *      layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This 	 *      won't work for fault tolerant netware but does for the rest. 	 */ 	sap = skb_header_pointer(skb, 0, sizeof(*sap), &_service_access_point); 	if (sap && *sap == 0xFFFF) 		return htons(ETH_P_802_3);  	/* 	 *      Real 802.2 LLC 	 */ 	return htons(ETH_P_802_2); }    void snull_rx(struct net_device *dev, struct snull_packet *pkt) { 	struct sk_buff *skb; 	struct snull_priv *priv = netdev_priv(dev);  	/* 	 * The packet has been retrieved from the transmission 	 * medium. Build an skb around it, so upper layers can handle it 	 */ 	skb = dev_alloc_skb(pkt->datalen + 2); 	skb_reserve(skb, 2); /* align IP on 16B boundary */   	memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);  	/* Write metadata, and then pass to the receive level */ 	skb->dev = dev; 	skb->protocol = eth_type_trans(skb, dev); 	skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ 	priv->stats.rx_packets++; 	priv->stats.rx_bytes += pkt->datalen;      	     (skb); 	return; } 

参考

深入理解linux网络技术内幕

LDD3

https://sites.google.com/site/emmoblin/smp-yan-jiu/napi

https://blog.51cto.com/u_15127616/3436261