学习网络设备驱动之前,先来分析一下网络子系统的结构等知识。
1.分为用户空间和内核空间,以及物理设备空间;
2.用户空间是应用层,内核空间与应用层交互的是系统文件调用层(system call interface),系统调用和网络协议层之间有个协议无关接口(protocol agnostic interface),网络协议层和设备驱动层(device dirver )之间有个设备无关接口(device agnostic interface);
3.为什么要有协议无关接口?这样系统调用可以用统一的接口来将不同类型的传输协议包送给协议无关接口,经过协议无关接口后来送给协议层处理,方便了应用层的系统调用。
4.为什么要有设备无关接口?这一层提供一组通用函数供底层驱动程序使用,让它们可以对高层协议栈进行操作。向上,为我们的网络协议层来访问设备驱动提供了统一的接口,不管网卡驱动如何写,我们的协议层访问网卡驱动都是用的统一的接口;向下,它为设备驱动程序处理协议提供了统一的接口,比如说网卡接收到了IP包,我们的驱动程序不需要去关心这个IP包是TCP的还是UDP的,甚至它都不需要关心是不是IP包,它将这个包直接丢给设备无关接口来处理了。
有了网络框架结构的了解,下面从net_device这个结构体来讲起。
1.每个网络接口都由一个net_device来描述,这个结构可以用alloc_netdev或者alloc_etherdev()来分配;
2.这个结构体主要的成员有
网络接口驱动的注册方式与字符驱动不同的地方是它没有主次设备号,用register_netdev(struct net_device*dev)注册
刚才提到设备无关接口,那么它为网络协议层提供统一的数据包收发接口,不论上层协议为arp还是ip,都通过dev_queue_xmit(struct sk_buff*skb)函数发送数据,并且通过netif_rx(struct sk_buff*skb)函数来接受数据,说到这儿,我们要引入套接字缓冲区struct sk_buff这个非常重要的结构体了。
该结构主要的成员如下:unsigned char*head;unsigned char*end;unsigned char*data;unsigned char*end;
head data tail end
分配空间的开始 有效数据的开始 有效数据的结尾 分配空间的结束
skb_buff 的操作函数主要有struct sk_buff *dev_alloc_skb(unsigned int len, int priority) ----------------供驱动代码使用
struct sk_buff *alloc_skb(unsigned int len, int priority) -------------供协议栈代码使用
另外三个操作函数分别是unsigned char*skb_put(struct sk_buff *skb, int len)和unsigned char*skb_push(struct sk_buff *skb, int len),以及static inline void skb_reserve(struct sk_buff *skb, int len)。skb_put的作用是将data指针前移len的长度,函数返回的是移动之后的指针;而skb_push的作用 是将tail指针往后移len的长度,并且返回的是移动之前的指针;skb_reserve的作用是将data和tail的指针 同时向后移动len的长度。
skb_buff的释放函数dev_kfree_skb(struct sk_buff *skb);
下面是网络设备驱动的注册与注销
先alloc_etherdev(sizeof_pri)函数来对net_device的生成和对其成员的赋值,它是alloc_netdev()针对以太网的快捷函数,分配并赋值后用register_netdev()注册设备进内核。net_device结构体的分配与驱动注册是在模块加载函数中进行。同理,释放函数free_netdev()和注销函数unregister_netdev()是在模块卸载函数中完成。
网络设备驱动注册,当寻找到device时,会去执行probe函数,此时我们的网络设备的初始化工作可以放在probe函数中去执行。具体的初始化主要包含以下几个方面:
网络设备的打开函数需要完成如下的工作:
网络设备的关闭函数需要完成如下的工作:
linux网络子系统发送数据包时,会调用驱动程序提供的hard_start_transmit()函数,在设备初始化的时候这个函数指针需要被初始化。hard_start_transmit()函数中,根据skb->data,skb->len来发送数据,当发送队列满了,或者其他原因来不及发送当前上层传下来的包时,我们调用netif_stop_queue()函数来阻止上层继续向网络设备驱动发送数据包。当忙于发送的数据包发送完成后,传送结束的中断处理中,应该调用netif_wake_queue唤醒被阻塞的上层。
当数据传输超时时,此时超时处理函数xxx_tx_timeout()将会被调用,这个函数里面也需要调用linux内核提供的netif_wake_queue()函数重新启动设备发送队列。
网络接收数据包的主要方法是中断引发设备的中断处理函数,中断处理判断中断类型,如果为接收中断则读取接收到的数据,分配sk_buff数据结构,将数据复制到数据缓冲区,并且调用netif_rx()函数传递给上层协议。我们经常在接收函数中看到skb=dev_alloc_skb(length+2);skb_reserve(skb,2),这里的2是因为IP协议头是4字节对齐的,而接收到的IP头首部是6个字节目的地址,6个字节源地址,2个字节数据字段长度,一共加起来是14个字节,所以分配skb数据缓冲区的时候多分配2个字节。