1引言
嵌入式操作系统与通用计算机操作系统相比,其网络设计要求适应更多的网络设备,支持更多网络协议。
为了便于网络驱动程序和协议的开发,要求驱动程序和协议之间的耦合较松,所以 VxWorks在协议层和数据链路层之间加了MUX层。MUX层正是为了减弱驱动程序和协议之间的耦合性而设计的。在开发网络设备驱动程序时,程序员不要考虑协议的影响,只要实现MUX提供的接口,同样,在开发协议时,也不要考虑下层驱动的影响,只要遵循MUX层接口规范。
VxWorks操作系统有着优越的网络性能,而 MUX层是影响其网络性能的重要因素。
2  VxWorks网络系统MUX层原理
MUX是一个例程库, VxWorks利用它减弱IP层和数据链路层的紧密连接。它使得实现一个新的协议或者一个网络驱动程序变得更加灵活和容易。实现一个新协议不再需要像以前一样考虑其他驱动程序的因素。一个应用程序希望发一个数据时,IP层把数据包发给MUX层,而不是像原来的协议一样发给驱动程序;同样,当要接收一个数据包是,END从网络上接收数据并把它发给MUX层,MUX层根据不同的服务类型与IP层进行通信。
VxWorks网络协议栈是基于 freeBSD4.4 TCP/IP协议栈发展而来的,包含了许多BSD4.4 TCP/IP协议栈没有的协议,而且在实现一些协议功能时增加了许多新特性,如在IP协议实现时增加了多播功能,VxWorks的网络协议栈层次如图2-1所示。

应用程序
物理媒体
Socket层
BSD socket
协议层
TCP/IP UDP
接口层(MUX)
数据链路层
Enthernet ppp slip
图2-1 VxWorks网络协议栈
 
从上图可以看出, VxWorks的网络协议栈的基本特征和传统的TCP/IP协议相似,VxWorks的网络协议栈最大的特点是在数据链路层和网络协议层之间多了MUX层。在VxWorks的网络协议栈中,网络接口的驱动程序叫做END(Enhanced Network Driver),即增强型网络驱动程序,它处于数据链路层。IP层和TCP/UDP层合称为网络协议层。在数据链路层和网络协议层之间有应用程序接口(API),这个接口在VxWorks的网络协议栈中叫做MUX(Multiplexer)接口。MUX接口如图2-2所示。

                 

互联网协议
数据流
通用协议
MUX
以太网
共享网络
串行链路
其他
图2 -2 VxWorks中的MUX接口
 
在网络协议层, VxWorks典型地使用TCP/IP协议,在数据链路层典型地使用Ethernet, 也支持其它数据传输的物理媒体,例如远距离连接使用的申行线路接入方式,如PPP 等。但是,无论使用什么物理媒体,网络接口驱动都要用到MUX 去与网络协议层通信(数据链路层是一个抽象概念,网络接口驱动程序则是这种抽象概念所描述的功能实现的代码)
在 4.3BSD 中,VxWorks 的网络接口驱动和协议是紧密结合在一起的,它们通过传递特定的数据结构相互通信;而在MUX 基础上,它们只是通过MUX 间接地相互作用。例如,在收到一个包后,网络接口驱动并没有直接与协议层连接。同样地,当网络接口驱动准备好向协议层发送数据时,驱动程序会调用一个MUX 提供的功能(函数)。这个功能(函数)具体负责将数据传给协议层的动作细节。应用MUX 的主要目的是把网络接口驱动和协议层分开,这样就使得网络接口驱动和协议层彼此基本上保持独立。这种独立性使得加载一个新的协议或网络接口驱动变得非常容易。例如:如果要加一个新的网络接口驱动,所有现有的基于MUX 的协议就都可以用这个新的网络接口驱动程序;同样,如果要加一个新的基于MUX 的协议,现有的网络接口驱动也能够用MUX 来与新协议通信。
3 VxWorks网络系统MUX层实现
3.1 MUX层主要接口实现
MUX 层作为独立的一个网络层有其自己的功能函数,但这些功能函数只是其上下两层通信的接口。在 MUX层之上自主开发协议栈,则必须对层间的接口函数有所了解。我们着重讨论协议层与MUX层之间的调用关系。网络协议层和网络驱动与MUX 接口的调用关系如图3-1 所示。
由图 3-1可知,一个网络协议至少包括4个函数:
(1)stackShutdownRtn()
这个程序在 MUX层接收到系统要调用muxDevUnload()时被调用。在卸载(unload)END之前,给绑定在这个END设备上的所有协议一个关闭(shutdown)消息。网络协议必须调用muxUnbind()来与设备断开。
 
