Linux设备驱动工程师之路——网络设备驱动基本原理和框架
K-Style
转载请注明来自于衡阳师范学院08电2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 邮箱:yangkeemail@qq.com
1.Linux网络子系统
Linux网络子系统的顶部是系统调用接口层。它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法(socket)。位于其下面是一个协议无关层,它提供一种通用的方法来使用传输层协议。然后是具体协议的实现,在Linux中包括内核的协议TCP,UDP,当然还有IP。然后是设备无关层,它提供了协议与设备驱动通信的通用接口,最下面是设备的驱动程序。
设备无关接口将协议与各种网络驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动使用,让它们可以对高层协议栈进行操作。需要从协议层向设备发生数据,需要调用dev_queue_xmit函数,这个函数对数据进行列队,然后交由底层驱动程序的hard_start_xmit方法最终完成传输。接收通常是使用netif_rx执行的。当底层设备程序接收到一个报文(发生中断)时,就会调用netif_rx将数据上传至设备无关层。
下图为设备无关层到驱动层的体系结构
2.网络设备描述(structnet_device)
每一个网络设备都由struct net_device来描述,该结构可使用如下内核函数进行动态分配
struct net_device *alloc_net(intsizeof_priv, const char *mask, void(*setup)(struct net_deive *))
sizeof_priv是私有数据区大小;mask是设备名,setup是初始化函数,在注册该设备时,该函数被调用。也就是net_deivce的init成员。
struct net_device *alloc_etherdev(intsizeof_priv)
这个函数和上面的函数不同之处在于内核知道会将该设备做一个以太网设备看待并做一些相关的初始化。
net_device结构可分为全局成员、硬件相关成员、接口相关成员、设备方法成员和公用成员等五个部分
主要全局成员
char name[INFAMSIZ]
设备名,如:eh%d
unsigned long state
设备状态
unsigned long base_addr
I/O基地址
unsigned int irq
中断号
主要设备方法有
int (*init)(struct net_device *dev)
初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化
int (*open)(struct net_device *dev)
打开接口。ifconfig激活时,接口将被打开
int (*stop)(struct net_deivce *dev)
停止接口,ifconfig eth% down时调用
int (*hard_start_xmit)(struct sk_buf*skb,struct net_device *dev)
数据发送函数
int (*do_ioctl)(struct net_deive *dev,struct ifreq *ifr, int cmd)
处理特定于接口的ioctl命令(sock_ioctl)进行调用。
int (*set_mac_address)(struct net_device*dev, void *addr)
改变MAC地址的函数,需要硬件支持该功能。
网络设备的注册
网络设备注册方式与字符驱动不同之处在于它没有主次设备号,并使用下面的函数注册
int register_netdev(struct net_deivce*dev)
网络设备的注销
void unregister_netdev(struct net_device*dev)
3.网络数据包描述(sk_buff)
Linux内核中每个网络数据包都由一个套接字缓冲区结构structsk_buff描述,既每个sk_buff结构就是一个包,指向sk_buff的指针通常被称作skb
sk_buff中重要的数据成员
struct device *dev;处理该包得设备
__u32 sadd;r//IP元地址
__u32 daddr;//IP目的地址
__u32 raddr;//IP路由器地址
unsigned char *head;//分配空间的开始
unsigned char *data;//有效数据的开始
unsigned char *tail;//有效数据的结束
unsigned char *end;//分配空间的结束
unsigned long len;//有效数据的长度
sk_buff操作
struct sk_buff *alloc_skb(unsigned intlen, int priority)
分配一个sk_buff结构,供协议栈代码使用
struct sk_buff *dev_alloc_skb(unsignedint len)
分配一个sk_buff结构。供驱动代码使用
void kfree_skb(struct sk_buff *skb)
void dev_kfree_skb(struct sk_buff *skb)
释放sk_buff结构
unsigned char *skb_push(struct sk_buff*skb,int len)
将data指针向前移动len长度。并返回移动之后的值。用于向skb有效数据区域前端添加数据(包头)。
unsigned char *skb_put(struct sk_buff*skb, int len)
将taill指针向后移动len长度,并返回tail移动之前的值。用于向skb有效数据区域末尾添加数据。
4.驱动的实现
1).初始化(init)
设备探测工作在init方法中进行,一般调用一个称之为probe方法的函数
初始化的主要工作时检测设备,配置和初始化硬件,最后向系统申请这些资源。此外填充该设备的dev结构,我们调用内核提供的ether_setup方法来设置一些以太网默认的设置。
2)打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活时被调用(即设备状态由down变成up)
实际上很多在初始化的工作可以放到这里来做。比如说资源的申请,硬件的激活。如果dev->open返回非0,则硬件状态还是down
3)关闭(stop)
stop方法做和open相反的工作
可以释放某些资源以减少系统负担
stop是在设备状态由up转为down时被调用
4)发送(hard_start_xmit)
在系统调用的驱动程序的hard_start_xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序传给硬件发出去。也有一些特殊的设备比如说loopback把数据组成一个接收数据在传送给系统或者dummy设备直接丢弃数据。
如果发送成功,hard_start_xmit方法释放sk_buff。如果设备暂时无法处理,比如硬件忙,则返回1。
5)接收
驱动程序并存在一个接受方法。当有数据收到时驱动程序调用netif_rx函数将skb交交给设备无关层。
一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb)从硬件中读取数据位置到申请号的缓冲区里。
接下来填充sk_buff中的一些信息。
中断有可能是收到数据产生也可能是发送完成产生,中断处理程序要对中断类型进行判断,如果是收到数据中断则开始接收数据,如果是发送完成中断,则处理发送完成后的一些操作,比如说重启发送队列。