Linux网络驱动框架
转载:http://www.cnblogs.com/yangykaifa/p/7080713.html
Linux 的网络系统主要是基于 BSD UNIX 的套接字机制。
在系统与驱动程序之间定义了数据结构 sk_buff 进行传输数据。系统支持对发送数据和接收数据缓存,提供流控机制并提供对多协议的支持。
1、 linux 网络驱动程序的体系结构
linux 网络驱动程序的体系结构从上到下分为4层。各层作用例如以下:
(1) 网络协议接口层向网络层协议提供统一的数据包收发接口。不论上层是IP还是ARP。都通过dev_queue_xmit 函数发送数据。并通过 net_rx 函数接收数据。这一层的存在使得上层协议独立于详细的设备。
(2) 网络设备接口层向协议接口层提供统一的用于描写叙述据详细网络设备属性和操作的结构体 net_device。net_device是设备驱动功能层中各函数的容器。
网络协议接口层从宏观上规划了详细操作硬件的设备驱动功能层的结构。
(3) 设备驱动功能层各函数是网络设备接口层 net_device 数据结构的详细成员。是驱使网络设备硬件完毕对应动作的程序。通过 hard_start_xmit 函数启动发送操作,并通过网络设备上的中断触发接收操作。
因为网络数据包的接收由中断引发,设备驱动功能层中还有一个主体部分是中断处理函数,中断处理函数负责读取硬件上接收的数据包并传递给上层协议,可能包括了xxx_interrupt 和 xxx_rx 函数。
xxx_interrupt 函数完毕中断类型推断等基本工作, xxx_rx 函数完毕数据包的生成和递交上层等复杂工作。
(4) 网络设备与媒介层是完毕数据包发送和接收的物理实体,包含了网路适配器饿坏详细的传输媒介,网络设备被设备驱动功能层中的函数物理上驱动。在linux中。网络设备和媒介都能够是虚拟的。
在linux 中全部网络系统都抽象为一个接口。由结构体 struct net_device 来表示网络设备在内核中的执行情况,即网络设备接口。网络设备接口既包含了纯软件网络设备接口。也包含了硬件网络设备接口。全部的网络设备都是通过以 dev_base 为头指针的设备链表来管理的。该设备链表中的每个元素代表一个网络接口。结构体 net_device 中包括了非常多供系统訪问和协议层调用的设备方法,包括了 init 函数(设备初始化和系统注冊)、open 函数(打开网络设备)、stop 函数(关闭网络设备)、hard_start_xmit 函数(处理数据包发送)和中断处理函数等。
2、 网络设备的初始化
网络设备的初始化主要是 struct net_device 中的 init 函数指针所指向的初始化函数来完毕。
当内核启动或者载入网络驱动模块的时候,就会调用 init 函数。init 函数通过检測物理设备的硬件特征推断网络物理设备是不是存在。然后才决定是不是启用这个驱动程序。
接着对设备进行资源配置,配置好之后就能够向系统申请资源,如中断和 i/o 空间。最后对 net_device 对应的成员变量初始化。这样一个网络设备才干被系统使用。
对于一个新加入的设备。则须要构造设备的 net_device 数据结构,然后向linux 内核注冊设备并申请内存空间。
3、 数据包的发送和接收
当网络设备被激活的时候,调用 net_device 中的 open 函数指针。打开网络设备。并使用 net_device 中的 hard_header 函数指针来建立硬件帧头信息。
然后通过 dev_queue_xmit 协议接口层函数来调用 net_device 中的 hard_start_xmit 函数指针完毕数据包的发送。hard_start_xmit 函数负责把存放在套接字缓冲区(sk_buff)中的数据发送到物理设备。
普通情况下,设备受到数据就会产生一个中断,在中断处理时驱动程序申请一块 sk_buff 。从硬件读出数据并放到申请好的缓冲区中,然后填充sk_buff 的一些信息。最后调用 netif_rx 函数把接收到的数据包传到网络协议的上层进行处理。
当网络设备被激活的时候。调用 net_device 中的 open 函数指针,打开网络设备,并使用 net_device 中的 hard_header 函数指针来建立硬件帧头信息。然后通过 dev_queue_xmit 协议接口层函数来调用 net_device 中的hard_start_xmit 函数指针完毕数据包的发送。hard_start_xmit 函数负责把存放在套接字缓冲区(sk_buff)中的数据发送到物理设备。
普通情况下,设备受到数据就会产生一个中断,在中断处理时驱动程序申请一块 sk_buff ,从硬件读出数据并放到申请好的缓冲区中,然后填充sk_buff 的一些信息。最后调用 netif_rx 函数把接收到的数据包传到网络协议的上层进行处理。
sk_buff 结构体(即套接字缓冲区)用于在linux 网络子系统中的各层之间传递数据。当发送数据包的时候,linux内核的网络处理模块必须建立一个包括要传输的数据包的 sk_buff ,然后将 sk_buff 递交给下层,各层在 sk_buff 中加入不同的协议头直至交给网络设备发送。当网络设备从网络媒介上接收到数据包的时候,必须将接收到的数据装换为 sk_buff 数据结构并传递给上层,各层去掉对应的协议头直至给交用户。
注:
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操作
1)分配:分配一个sk_buff结构,供协议栈代码使用
struct sk_buff *alloc_skb(unsigned int len, int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);
分配一个缓冲区。alloc_skb函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。
2)释放:
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
Linux内核内部使用kfree_skb()函数,而网络设备驱动程序中则最好使用dev_kfree_skb()。
sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示:
用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。
3)变更
unsigned char *skb_put(struct sk_buff *skb, int len);将taill指针向后移动len长度,并返回tail移动之前的值。用于向skb有效数据区域末尾添加数据。
unsigned char *skb_push(struct sk_buff *skb, int len);将data指针向前移动len长度。并返回移动之后的值。用于向skb有效数据区域前端添加数据(包头)。
unsigned char *skb_pull(struct sk_buff *skb, int len);
void skb_reserve(struct sk_buff ×skb, int len);
下图分别对应了这四个函数,看了这张图应该对这4个函数的作用了然于胸。
4、 网络设备的模块载入
网络设备驱动程序载入方式有内核载入和模块载入两种方式。
系统内核有专门载入网络设备的过程,也能够通过 module_init 宏为系统加入新网络设备。此处主要讨论网络设备的模块载入。其基本流程为:
(1) 通过 module_init 宏修饰的函数会在模块载入(或系统启动)时被调用;
(2) 网络设备被检測到后,通过调用 register_netdev 函数在 linux 系统中把设备加入到系统的网络设备链表dev_base 的末尾;
(3) 注冊成功,将调用net_device 中的 init 函数,初始化设备。
通过 register_netdev 函数注冊网络设备是一个动态载入的过程。在 linux 2.6 内核中,全部网络设备都是用该函数载入的。模块载入不存在为网络设备预留空间,从而节省了系统内存资源。
5、 net_device 结构细节
netdevice.h文件保存在 include/linux 文件夹中
(1) 全局信息
char name[IFNAMSIZ];
设备名称。分配的编号从零開始。
unsigned long state;
设备状态。包括若干标识。
struct net_device *next;
指向全局链表下一个设备的指针。
(2) 硬件信息
unsignedlong mem_end;
unsigned long mem_start;
设备内存信息。保存了设备使用的共享内存值起始和终止地址
unsigned long base_addr;
网络接口的的I/O基地址。
unsigned int irq;
被赋予的中断号。
unsigned char if_port;
指定在多port设备上使用哪个port。
unsigned char dma;
为设备分配的DMA通道。用于显示信息(ifconfig命令)。
(3) 设备方法
下面仅列举使用接口必需的方法。
int (*open)(struct net_device*dev);
打开接口。在ifconfig激活接口时,接口将被打开。
open函数应该注冊全部的系统资源。打开硬件并对设备运行其它所需的设置。
int (*stop)(struct net_device*dev);
停止接口。该函数运行的操作与open函数相反。
int (*hard_header) (struct sk_buff *skb,
structnet_device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
该函数依据先前检索到的源和目标硬件地址建立硬件头。该函数任务是将作为參数传递进入的信息,组织成设备特有的适当硬件头。
eth_header 是以太网类型接口的默认函数。ether_setup 将该成员赋值成eth_header。
int (*rebuild_header)(structsk_buff *skb);
该函数用来在数据传输包之前,完毕ARP解析之后,又一次建立硬件头。
void (*tx_timeout) (structnet_device *dev);
如果数据包的传输在合理的时间段内失败。则如果丢失了中断或接口被锁住。这时将调用该方法。tx_timeout 负责解决这个问题并又一次開始数据包的传输。
int (*set_config)(structnet_device *dev,
struct ifmap *map);
改变接口配置。该函数是配置驱动程序的入口点。利用
set_config
能够在执行中改变设备的
I/O
地址和中断号。
struct net_device_stats* (*get_stats)(struct net_device*dev);
当应用程序须要获得接口上的统计信息的时候。将会调用该函数。
网络设备接口层:
网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。
每一个网络设备都由struct net_device来描述,该结构可使用如下内核函数进行动态分配
struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void(*setup)(struct net_device *))
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 (*open)(struct net_device *dev);
打开接口。ifconfig激活时,接口将被打开
int (*stop)(struct net_device *dev);
停止接口,ifconfig eth% down时调用
要注意的是ifconfig是interface config的缩写,通常我们在用户空间输入:
ifconfig eth0 up 会调用这里的open函数。
在用户空间输入:
ifconfig eth0 down 会调用这里的stop函数。
在使用ifconfig向接口赋予地址时,要执行两个任务。首先,它通过ioctl(SIOCSIFADDR)(Socket I/O Control Set Interface Address)赋予地址,然后通过ioctl(SIOCSIFFLAGS)(Socket I/O Control Set Interface Flags)设置dev->flag中的IFF_UP标志以打开接口。这个调用会使得设备的open方法得到调用。类似的,在接口关闭时,ifconfig使用ioctl(SIOCSIFFLAGS)来清理IFF_UP标志,然后调用stop函数。
int (*init)(struct net_device *dev)
初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化
int (*hard_start_xmit)(struct sk_buf*skb,struct net_device *dev)
数据发送函数
int (*hard_header)( struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);
该方法根据先前检索到的源和目的硬件地址建立硬件头
int (*rebuild_header)(struct sk_buff *skb);
以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来。
void (*tx_timeout)(struct net_device *dev);
如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。
struct net_device_stats *(*get_stats)(struct net_device *dev);
当应用程序需要获得接口的统计信息时,这个方法被调用。
int (*set_config)(struct net_device *dev, struct ifmap *map);
改变接口的配置,比如改变I/O端口和中断号等,现在的驱动程序通常无需该方法。
int (*do_ioctl)(struct net_device *dev, struct ifmap *map);
用来实现自定义的ioctl命令,如果不需要可以为NULL。
void (*set_multicast_list)(struct net_device *dev);
当设备的组播列表改变或设备标志改变时,该方法被调用。
int (*set_mac_address)(struct net_device *dev, void *addr);
如果接口支持mac地址改变,则可以实现该函数。
设备驱动接口层:
net_device结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。对具体的设置xxx,工程师应该编写设备驱动功能层的函数,这些函数型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。
网络设备与媒介层:
网络设备与媒介层直接对应于实际的硬件设备。
网络设备的注册
网络设备注册方式与字符驱动不同之处在于它没有主次设备号,并使用下面的函数注册
int register_netdev(struct net_deivce*dev)
网络设备的注销
void unregister_netdev(struct net_device*dev)
四、驱动的实现
1).初始化(init)
设备探测工作在init方法中进行,一般调用一个称之为probe方法的函数
初始化的主要工作时检测设备,配置和初始化硬件,最后向系统申请这些资源。此外填充该设备的dev结构,我们调用内核提供的ether_setup方法来设置一些以太网默认的设置。
2)打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活时被调用(即设备状态由down变成up)
实际上很多在初始化的工作可以放到这里来做。比如说资源的申请,硬件的激活。如果dev->open返回非0,则硬件状态还是down,
注册中断、DMA等;设置寄存器,启动设备;启动发送队列
一般注册中断都在init中做,但在网卡驱动程序中,注册中断大部分都是放在open中注册,因为要经常关闭和重启网卡
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中的一些信息。
中断有可能是收到数据产生也可能是发送完成产生,中断处理程序要对中断类型进行判断,如果是收到数据中断则开始接收数据,如果是发送完成中断,则处理发送完成后的一些操作,比如说重启发送队列。
接收流程:
1、分配skb=dev_alloc_skb(pkt->datalen+2)
2、从硬件中读取数据到skb
3、调用netif_rx将数据交给协议栈
中断处理
网络接口通常支持3种类型的中断:
报文到达中断
报文发送完成中断
出错中断。
中断处理程序可通过查看网卡的中断状态寄存器,来分辨出中断类型。