图3-1 网络协议层、MUX层、网络驱动调用关系图
(2)stackErrorRtn()
END给网络协议传数据时,如果有错误发生,则调用 stackErrorRtn()进行处理。在网络协议对收到的错误采取必要的措施。由muxError()调用。
(3)stackRcvRtn()
MUX层通过调用 stackRcvRtn()从END接收数据后传给协议层。由muxReceive()调用。
(4)stackTxRestartRtn()
MUX层调用这个程序重起一个以前被停止 (stop)的协议。由muxTxRestart() 调用。MUX层通过这些函数与网络协议进行交互。要实现一个基于MUX的协议,就必须实现这四个函数。
MUX层本身也包含一些函数,例如 muxBind()和muxUnBind()。协议层和数据链路层的实现都要用到MUX层的函数。endLoad()和endSend()是网络驱动程序中必须实现的函数。MUX层利用这些函数和驱动程序进行交互。
3.2 MUX层主要数据结构的实现
(1) DEV­­_OBJ结构
MUX使用 DEV­­_OBJ结构来确定设备的名称和控制结构。驱动程序利用pDevice成员监视一些私有设置,如标志(flags)、存储池地址(memory pool addresses)驱动函数。DEV_OBJ结构包含在end.h里面:
表3-1 DEV_OBJ结构表

typedef struct dev_obj
{
char name[END_NAME_MAX];
int unit;
char description[END_DESC_MAX];
void* pDevice;              
   } DEV_OBJ;
 
(2)END_OBJ结构
设备的核心数据结构是 END_OBJ。在endLoad()函数里面,驱动程序应该分配内存给此数据结构,并且初始化其中一些成员。这个结构定义在target/h/end.h里面:
 
 
表3-2 END_OBJ结构表

typedef struct end_object
   {
 NODE node;
DEV_OBJ devObject;           
 STATUS(*receiveRtn)(void*, M_BLK_ID);
BOOL(*outputFilter)(void*,long,M_BLK_ID, LL_HDR_INFO *, void*);
void* pOutputFilterSpare;   
BOOL    attached;        
SEM_ID    txSem;       long   flags;        struct net_funcs * pFuncTable;
M2_INTERFACETBL    mib2Tbl;
LIST     multiList;             
int         nMulti;                 
LIST    protocols;        
BOOL snarfProto;     
NET_POOL_ID pNetPool;
    } END_OBJ;
 
(3) LL_KDR_INFO结构
这个结构体包含链路层数据包头信息。此数据结构定义的信息仅与协议栈的接收例程有关,定义如下:
表3-3 LL_KDR_INFO结构

typedef struct llHdrInfo
{
    int destAddrOffset;     
    int destSize;              
    int srcAddrOffset;      
    int srcSize;         
    int ctrlAddrOffset;     
    int ctrlSize;        
    int pktType;        
    int dataOffset;            
} LL_HDR_INFO;
 
(4) M2_INTERFACETBL 结构体
表3-4 M2_INTERFACETBL结构表

typedef struct
 {
int  ifIndex;            
char    ifDescr [M2DISPLAYSTRSIZE];
long    ifType;    
long    ifMtu;    
unsigned long ifSpeed;
M2_PHYADDR ifPhysAddress;
long    ifAdminStatus;    
long    ifOperStatus;   
unsigned long    ifLastChange;
unsigned long    ifInOctets;  
unsigned long    ifInUcastPkts;
unsigned long    ifInNUcastPkts;
unsigned long    ifInDiscards; 
unsigned long    ifInErrors;    
unsigned long  ifInUnknownProtos;
unsigned long    ifOutOctets; 
unsigned long    ifOutUcastPkts; 
unsigned long    ifOutNUcastPkts;
unsigned long    ifOutDiscards;
unsigned long    ifOutErrors;   
unsigned long    ifOutQLen;  
M2_OBJECTID ifSpecific; } M2_INTERFACETBL;
 
