二.Linux系统网络设备驱动程序
2.1网络驱动程序的结构
所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备就是一个对象(device结构),它内部有自己的数据和方法。每一个设备的方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身的数据(类似面向对象程序设计时的this引用)。一个网络设备最基本的方法有初始化、发送和接收。
-------------------
---------------------
|deliverpackets
|
|receivepacketsqueue|
|(dev_queue_xmit())|
|them(netif_rx())
|
-------------------
---------------------
|
|
/
/
|
|
-------------------------------------------------------
|methodsandvariables(initialize,open,close,hard_xmit,|
|interrupthandler,config,resources,status...)
|
-------------------------------------------------------
|
|
/
/
|
|
-----------------
----------------------
|sendtohardware|
|receivcefromhardware|
-----------------
----------------------
|
|
/
/
|
|
-----------------------------------------------------
|
hardwaremedia
|
-----------------------------------------------------
初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请、发送程序是在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发送数据进行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通过硬件中断来通知的。在中断处理程序里,把硬件帧信息填入一个skbuff结构中,然后调用netif_rx()传递给上层处理。
2.2网络驱动程序的基本方法
网络设备做为一个对象,提供一些方法供系统访问。正是这些有统一接口的方法,掩蔽了硬件的具体细节,让系统对各种网络设备的访问都采用统一的形式,做到硬件无关性。
下面解释最基本的方法。
2.2.1初始化(initialize)
驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初始化程序。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的特征检查硬件是否存在,然后决定是否启动这个驱动程序。配置和初始化硬件。在初始化程序里你可以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时候进行配置(Linux内核对PnP功能没有很好的支持,可以在驱动程序里完成这个功能)。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是能)。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。接下来你要初始化device结构中的变量。最后,你可以让硬件正式开始工作。
2.2.2打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状态由down-->up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。
open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。
2.2.3关闭(stop)
close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。另外close方法必须返回成功(0==success)。
2.2.4发送(hard_start_xmit)
所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数据。如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果发送成功,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),确保其他程序不会存取它。
2.2.5接收(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()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回,真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后,驱动程序就不能再存取数据缓冲区skb。
2.2.6硬件帧头(hard_header)
硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网(Ethernet)就有14字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供一个hard_header方法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。硬件帧头的长度必须填在dev->hard_header_len,这样协议层回在数据之前保留好硬件帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就可以了。
在协议层调用hard_header时,传送的参数包括(2.0.xx):数据的sk_buff,
device指针,protocol,目的地址(daddr),源地址(saddr),数据长度(len)。数据长度不要使用sk_buff中的参数,因为调用hard_header时数据可能还没完全组织好。saddr是NULL的话是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目的地址。如果hard_header完全填好了硬件帧头,则返回添加的字节数。如果硬件帧头中的信息还不完全(比如daddr为NULL,但是帧头中需要目的硬件地址。典型的情况是以太网需要地址解析(arp)),则返回负字节数。hard_header返回负数的情况下,协议层会做进一步的buildheader的工作。目前Linux系统里就是做arp(如果hard_header返回正,dev->arp=1,表明不需要做arp,返回负,dev->arp=0,做arp)。对hard_header的调用在每个协议层的处理程序里。如ip_output。
2.2.7地址解析(xarp)
有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方法。调用的主要参数包括指向硬件帧头的指针,协议层地址。如果驱动程序能够解析硬件地址,就返回1,如果不能,返回0。
对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。
2.2.8参数设置和统计数据
在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。设置方法有:
dev->set_mac_address()
当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。
dev->set_config()
当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。
dev->do_ioctl()
dev->do_ioctl()
如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱动程序的这个方法。一般是设置设备的专用数据。
读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个
dev->get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。
ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。
2.3网络驱动程序中用到的数据结构
最重要的是网络设备的数据结构。定义在include/linux/netdevice.h里。它的注释已经足够详尽。
structdevice{
/*
*Thisisthefirstfieldofthe"visible"partofthisstructure
*(i.e.asseenbyusersinthe"Space.c"file).
Itisthename
*theinterface.
*/
char
*name;
/*I/Ospecificfields-FIXME:Mergetheseandstructifmapintoone*/
unsignedlong
rmem_end;
/*shmem"recv"end
*/
unsignedlong
rmem_end;
/*shmem"recv"end
*/
unsignedlong
rmem_start;
/*shmem"recv"start
*/
unsignedlong
mem_end;
/*sharedmemend
*/
unsignedlong
mem_start;
/*sharedmemstart
*/
unsignedlong
base_addr;
/*deviceI/Oaddress
*/
unsignedchar
irq;
/*deviceIRQnumber
*/
/*Low-levelstatusflags.*/
volatileunsignedchar
start,
/*startanoperation
*/
interrupt;
/*interruptarrived
*/
/*在处理中断时interrupt设为1,处理完清0。*/
unsignedlong
tbusy;
/*transmitterbusymustbelongfor
structdevice
*next;
/*Thedeviceinitializationfunction.Calledonlyonce.*/
/*指向驱动程序的初始化方法。*/
int
(*init)(structdevice*dev);
/*Somehardwarealsoneedsthesefields,buttheyarenotpartofthe
usualsetspecifiedinSpace.c.*/
/*一些硬件可以在一块板上支持多个接口,可能用到if_port。*/
/*一些硬件可以在一块板上支持多个接口,可能用到if_port。*/
unsignedchar
if_port;
/*SelectableAUI,TP,..*/
unsignedchar
dma;
/*DMAchannel
*/
structenet_statistics*(*get_stats)(structdevice*dev);
/*
*Thismarkstheendofthe"visible"partofthestructure.All
*fieldshereafterareinternaltothesystem,andmaychangeat
*will(read:maybecleanedupatwill).
*/
/*Thesemaybeneededforfuturenetwork-power-downcode.*/
/*trans_start记录最后一次成功发送的时间。可以用来确定硬件是否工作正常。*/
unsignedlong
trans_start;
/*Time(injiffies)oflastTx*/
unsignedlong
last_rx;
/*TimeoflastRx
*/
/*flags里面有很多内容,定义在include/linux/if.h里。*/
unsignedshort
flags;
/*interfaceflags(alaBSD)
*/
unsignedshort
family;
/*addressfamilyID(AF_INET)
*/
unsignedshort
metric;
/*routingmetric(notused)
*/
unsignedshort
mtu;
/*interfaceMTUvalue
*/
/*type标明物理硬件的类型。主要说明硬件是否需要arp。定义在
include/linux/if_arp.h里。*/
unsignedshort
type;
/*interfacehardwaretype
*/
/*上层协议层根据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/
unsignedshort
hard_header_len;
/*hardwarehdrlength
*/
/*priv指向驱动程序自己定义的一些参数。*/
void
*priv;
/*pointertoprivatedata
*/
/*Interfaceaddressinfo.*/
unsignedchar
broadcast[MAX_ADDR_LEN];
/*hwbcastadd*/
unsignedchar
pad;
/*makedev_addralignedto8bytes*/
unsignedchar
dev_addr[MAX_ADDR_LEN];
/*hwaddress
*/
unsignedchar
addr_len;
/*hardwareaddresslength
*/
unsignedlong
pa_addr;
/*protocoladdress
*/
unsignedlong
pa_brdaddr;
/*protocolbroadcastaddr
*/
unsignedlong
pa_dstaddr;
/*protocolP-Pothersideaddr*/
unsignedlong
pa_mask;
/*protocolnetmask
*/
structdev_mc_list
*mc_list;
/*Multicastmacaddresses
*/
int
mc_count;
/*Numberofinstalledmcasts
*/
structip_mc_list
*ip_mc_list;
/*IPmulticastfilterchain
*/
__u32
tx_queue_len;
/*Maxframesperqueueallowed*/
/*Forloadbalancingdriverpairsupport*/
unsignedlong
pkt_queue;
/*Packetsqueued*/
structdevice
*slave;
/*Slavedevice*/
structnet_alias_info
*alias_info;
/*maindevaliasinfo*/
structnet_alias
*my_alias;
/*aliasdevs*/
/*Pointertotheinterfacebuffers.*/
structsk_buff_head
buffs[DEV_NUMBUFFS];
/*Pointerstointerfaceserviceroutines.*/
int
(*open)(structdevice*dev);
int
(*hard_start_xmit)(structsk_buff*skb,
structdevice*dev);
int
(*hard_header)(structsk_buff*skb,
structdevice*dev,
unsignedshorttype,
void*daddr,
void*saddr,
unsignedlen);
int
(*rebuild_header)(void*eth,structdevice*dev,
unsignedlongraddr,structsk_buff*skb);#defineHAVE_MULTICAST
void
(*set_multicast_list)(structdevice*dev);#defineHAVE_SET_MAC_ADDR
int
(*set_mac_address)(structdevice*dev,void*addr);#defineHAVE_PRIVATE_IOCTL
int
(*do_ioctl)(structdevice*dev,structifreq*ifr,intcmd);#defineHAVE_SET_CONFIG
int
(*set_config)(structdevice*dev,structifmap*map);#defineHAVE_HEADER_CACHE
void
(*header_cache_bind)(structhh_cache**hhp,structdevice*dev,unsignedshorthtype,__u32daddr);*dev,unsignedshorthtype,__u32daddr);
void
(*header_cache_update)(structhh_cache*hh,structdevice*dev,unsignedchar*
haddr);#defineHAVE_CHANGE_MTU
structiw_statistics*
(*get_wireless_stats)(structdevice*dev);};