1.Linux网络设备驱动的结构
与字符设备和块设备不同,网络设备并不对应于/dev目录下的文件,应用程序最终使用套接字完成与网络设备的接口。
Linux系统对网络设备驱动定义了4个层次,这4个层次为:
- 网络协议接口层:向网络层协议提供同一的数据包收发接口,无论是IP还是ARP,都是通过dev_queue_xmit()发送数据,通过netif_rx()接收数据
- 网络设备接口层:向网络协议层提供同一用于描述具体网络设备属性和操作的结构体net_device,此结构体是设备驱动功能层中各函数的容器
- 设备驱动功能层:这一层的各函数是网络设备接口层net_device数据结构体的具体成员,驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作
- 网络设备与媒介层:完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动驱动功能层中的函数在物理上驱动
在设计具体的网络设备驱动程序时,需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册入内核
1.1 网络协议接口层
网络接口协议层最主要的功能是給上层协议提供透明的数据包发送和接收接口。上层ARP协议或IP需要发送数据包时,调用dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个struct sk_buff数据结构的指针。
dev_queue_xmit()函数的原型:
int dev_queue_xmit(struct sk_buff *skb);
上层对数据包的接收也通过向netif_rx()函数传递一个struct sk_buff数据结构的指针来完成。netif_rx()函数的原型为:
int netif_rx(struct sk_buff *skb);
sk_buff含义为套接字缓冲区,定义于include/linux/skbuff.h文件中,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统的"中枢神经"。
当发送数据包时,Linux内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff递交到下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网卡接收到数据包后,它必须将接收到数据转换为sk_buff数据结构体并传递给上层,各层剥去相应的协议头直至交给用户。
sk_buff原型:
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
union {
struct net_device *dev;
/* Some protocols might use this space to store information,
* while device pointer would be NULL.
* UDP receive path is one user.
*/
unsigned long dev_scratch;
};
};
struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */
struct list_head list;
};
......
unsigned int len,
data_len;
__u16 mac_len,
hdr_len;
.....
__u32 priority;
int skb_iif;
__u32 hash;
__be16 vlan_proto;
__u16 vlan_tci;
....
union {
__be16 inner_protocol;
__u8 inner_ipproto;
};
__u16 inner_transport_header;
__u16 inner_network_header;
__u16 inner_mac_header;
__be16 protocol;
__u16 transport_header;
__u16 network_header;
__u16 mac_header;
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
...
};
head和end指向缓冲区的头部和尾部,而data和tail指向实际数据的头部和尾部,每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据
套接字缓冲区涉及的操作函数:
(1) 分配
分配套接字缓冲区的函数:
// 分配一个套接字缓冲区和一个数据缓冲区, 参数len为数据缓冲区的空间大小, 通常以L1_CACHE_BYTES字节(对于ARM为32) 对齐, 参数priority为内存分配的优先级
struct sk_buff *alloc_skb(unsigned int len, gfp_t priority);
// dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配, 原因是该函数经常在设备驱动的接收中断里被调用
struct sk_buff *dev_alloc_skb(unsigned int len);
(2) 释放
用于释放alloc_skb套接字缓冲区和数据缓冲区的函数有:
void kfree_skb(struct sk_buff *skb); // 一般在内核内部使用
void dev_kfree_skb(struct sk_buff *skb); // 用于非中断上下文
void dev_kfree_skb_irq(struct sk_buff *skb); // 用于中断上下文
void dev_kfree_skb_any(struct sk_buff *skb); // 在中断或非中断皆可采用(实际是在内部做了判断,分别调用dev_kfree_skb_irq和dev_kfree_skb)
(3) 变更
在缓冲区尾部增加数据
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
它会导致skb->tail后移len(skb->tail+=len) , 而skb->len会增加len的大小(skb->len+=len) 。 通常, 在设备驱动的接收数据处理中会调用此函数。
在缓冲区开头增加数据
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
它会导致skb->data前移len(skb->data-=len) , 而skb->len会增加len的大小(skb->len+=len) 。 与该函数的功能完成相反的函数是skb_pull() , 它可以在缓冲区开头移除数据, 执行的动作是skb->len-=len、skb->data+=len。
调整缓冲区的头部
static inline void skb_reserve(struct sk_buff *skb, int len);
它会将skb->data和skb->tail同时后移len, 执行skb->data+=len、 skb->tail+=len。
内核中的使用实例
skb=alloc_skb(len+headspace, GFP_KERNEL);
skb_reserve(skb, headspace);
skb_put(skb,len);
memcpy_fromfs(skb->data,data,len);
pass_to_m_protocol(skb);
先分配一个全新的sk_buff,接着调用skb_reserve() 腾出头部空间, 之后调用skb_put() 腾出数据空间, 然后把数据复制进来, 最后把sk_buff传给协议栈。
1.2 网络设备接口层
net_device结构体在内核中指代一个网络设备,它定义于include/linux/netdevice.h文件中,网络设备程序只需通过net_device的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。
net_device中包含了网络设备的属性描述和操作接口,比如下面这些关键成员:
(1)全局信息
char name[IFNAMESIZE]; // 网络设备的名称
(2)硬件信息
unsigned long mem_end; // 设备所使用的共享内存的起始地址
unsigned long mem_start; // 设备所使用的共享内存的结束地址
unsigned long base_addr; // 网络设备I/O基地址
unsigned char irq; // 设备使用的中断号
unsigned char if_port; // 指定多端口设备使用哪一个端口,比如IF_PORT_10BASE2(同轴电缆)和IF_PORT_10BASET(双绞线)
unsigned char dma; // 指定分配给设备的DMA通道
(3)接口信息
unsigned short hard_header_len; // 网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋值为ETH_HLEN,即14
unsigned short type; // 接口的硬件类型
unsigned mtu; // 最大传输单元
unsigned char *dev_addr; // 存放设备的硬件地址,驱动可能会提供设置MAC地址的接口,这会导致用户设置的MAC地址等存入该成员
unsigned short flags; // 网络接口标志
网络接口标志主要包括以下几种:
IFF_UP:当设备被激活并可以开始发送数据包时,内核设置该标志
IFF_AUTOMEDIA:设备可在多种媒介间切换
IFF_BROADCAST:允许广播
IFF_DEBUG:调试模式,可用于控制printk调用的详细程度
IFF_LOOPBACK:回环
IFF_MULTICAST:允许组播
IFF_NOARP:接口不能执行ARP
IFF_POINTOPOINT:接口连接到点对点链路
(4) 设备操作函数
const struct net_device_ops *netdev_ops; // 此结构式网络设备的一系列硬件操作的集合
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
void (*ndo_uninit)(struct net_device *dev);
int (*ndo_open)(struct net_device *dev); // 打开网络接口设备,获取设备需要的I/O地址,IRQ,DMA通道等等
int (*ndo_stop)(struct net_device *dev); // 停止网络接口设备
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,
struct net_device *dev); // 启动数据包发送
netdev_features_t (*ndo_features_check)(struct sk_buff *skb,
struct net_device *dev,
netdev_features_t features);
u16 (*ndo_select_queue)(struct net_device *dev,
struct sk_buff *skb,
void *accel_priv,
select_queue_fallback_t fallback);
void (*ndo_change_rx_flags)(struct net_device *dev,
int flags);
void (*ndo_set_rx_mode)(struct net_device *dev);
int (*ndo_set_mac_address)(struct net_device *dev,
void *addr); // 用于设置设备的MAC地址
int (*ndo_validate_addr)(struct net_device *dev);
int (*ndo_do_ioctl)(struct net_device *dev,
struct ifreq *ifr, int cmd); // 进行设备特定的I/O控制
int (*ndo_set_config)(struct net_device *dev,
struct ifmap *map); // 用于配置接口,也可用于改变设备的I/O地址和中断号
int (*ndo_change_mtu)(struct net_device *dev,
int new_mtu);
int (*ndo_neigh_setup)(struct net_device *dev,
struct neigh_parms *);
void (*ndo_tx_timeout) (struct net_device *dev); // 数据包发送超时时调用,需采取重新启动数据包发送过程或重新启动硬件等措施来恢复网络设备到正常状态
void (*ndo_get_stats64)(struct net_device *dev,
struct rtnl_link_stats64 *storage);
bool (*ndo_has_offload_stats)(const struct net_device *dev, int attr_id);
int (*ndo_get_offload_stats)(int attr_id,
const struct net_device *dev,
void *attr_data);
struct net_device_stats* (*ndo_get_stats)(struct net_device *dev); // 获取网络设备的状态信息
int (*ndo_vlan_rx_add_vid)(struct net_device *dev,
__be16 proto, u16 vid);
int (*ndo_vlan_rx_kill_vid)(struct net_device *dev,
__be16 proto, u16 vid);
....
};
const struct ethtool_ops *ethtool_ops; // 成员函数与ethtool各个命令选项对应
const struct header_ops *header_ops; // 对应于硬件头部操作,主要完成创建硬件头部和从给sk_buff分析出硬件头部等操作
(5) 辅助成员
unsigned long trans_start; // 记录最后的数据包开始发送时的时间戳
unsigned long last_rx; // 最后一次接收到数据包时的时间戳,这俩个时间戳记录的都是jiffies
NAPI
通常情况下,网络设备以中断方式接收数据包,而poll_conmtroller()则采用纯轮询方式,另外一种数据接收方式是NAPI(New API),其数据接收流程为"接收中断来临->关闭接收中断->以轮询方式接收所有数据包直到收空->开启接收中断->接收中断来临......"
static inline void netif_napi_add(struct net_device *dev,
struct napi_struct *napi,
int (*poll)(struct napi_struct *,int), // NAPI要调度执行的轮询函数
int weight); // 初始化一个NAPI
static inline void netif_napi_del(struct napi_struct *napi); // 移除一个NAPI
static inline void napi_enable(struct napi_struct *n); // 使能NAPI调度
static inline void napi_disable(struct napi_struct *n); // 禁止NAPI调度
static inline int napi_schedule_prep(struct napi_struct *n); // 用于检查NAPI是否可以调度
static inline void napi_schedule(struct napi_struct *n); // 用于调用轮询实例的运行
static inline void napi_complete(structg napi_struct *n); // NAPI处理完成的时候应该调用
1.3 设备驱动功能层
设备驱动功能层主要是给net_device结构体中的成员(属性和net_device_ops结构体中的函数指针)赋予具体的数值和函数。也就是说设备驱动功能层中的函数是实际的硬件驱动函数。这些函数形如:xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header()等。
由于网络数据包的接收可由中断触发,所以设备驱动功能层中的另一个主体部分将是中断处理函数,它负责读取硬件上接收到的数据包并传送给上层协议,它负责读取硬件上接收到的数据包并传送给上层协议,因此可能包含xxx_interrupt()和xxx_rx()函数,前者完成中断类型判断的等基本工作,后者完成数据包生成及将其传递给上层等复杂工作。
对于特定的设备,还可以定义相关的私有数据和操作,并封装为一个私有信息结构体xxx_private,让其指针赋值给net_device的私有成员。在xxx_private结构体中可包含设备的页数属性和统计信息等。
在驱动中要用到私有数据的时候,则使用在netdevice.h中定义的接口:
static inline void *netdev_priv(const net_device *dev);
比如驱动dm9000.c的dm9000_probe函数中,使用alloc_etherdev(sizeof(struct board_info))分配网络设备,board_info结构体就成了这个网络设备的私有数据,在其他函数里可以简单地提取这个私有数据,例如:
static int dm9000_start_xmit(struct sk_buff,struct net_device *dev)
{
unsigned long flags;
board_info_t *db = netdev_priv(dev);
...
}
标签:struct,Linux,dev,len,skb,device,网络设备,net,14
From: https://www.cnblogs.com/Wangzx000/p/18232029