网络设备驱动相比字符型设备的驱动要复杂一些,除了总体上驱动的框架有一些相似外,有很多地方都是不同,但网络设备驱动有一个很大的特点就是有固定的框架可以遵循,具体的框架会在后边详细的叙述,这里主要分析网络设备驱动的结构,和整个tcp/ip网络结构一样,整个网络设备驱动也是一个分层的结构。具体如下:
1.网络协议接口层
在网络协议接口层,只提供了两个抽象函数dev_queue_xmit()与netif_rx(),之所以称之为抽象函数,是因为这两个函数抽象了很多底层的操作,不管是那个芯片它在网络协议结构的操作函数都是这两个函数,采用这样的抽象后,给上层带来了很多的方便,给上层协议提供统一的数据包收发接口,无论上层是ARP协议还是IP协议,都通过dev_queue_xmit()函数发送数据,通过netif_rx()函数接收数据。此层使上层协议独立于具体的设备。
相关数据结构sk_buff:
sk_buff称为“套接字缓冲区”,用于在Linux网络子系统中各层之间传递数据。是Linux网络子系统数据传递的“中枢神经”。sk_buff定义位置为:include/linux/skbuff.h,这个数据结构定义了很多用于网络操作的函数,更多的设计整个协议的实现,包括各层报文的头信息,以及报文的帧格式,下边这个这个结构体是有关报文header信息的,下边的代码摘自kernel2.6.29/include/linux/skbuff.h
DE>struct sk_buff { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev;
struct sock *sk; ktime_t tstamp; struct net_device *dev;
union { struct dst_entry *dst; struct rtable *rtable; }; #ifdef CONFIG_XFRM struct sec_path *sp; #endif /* * This is the control buffer. It is free to use for every * layer. Please put your private variables there. If you * want to keep them across layers you have to do a skb_clone() * first. This is owned by whoever has the skb queued ATM. */ char cb[48];
unsigned int len, data_len; __u16 mac_len, hdr_len; union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u32 priority; __u8 local_df:1, cloned:1, ip_summed:2, nohdr:1, nfctinfo:3; __u8 pkt_type:3, fclone:2, ipvs_property:1, peeked:1, nf_trace:1; __be16 protocol;
void (*destructor)(struct sk_buff *skb); #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) struct nf_conntrack *nfct; struct sk_buff *nfct_reasm; #endif #ifdef CONFIG_BRIDGE_NETFILTER struct nf_bridge_info *nf_bridge; #endif
int iif; __u16 queue_mapping; #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #ifdef CONFIG_NET_CLS_ACT __u16 tc_verd; /* traffic control verdict */ #endif #endif #ifdef CONFIG_IPV6_NDISC_NODETYPE __u8 ndisc_nodetype:2; #endif #if defined(CONFIG_MAC80211) || defined(CONFIG_MAC80211_MODULE) __u8 do_not_encrypt:1; __u8 requeue:1; #endif /* 0/13/14 bit hole */
#ifdef CONFIG_NET_DMA dma_cookie_t dma_cookie; #endif #ifdef CONFIG_NETWORK_SECMARK __u32 secmark; #endif
__u32 mark;
__u16 vlan_tci;
sk_buff_data_t transport_header;//传输层帧头 sk_buff_data_t network_header;//网络层帧头 sk_buff_data_t mac_header;//数据链路层帧头 /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail;//下边的tail、end、head、data是与缓冲区相关 sk_buff_data_t end; unsigned char *head,*data; unsigned int truesize; atomic_t users; };DE> |
sk_buff中的数据缓冲区的指针
Linux必须分配用于容纳数据包的缓冲区,sk_buff中定义了4个指向这片缓冲区的不同位置的指针head、data、tail、end。
head:指针指向内存中已分配的用于存储网路数据的缓冲区起始地址,sk_buff和相关数据块在分配后,该指针的值就固定了。
data:指针指向对应当前协议层有效数据的起始地址。每个协议的有效数据含义不同。
tail:指向对应当前协议层有效数据负载的结尾地址,与data对应。
end:指向内存分配的数据缓冲区的结尾地址,与head指针对应。和head一样,sk_buff和相关数据块被分配后,end指针也就固定了。如图:
套接字缓冲区sk_buff相关操作:
分配空间:
struct sk_buff *dev_alloc_skb(unsigned len)
释放空间:
dev_kfree_skb(struct sk_buff *skb)
dev_kfree_skb_irq(struct sk_buff *skb)
dev_kfree_skb_any(struct sk_buff *skb)
put操作:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
unsigned char *__skb_put(struct sk_buff *skb, unsigned int len);
作用:tail指针下移len长度,并增加sk_buff中len的值,返回改变后的tail值。主要用于在尾部追加数据。
push操作:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
unsigned char *__skb_push(struct sk_buff *skb, unsigned int len);
作用:将data指针上移,同时增加sk_buff中的len。主要用于在数据包发送时添加头部。函数带__和不带__的区别在于:带__的会检测放入缓冲区的数据,后则不会。
pull操作:
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
作用:将data指针下移,并减少sk_buff中的len值。这个操作一般用于下层协议向上层协议移交数据包,使data指针指向上一层协议的协议头
reserve操作:
void skb_reserve(struct sk_buff *skb, unsigned int len);
作用:将data和tail指针同时下移,这个操作主要用于在存储空间的头部预留len长度的空隙。
2.网络设备接口层
我们知道,通常的驱动编写就是填充一个设备相关的结构体,网络设备也是这样,在2.6.29的内核中,需要填写结构体如下:
DE>kernel2.6.29/include/linux/netdevice.hDE>
DE>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); int (*ndo_stop)(struct net_device *dev); int (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev); u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb); #define HAVE_CHANGE_RX_FLAGS void (*ndo_change_rx_flags)(struct net_device *dev, int flags); #define HAVE_SET_RX_MODE void (*ndo_set_rx_mode)(struct net_device *dev); #define HAVE_MULTICAST void (*ndo_set_multicast_list)(struct net_device *dev); #define HAVE_SET_MAC_ADDR int (*ndo_set_mac_address)(struct net_device *dev, void *addr); #define HAVE_VALIDATE_ADDR int (*ndo_validate_addr)(struct net_device *dev); #define HAVE_PRIVATE_IOCTL int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd); #define HAVE_SET_CONFIG int (*ndo_set_config)(struct net_device *dev, struct ifmap *map); #define HAVE_CHANGE_MTU int (*ndo_change_mtu)(struct net_device *dev, int new_mtu); int (*ndo_neigh_setup)(struct net_device *dev, struct neigh_parms *); #define HAVE_TX_TIMEOUT void (*ndo_tx_timeout) (struct net_device *dev);
struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);
void (*ndo_vlan_rx_register)(struct net_device *dev, struct vlan_group *grp); void (*ndo_vlan_rx_add_vid)(struct net_device *dev, unsigned short vid); void (*ndo_vlan_rx_kill_vid)(struct net_device *dev, unsigned short vid); #ifdef CONFIG_NET_POLL_CONTROLLER #define HAVE_NETDEV_POLL void (*ndo_poll_controller)(struct net_device *dev); #endif };DE> |
在我们实现这些函数中的一部分后,就实现了驱动的功能了,为千变万化的网络设备定义统一的、抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。net_device结构体在内核中指代一个网络设备,网络设备驱动只需填充其结构体就可以实现内核与具体硬件操作函数的挂接。实际驱动的编写过程中,我们并不需要实现全部的函数,实际上,我们只要根据具体的需要实现上边的部分就可以了。
net_device结构体的相关成员
网络设备的名称:
char name[IFNAMESIZ]
设备初始化指针:
int (*init)(struct net_device *dev);
硬件信息:
unsigned long mem_end;
unsigned long mem_start;
二者分别定义了设备所用的共享内存 的起始和结束地址
unsigned long base_addr;网络设备的I/O基地址
unsigned char irq;设备使用的中断号
unsigned char if_port;多端口设备中端口选择
unsigned char dma;分配给设备的DMA通道
接口信息
unsigned short hard_header_len;网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋值为ETH_HLEN,即14
unsigned short type;接口的硬件类型
unsigned mtu;最大传输单元
unsigned char dev_addr[MAX_ADDR_LEN];
unsigned char broadcase[MAX_ADDR_LEN];
二者分别用于存放设备的硬件地址和广播地址,以太网设备的广播地址为6个0xFF
unsigned short flags;网络接口标志
设备操作函数
打开、关闭
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
启动数据包发送
int (*hard_start_xmit)(struct sk_buff *skb, struct net_device *dev);
获得网络设备状态
struct net_device_status * (*get_status)(struct net_device *dev);
设备I/O控制
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
配置接口,I/O地址,中断号
int (*set_config)(struct net_device *dev, struct ifmap *map);
设置MAC地址
int (*set_mac_address)(struct net_device *dev, void *addr);
辅助成员
unsigned long trans_start;
unsigned long last_rx;
二者分别为:最后一次开始发送的时间戳、最后一次接收到数据包的时间戳,这两个时间戳记录都是jiffies,驱动程序应维护这两个成员。
void *priv;私有信息指针
spinlock_t xmit_lock;
int xmit_lock_owner;
xmit_lock是避免hard_start_xmit函数同时多次调用的自旋锁。xmit_lock_owner指向拥有此自旋锁的CPU的编号。