标签:
linux设备驱动在linux中作为linux的内核模块存在。而内核模块可以在系统运行期间动态扩展系统。所以,我们可以在用户空间,使用insmod和rmmod动态安装或卸载模块。
现实世界中存在着大量的设备,这些设备在电气特性和I/O方式上都各不相同。为了简化设备驱动程序员的工作,linux系统从这些各异的设备中提取了共性的特征,将其划分为三大类:字符设备、块设备和网络设备。
Linux设备驱动模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,从而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。
用户接口
利用系统中所有设备的完整分层关系图,可以很容易的向用户空间导出一副完整的视图。
这通过一个叫sysfs的特殊的虚拟文件系统实现了,用户可以在用户空间的任意节点挂载这一文件系统。正所谓Linux下一切皆文件。sysfs将变量和功能都作为接口向用户空间公开了,使得设备驱动变得相当透明,对设备驱动的操作也可以简单的变成了文件操作。结合守护进程和内核事件层,sysfs是一个用户空间和内核空间通信的完美的渠道。
linux设备底层模型
设备模型是层次的结构,层次的每一个节点都是通过kobject实现的,在文件上则体现在sysfs文件系统。
kobject结构内核中存在struct kobject数据结构,每个加载到系统中的kobject都唯一对应/sys或者子目录中的一个文件夹。可以这样说,许多kobject结构就构成设备模型的层次结构。每个kobject对应一个或多个struct attribute描述属性的结构。
struct kobject {
const char *name; /* 对应sysfs的目录名 */
struct list_head entry; /* kobjetct双向链表 */
struct kobject *parent; /* 指向kset中的kobject,相当于指向父目录 */
struct kset *kset; /*指向所属的kset */
struct kobj_type *ktype; /*负责对kobject结构跟踪*/
struct sysfs_dirent *sd;
struct kref kref; /*kobject引用计数*/
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
网络设备(又称“网卡”),用来完成高层网络协议(如TCP/UDP等)的底层数据传输及设备控制等功能。
网络设备在/dev目录下没有入口点,换句话说,网络设备在系统中并不像块设备那样以一个设备文件的形式存在,在应用层,用户通过套接口API的socket函数来使用网络设备。
socket API的原型函数为:
<sys/socket.h>
int socket(int family, int type, int protocol)
其中family表示套接口所使用的协议族,包括AF_INET(IPV4协议族)和AF_INET6(IPv6协议族)等。
参数type用来表示套接口的类型,有SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口)。
一般派说,函数的第三个参数在实际使用中常设置为0,除非是用在原始套接口上。
之后linux内核中高层网络代码负责分配skb以存储网络数据包,然后将该skb传到驱动程序的发送函数中。驱动程序的发送函数一般会使用DMA来将位于主存中的skb传输到设备内存中,再由硬件逻辑发送出去。硬件在成功发送完一个数据帧后,通常会以中断的方式通知处理器,接下来相应驱动程序的中断处理函数被调用来处理这种情况。对于成功发送分组产生的中断,在其中断处理函数中会释放该分组所在的skb。
网络设备除了响应来自内核的请求wait,还需要异步地处理来自外部世界的数据包,而对于块设备而言,只需要响应来自内核的请求。这使得网络设备驱动程序的设计模式,除了处理数据wait,还需要完成诸如地址设置、配置网络传输参数以及流量统计等一些管理类的任务。
底层网络设备在成功将一个分组传输到主存后,会以中断的方式通知驱动程序。后者负责分配一个新的skb来容纳该新入的分组,然后通过调用netif_rx函数通知网络子系统高层新数据包到达。
网络设备和驱动程序完成的是最底层的物理层和数据链路层,该层面向实际承担数据传输任务的物理媒体,为数据通信的介质提供规范和定义,主要关心的是在通信线路上传输比特流的问题。
所有的Linux网络驱动程序遵循通用的接口,设计时采用的是面向对象的方法。
一个设备就是一个对象(device 结构),它内部有自己的数据和方法。
每一个设备的方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身的数据(类似面向对象程序设计时的this引用)。
一个网络设备最基本的方法有初始化、发送和接收。
网络驱动程序的基本方法
初始化(initialize)
驱动程序必须有一个初始化方法,在把驱动程序载入系统的时候会调用这个初始化程序,它做以下几方面的工作:
检测设备。在初始化程序里你可以根据硬件的特征检查硬件是否存在,然后决定是否启动这个驱动程序。
配置和初始化硬件。在初始化程序里你可以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时候进行配置(Linux内核对PnP功能没有很好的支持,可以在驱动程序里完成这个功能)。
配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。
接下来你要初始化device结构中的变量。
最后,你可以让硬件正式开始工作。
打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状态由down–>up)。
所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。
open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。
关闭(stop)
close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在设备状态由up转为down时被调用的。
另外如果是做为模块装入的驱动程序,close里应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。
另外close方法必须返回成功(0==success)。
发送(hard_start_xmit)
所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit 时,发送的数据放在一个sk_buff结构中。
一般的驱动程序把数据传给硬件发出去。也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数据。
如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。
如果设备暂时无法处理,比如硬件忙,则返回1。这时如果dev->tbusy置为非0,则系统认为硬件忙,要等到dev->tbusy置0以后才会再次发送。tbusy的置0任务一般由中断完成。硬件在发送结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通知系统可以再次发送。在发送不成功的情况下,也可以不置dev->tbusy为非0,这样系统会不断尝试重发。如果hard_start_xmit发送不成功,则不要释放sk_buff。传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬件发送。
sk_buff是被锁住的(locked),确保其他程序不会存取它。
接收(reception)
驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。
一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb),从硬件读出数据放置到申请好的缓冲区里。
接下来填充sk_buff中的一些信息。skb->dev = dev,判断收到帧的协议类型,填入skb->protocol(多协议的支持)。
把指针skb->mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要设置skb->pkt_type,标明第二层(链路层)数据类型。可以是以下类型:
PACKET_BROADCAST : 链路层广播
PACKET_MULTICAST : 链路层组播
PACKET_SELF : 发给自己的帧
PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧)
最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回,真正的处理是在中断返回以后,这样可以减少中断时间。
调用netif_rx()以后,驱动程序就不能再存取数据缓冲区skb。
地址解析(xarp)
有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。
这个对应是通过地址解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方法。调用的主要参数包括指向硬件帧头的指针,协议层地址。
如果驱动程序能够解析硬件地址,就返回1,如果不能,返回0。
对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。
参数设置和统计数据
在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。
设置方法有:
dev->set_mac_address() 当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。
dev->set_config() 当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。
dev->do_ioctl()如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱动程序的这个方法。一般是设置设备的专用数据。读取信息也是通过ioctl调用进行。
除次之外驱动程序还可以提供一个dev->get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。
struct device
{
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char *name;
/* I/O specific fields - FIXME: Merge these and struct ifmap into one */
unsigned long rmem_end; /* shmem "recv" end */
unsigned long rmem_end; /* shmem "recv" end */
unsigned long rmem_start; /* shmem "recv" start */
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned char irq; /* device IRQ number */
/* Low-level status flags. */
volatile unsigned char start, /* start an operation */
interrupt; /* interrupt arrived */
/* 在处理中断时interrupt设为1,处理完清0。 */
unsigned long tbusy; /* transmitter busy must be long
for
struct device *next;
/* The device initialization function. Called only once. */
/* 指向驱动程序的初始化方法。 */
int (*init)(struct device *dev);
/* Some hardware also needs these fields, but they are not part of the
usual set specified in Space.c. */
/* 一些硬件可以在一块板上支持多个接口,可能用到if_port。 */
/* 一些硬件可以在一块板上支持多个接口,可能用到if_port。 */
unsigned char if_port; /* Selectable AUI, TP,..*/
unsigned char dma; /* DMA channel */
struct enet_statistics* (*get_stats)(struct device *dev);
/*
* This marks the end of the "visible" part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will).
*/
/* These may be needed for future network-power-down code. */
/* trans_start记录最后一次成功发送的时间。可以用来确定硬件是否工作正常。*/
unsigned long trans_start; /* Time (in jiffies) of last Tx */
unsigned long last_rx; /* Time of last Rx */
/* flags里面有很多内容,定义在include/linux/if.h里。*/
unsigned short flags; /* interface flags (a la BSD) */
unsigned short family; /* address family ID (AF_INET) */
unsigned short metric; /* routing metric (not used) */
unsigned short mtu; /* interface MTU value */
/* type标明物理硬件的类型。主要说明硬件是否需要arp。定义在
include/linux/if_arp.h里。 */
unsigned short type; /* interface hardware type */
/* 上层协议层根据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/
unsigned short hard_header_len; /* hardware hdr length */
/* priv指向驱动程序自己定义的一些参数。*/
void *priv; /* pointer to private data */
/* Interface address info. */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */
unsigned char pad; /* make dev_addr aligned
to 8
bytes */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */
unsigned char addr_len; /* hardware address length */
unsigned long pa_addr; /* protocol address */
unsigned long pa_brdaddr; /* protocol broadcast addr */
unsigned long pa_dstaddr; /* protocol P-P other side addr */
unsigned long pa_mask; /* protocol netmask */
struct dev_mc_list *mc_list; /* Multicast mac addresses */
int mc_count; /* Number of installed mcasts */
struct ip_mc_list *ip_mc_list; /* IP multicast filter chain */
__u32 tx_queue_len; /* Max frames per queue allowed */
/* For load balancing driver pair support */
unsigned long pkt_queue; /* Packets queued */
struct device *slave; /* Slave device */
struct net_alias_info *alias_info; /* main dev alias info */
struct net_alias *my_alias; /* alias devs */
/* Pointer to the interface buffers. */
struct sk_buff_head buffs[DEV_NUMBUFFS];
/* Pointers to interface service routines. */
int (*open)(struct device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,
struct device *dev);
int (*hard_header) (struct sk_buff *skb,
struct device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
int (*rebuild_header)(void *eth, struct device *dev,
unsigned long raddr, struct sk_buff *skb);
#define HAVE_MULTICAST
void (*set_multicast_list)(struct device *dev);
#define HAVE_SET_MAC_ADDR
int (*set_mac_address)(struct device *dev, void *addr);
#define HAVE_PRIVATE_IOCTL
int (*do_ioctl)(struct device *dev, struct ifreq *ifr, int
cmd);
#define HAVE_SET_CONFIG
int (*set_config)(struct device *dev, struct ifmap *map);
#define HAVE_HEADER_CACHE
void (*header_cache_bind)(struct hh_cache **hhp, struct dev
ice
*dev, unsigned short htype, __u32 daddr);
*dev, unsigned short htype, __u32 daddr);
void (*header_cache_update)(struct hh_cache *hh, struct dev
ice
*dev, unsigned char * haddr);
#define HAVE_CHANGE_MTU
struct iw_statistics* (*get_wireless_stats)(struct device *dev);
};
标签:
原文地址:http://blog.csdn.net/gengzhikui1992/article/details/51063266