网络驱动程序有一个非常复杂结构,里面包含了各种各样的网络协议。就目前最为常用的
internet
网络协议来说就包含了应用层、传输层、网络层、链路层以及物理层等
5
个层次,每个层次必须遵守各自的协议。就
AM79C97X
通信芯片来说,与之相关的主要是链路层,它负责为网络层提供服务。因此本章研究的重点在于链路层的驱动。
Internet
网络协议的一个明显的特征,就是它的各个层次是严格分明的,链路层通过通用的函数接口为网络层提供服务。图
5.1
则说明了链路层驱动函数库的层次关系。
其中函数库
muxLib
和函数库
endLib
与硬件无关,主要实现了初始化过程以及一些链路层协议,而函数库
ln97xEnd
主要用于
AM79C97X
通信芯片的控制。网络层和链路层之间虽然在操作上层次比较分明,但是其内部的数据结构比较复杂,因此本章会在最后简单的介绍一个
ioProto
函数库,说明网络层和链路层在数据结构上的关联。
5.2.1
函数库
muxLib
函数库
muxLib
为网络层提供了一组链路层的通用接口,用于处理数据链路层和网络层的通信从而将网络层和链路层隔离开来。
muxLib
函数库在网络驱动中的位置如图
5.1
所示。
该函数库主要提供了两个功能:一是对于系统中所有网卡的管理,主要的核心在于
endList
链的管理;另一个在于对每个
END
,管理其网络服务链即
END_OBJ.protocols
,这也就是为什么该函数库多采用数据结构
MUX_BIND_ENTRY
作为各个函数处理中的主要参数,该结构包含一个
END
设备和一个网络服务:
typedef struct muxBindEntry
int unitNo; /*
设备的
unit
号
*/
char devName[END_NAME_MAX]; /*
设备的名称
*/
long netSvcType; /*
网络服务类型
*/
long netDrvType; /*
网络驱动类型
*/
UINT32 flags; /* BIB
入口标识
*/
END_OBJ* pEnd; /* END
对象指针
*/
void* pNetCallbackId; /*
网络服务回调
Id */
FUNCPTR addrResFunc; /*
地址解析函数指针
*/
FUNCPTR netStackRcvRtn; /* network service stackRcvRtn
指针
*/
}MUX_BIND_ENTRY, *MUX_ID;
vxWorks
系统中通常会遇到一个缩写
END
,
END
的全称是
Enhanced Network Drivers
,即增强型网络驱动,它的作用主要是为了处理链路层的一些协议,并不涉及具体的网络接口设备的控制。
在研究函数库
endLib
之前还需要理解另外一个术语:
NPT
。
NPT
的全称是
Network Protocol ToolKit
,网络协议工具组件,它在保留
END
全部功能的基础上进一步的扩展。它们的不同之处它们处理链路层帧结构的时候有所不同:对于
END
类型的网络设备,在网络层调用
muxLib
库函数发送数据之前,所有的链路层数据包都已经封装好了,而对于
NPT
类型的网络设备,网络层在调用
MUXtk
层时还没有形成完整的数据包,需要在
MUXtk
根据目标
IP
地址组织完整的数据包。参见图
5.2
的结构。
如果一个网络设备被定义成一个
END
类型那么就不能被定义为一个
NPT
类型,反之亦然。无论
END
类型还是
NPT
类型,它们的都采用
END_OBJ
的数据结构。由于么全
END
类型比较完整,条理也比较清晰,因此在后面的分析中我们将更多的精力放在
END
类型的设备中,对
mux
层的分析也仅限于函数库
muxLib
而不再分析
muxTkLib
。
由于在网络驱动中轮询模式实际上很少使用,本章将不再对轮询模式的相关函数进行分析。需要注意的是这个函数库中的一些轮询函数还存在这不少的
bug
,完全没有办法正常执行。一个最直观的例子就是函数
muxPollStart
中分配并初始化了
ppMuxPollDevices
及
pMuxPollMblkRing
数组之后就马上启动
muxPollTask
进行,这时候
ppMuxPollDevices
及
pMuxPollMblkRing
数组中的元素都是
NULL
,
muxPollTask
函数可以对数值为
NULL
的
pMuxPollMblkRing
数组元素进行重新分配,可是对
ppMuxPollDevices
就没有判断直接使用,这样就造成了内存的泄露。这是一个十分严重的错误。如果需要使用到轮询模式,那么这一点尤其需要注意。
函数
muxDevLoad
用于装载网卡驱动。该过程主要分几步:第一步,调用特定网卡的驱动函数
(*endLoad)
,如果
endDevTbl
数组中正好试探到了
am97x
的驱动,那么
endLoad
函数指定的函数为
config/pcpentium/sysLn97xEndLoad
,参数为空白字符串(字符串地址存在,但长度为
0
),深入研究一下
sysLn97xEndLoad
,
sysLn97xEndLoad
函数发现为空白字符串,则调用
ln97xEnd.c/ln97xEndLoad (pParamStr)
,
ln97xEndLoad
发现参数为空字符串,自动将字符串
"lnPci"
复制到参数中,并返回到
muxDevLoad
;第二步,调用函数
endFindByName
在
endList
链表中查找是否存在
"lnPci"
且
unit
号与
pDevTbl->unit
相同这个设备,如果有的话说明已经安装过了,否则需要在
endList
添加这个设备结构
END_TBL_ROW
,其名字为
"lnPci"
;第三步,以字符串参数
paramStr
调用函数
sysLn97xEndLoad
试探性的
load
。一旦成功,就将其挂载到
endlist
中。
关于该函数的执行过程,还会在后面关于网卡驱动的动态安装过程分析中做进一步详细分析。
注意
endList
的结构如图
5.4
所示。注意在计算机系统中可能会有多块网卡,这些网卡的类型也并不一定相同,
endList
记录了系统中网卡的的情况,同一类型的网卡形成一个
END_OBJ
结构链表,而不同类型的网卡分属于不同的链,这些链又通过
END_TBL_ROW
结构链在一起。
当装载一个网卡驱动时,首先要检查该类型及标号为
unit
的网卡有没有装过,如果装过则不需要安装,直接退出;否则还要明确是该类型的网卡有没有安装,如果没有安装则需要重新分配一个
END_TBL_ROW
结构变量并链接到
endList
链中,如果该类型的网卡驱动已经装过,只是没有安装标号为
unit
的网卡驱动,那么只需要分配一个
END_OBJ
结构挂在相应的
END_TBL_ROW
结构变量为链头的链上即可。
l MUX_PROTO_SNARF
类型的网络服务结构总是保存在
protocols
链表的头部位置,
pEnd->snarfCount
记录了
MUX_PROTO_SNARF
类型的网络服务的数量;
l END_OBJ
结构的
protocols
链中除
MUX_PROTO_SNARF
、
MUX_PROTO_PROMISC
之外,其他类型的网络服务每种最多只能有一个;
l 所有类型的网络服务都要指定一个
stackRcvRtn
函数;
l 除
MUX_PROTO_OUTPUT
以外,其它类型的网络服务都要指定
stackShutdownRtn
、
stackTxRestartRtn
、
stackErrorRtn
函数;
l 所有绑定网络服务结构的
nptFlag
元素为
FALSE
,表明是
MUX
类型;
l 除
MUX_PROTO_OUTPUT
网络服务以外都要指定服务的名称,如果参数
pProName
指定了名称,就采用指定的名称,否则名称为
Protocol %d
,这里
%d
为协议的编号;
l 如果新绑定的网络服务是
MUX_PROTO_OUTPUT
以及
MUX_PROTO_PROMISC
类型的网络服务结构,则直接存放在
protocols
链的尾部;否则要存放在
protocols
链中的
MUX_PROTO_SNARF
类型的网络服务结构之后的第一个位置;
参数说明:
char * pName
:网卡的名字如
ln
、
ei
等等;
int unit
:某一类网卡的序号,和
pName
一起确定一个
END
设备;
BOOL (*stackRcvRtn) (void*, long, M_BLK_ID, LL_HDR_INFO *, void*)
:回调函数,
MUX
可以在收到数据包时调用该函数;
STATUS (*stackShutdownRtn) (void*, void*)
:回调函数,
MUX
可以通过这个函数关闭网络服务;
STATUS (*stackTxRestartRtn) (void*, void*)
:回调函数,
MUX
可以通过这个函数通知网络服务可以发送报文;
void (*stackErrorRtn) (END_OBJ*, END_ERR*, void*)
:回调函数,
MUX
可以通过这个函数向网络服务报告错误;
long type
:
RFC1700
规定的网络服务类型。如
0x800
表示
IP
协议;
char * pProtoName
:协议名称;
void * pSpare
:指向网络服务的数据区域,
MUX
调用回调函数时可用于传递参数。
l 对于标准类型的(非
MUX_PROTO_SNARF
、
MUX_PROTO_PROMISC
及
MUX_PROTO_OUTPUT
类型)网络服务只能处理与之类型相同的数据包;
l 函数
muxReceive
将收到的数据包通过回调函数
stackRcvRtn
交给所有的网络服务进行处理,如果某一网络服务需要对数据包进行处理,那么回调函数将返回
TRUE
,否则返回
FALSE
。
l END
收到一个数据包,将根据
protocols
链的顺序交给各个数据包进行处理,如果由其中的任何一个网络服务处理后将直接返回,因此后面的网络服务不再处理,处理的优先级从高到底依次为:
MUX_PROTO_SNARF
、标准型、
MUX_PROTO_PROMISC
类型。当然从
muxBind
函数中
END_OBJ.protocols
链的存放顺序也可以看出其存放顺序:
MUX_PROTO_SNARF
存放在链表前端,
MUX_PROTO_PROMISC
及
MUX_PROTO_OUTPUT
存放在链表尾端。
l END_OBJ.pMib2Tbl
中存放的是收发报文的统计。
从一个数据包中获取其地址信息。通过调用函数
pEnd->pFuncTable->addrGet (pMblk, pSrcAddr, pDstAddr,pESrcAddr, pEDstAddr)
直接实现。
参数说明:
void * pCookie
:一个
MUX_BIND_ENTRY
结构中间包含一个
ENDshebei
和一个网络服务;
M_BLK_ID pMblk
:
M_BLK
结构指针,其中存放了数据;
M_BLK_ID pSrcAddr
:
M_BLK
结构指针,其中存放了源地址;
M_BLK_ID pDstAddr
:
M_BLK
结构指针,其中存放了目的地址;
M_BLK_ID pESrcAddr
:
M_BLK
结构指针,其中存放了源地址;
M_BLK_ID pEDstAddr
:
M_BLK
结构指针,其中存放了目的地址。
muxAddrResFuncAdd
、
muxAddrResFuncGet
、
muxAddrResFuncDel
这三个函数的主要作用是对
LIST addrResList[MUX_MAX_IFTYPE + 1]
的管理。
addrResList
是一个数组,它中间存放了
MUX_MAX_IFTYPE
个链表(
MUX_IFTYPE
是从
1
开始的,
addrResList[0]
未使用),元素
addrResList[ifType]
是一个链头,该链中保存的是
IFT_xxx
对应的地址解析协议函数,该解析函数能够将一个网络服务的地址翻译成一个链路层协议地址。一个常见的例子就是
arpresolve
函数,它能够将一个
IP
地址翻译成一个链路层地址。
muxAddrResFuncAdd
函数就是在链
addrResList[ifType]
中增加一个
MUX_ADDR_REC
结构
typedef struct mux_addr_rec
NODE node;
long protocol;
FUNCPTR addrResFunc;
} MUX_ADDR_REC
mail_xuan
2013-12-11 18:51:46
您好,博主,我看了您的文章觉得写得非常好。我现在正在学习,想请教一下您,NPT驱动平时用的多么,我只了解了muxLib中关于END的源码,是否需要了解NPT方面的呢?