(5)NET_FUNCS结构体
MUX层使用这个结构为驱动程序引用一些实现好的函数。NET_FUNCS结构体定义如下:
表3-5 NET_FUNCS结构表

typedef struct net_funcs
{
STATUS     (*start) (END_OBJ*);                
STATUS     (*stop) (END_OBJ*);                    
STATUS    (*unload) (END_OBJ*);               
int    (*ioctl) (END_OBJ*, int, caddr_t);        
STATUS (*send) (END_OBJ* , M_BLK_ID);          
STATUS (*mCastAddrAdd) (END_OBJ*, char*); 
STATUS (*mCastAddrDel) (END_OBJ*, char*); 
STATUS(*mCastAddrGet)(END_OBJ*,MULTI_TABLE*);
STATUS (*pollSend) (END_OBJ*, M_BLK_ID); 
STATUS (*pollRcv) (END_OBJ*, M_BLK_ID);   
M_BLK_ID(*formAddress)(M_BLK_ID,M_BLK_ID, M_BLK_ID);
STATUS (*packetDataGet) (M_BLK_ID, LL_HDR_INFO *);
STATUS(*addrGet);
(M_BLK_ID,M_BLK_ID,M_BLK_ID,M_BLK_ID,
M_BLK_ID);    } NET_FUNCS;
 
3.3 MUX层的主要函数实现
(1) 加载网络设备
END_OBJ* muxDevLoad()
    (
    int unit,                       END_OBJ* (*endLoad) (char*, void*),    char* pInitString,          BOOL loaning,            
void* pBSP                  
 )
通过调用 muxDevLoad()例程把设备加载到系统中,它返回一个设备标识符指针,在以后的函数调用过程中需要用到该设备的地方都使用该标识符。系统启动时,VxWorks产生一个任务(tUsrRoot)初始化网络。该任务调用muxDevLoad
(),在这个例程中又要调用所加载设备驱动中 endLoad(),endLoad()将创建一个用于描述驱动程序的END_OBJ结构体和一个可以引用包含有驱动程序功能函数的NET_FUNCS结构体。系统有一个系统设备(END_TBL_ENTRY endDevTbl[]),定义了所有的接口。系统启动是自动为每个接口调用muxDevLoad()例程。
(2)激活网络设备
STATUS muxDevStart()
    (
    void* pCookie  
    )
设备加载成功以后,函数 muxDevStart()激活该设备。在调用muxDevLoad()加载驱动以后,调用muxDevStart()函数,它又将调用驱动程序中的endLoad()函数。endLoad函数激活驱动程序,用对于系统结构和BSP比较合适的中断连接例程为驱动程序注册一个中断服务例程。
(3)注册服务地址映射例程
M_BLK_ID muxAddressForm()
    (
    void* pCookie,                 M_BLK_ID pMblk,                  M_BLK_ID pSrcAddr,  
M_BLK_ID pDstAddr   
    )
STATUS muxMCastAddrAdd()
    (
void* pCookie,      
char *pAddress      
    )
配置 MUX层的服务地址映射(地址解析、多播映射函数),需要用到的函数有muxAddrResFuncAdd()和muxMCastAddrAdd()。注册服务地址映射函数为驱动程序和相应的网络协议提供需要的地址映射函数。
(4)初始化网络协议
END_OBJ* muxBind()
    (
    char * pName,             
    int unit,                  
BOOL (*stackRcvRtn) (void*, long, M_BLK_ID, LL_HDR_INFO *, void*),
    STATUS (*stackShutdownRtn) (void*,void*),
    STATUS (*stackTxRestartRtn) (void*, void*),
    void (*stackErrorRtn) (END_OBJ*, END_ERR*, void*),
long type,                     
char* pProtoName,        
void* pSpare               
    )

一个网络协议提供一个例程给驱动程序分配一个网络服务地址。这个例程通过调用muxBind()例程把协议和驱动程序绑定起来。完成这一步后,网络协议就可以跟通讯设备进行数据收发