驱动小牛
|
阅读:1675回复:0
一点老资料
用NT DDK开发协议驱动程序
王大伟 杨凯锋 胡熠 洪佩琳 摘要: 本文针对基于Windows NT平台的协议驱动程序的开发方法进行讨论,并给出一个开发实例进行具体说明。 关键词: 协议驱动程序、DDK、NDIS 协议驱动程序简介 微软公司为Windows系列平台上的网络驱动程序开发者专门制定了NDIS规范(Network Driver Interface Specification)。根据NDIS规范,Windows NT平台上的网络驱动程序主要有三类:网卡驱动程序、中介协议驱动程序和上层协议驱动程序。网络驱动程序的层次结构参见参考文献【1】。网卡驱动程序位于驱动程序层次的最底层,它直接管理硬件,屏蔽底层物理硬件的细节,向上层驱动程序提供一个抽象的服务接口。协议驱动程序加载在网卡驱动程序之上,利用网卡驱动程序提供的服务实现各种网络协议的功能。协议驱动程序可以在其上边界实现TDI(Transport Driver Interface)接口为网络用户提供服务。譬如分配分组,从用户应用程序把数据拷贝到分组中,然后调用NDIS提供的函数把分组发送给下层驱动程序。它在下边界提供一个协议接口,接收并解释从下层驱动程序接收到的分组。 微软公司为开发者编写驱动程序提供了DDK(Device DriverKit)工具包。根据不同的平台分为NT DDK和Windows 95 DDK。在DDK中,包含了依据NDIS规范开发网络驱动程序的工具。关于DDK的介绍,以及用DDK开发驱动程序的步骤参见参考文献【1】。 二.协议驱动程序的结构 协议驱动程序包括驱动程序对象和驱动程序所提供的入口函数集合。一个协议驱动程序必须绑定到小端口网卡驱动程序或者中介驱动程序(此时相当于一个虚拟的小端口网卡驱动程序)之上,它可以绑定到一个或者多个底层驱动程序之上。协议驱动程序在其下边界提供一系列的ProtocolXxx接口函数,并通过NDIS发送和接收网络分组。NDIS代理协议驱动程序与绑定到其底层的小端口驱动程序或中介驱动程序进行通信,并调用底层驱动程序上边界提供的MiniportXxx接口函数完成网络分组的收发。协议驱动程序的初始入口函数DriverEntry与小端口网卡驱动程序的形式相同,在DriverEntry中所完成的工作也很相似,主要是向NDIS库登记它的上、下边界函数(关于小端口驱动程序DriverEntry的编写参见参考文献【1】)。协议驱动程序通过调用NdisOpenAdapter函数打开绑定的网卡。协议驱动程序可以提供ProtocolBindAdapter入口函数(该入口函数是可选的),在该入口函数中进行打开底层网卡的操作,并分配相应的系统资源。协议驱动程序也可以不提供ProtocolBindAdapter函数,而将这些操作放入到DriverEntry中完成。与小端口驱动程序调用NdisMRegisterMiniport函数向NDIS登记小端口驱动程序信息相对应,协议驱动程序调用NdisRegisterProtocol来登记它的ProtocolXxx函数等协议驱动程序的信息。 下面是协议驱动程序的ProtocolXxx入口函数的简要描述,这些入口函数决定了协议驱动程序的功能和特性,应该由开发者根据自己的需要实现。 ProtocolBindAdapter 当网卡适配器(可能是逻辑适配器)可用时被NDIS自动调用将协议驱动程序绑定到网卡上。 ProtocolUnbindAdapter 当网卡适配器不可用时被NDIS自动调用解除绑定。 ProtocolOpenAdapterComplete 协议驱动程序在调用NdisOpenAdapter函数时若返回NDIS_STATUS_PENDING,NDIS将调用该入口函数通知驱动程序打开网卡操作已经完成。 ProtocolReceive NDIS调用该入口函数通知协议驱动程序,下层驱动程序接收到的网络分组。 ProtocolReceiveComplete NDIS调用该入口函数通知协议驱动程序可以对刚刚使用ProtocolReceive函数报告的分组进行后处理了。 ProtocolTransferComplete 协议驱动程序在调用NdisTransferData函数时若返回NDIS_STATUS_PENDING,NDIS将调用该入口函数通知驱动程序剩余的数据已被拷贝进指定的缓冲区中。 ProtocolReceivePacket 这是一个可选的入口函数。若下层的网卡驱动程序一次可以向上递交多个分组,则协议驱动程序需实现该函数。NDIS将调用该入口函数,通知驱动程序下层一次递交了多个分组。 ProtocolSendComplete 协议驱动程序在调用NdisSend函数发送分组时若返回NDIS_STATUS_PENDING,NDIS将调用该入口函数通知驱动程序发送分组操作完毕。 ProtocolResetComplete 协议驱动程序调用NdisReset函数复位网卡时若返回NDIS_STATUS_PENDING,NDIS将调用该入口函数通知驱动程序复位操作完毕。 ProtocolRequestComplete 协议驱动程序调用NdisRequest函数查询/设置网卡时若返回NDIS_STATUS_PENDING,NDIS将调用该入口函数通知驱动程序操作已经完成。 ProtocolStatus NDIS调用该入口函数处理下层网卡驱动程序报告的网卡状态的改变。 ProtocolStatusComplete NDIS调用该入口函数表示NDIS或网卡驱动程序发起的(状态改变引发的)复位操作结束。 这些入口函数并不都是一定要实现的,开发者可以选择实现那些他所需要的函数,那些未实现的函数在登记网卡时应置为空函数指针。但有些函数如ProtocolReceive是必须实现的。由于在向NDIS库登记驱动程序时传递的实际上是这些函数的指针,所以这些函数也不一定要取成上表所列的名称。 三.协议驱动程序开发实例:连接ATM网段和传统以太网的网桥驱动程序 这个协议驱动程序运行在一台插有两块网卡(一块以太网卡、一块ATM网卡,每一块网卡连接一个网段)的PC上,驱动程序同时绑定在两块网卡的驱动程序上(ATM网卡驱动程序内具LAN仿真功能)。网桥驱动程序有一个自学习的过程,每块网卡侦听其所连网段上的所有分组(MAC帧),并根据源MAC地址判断该网段上存在哪些主机。由于ATM仿真LAN网段的特性与以太网段不同,网桥收集到的以太网段上的MAC地址,必须作为LAN仿真客户代理的MAC地址加入ATM网卡驱动程序维护的信息库。当一块网卡收到分组时,首先判断其目的地址是否在本网段,如果是则丢弃,否则从另一网卡发到另一个网段上。从上述描述中我们可以看出:网桥驱动程序加载在ATM/以太网卡驱动程序之上,同时没有任何其它驱动程序绑定在它之上。它完成协议驱动程序所具有的功能。 下面是该驱动程序主要接口函数的伪码说明: // 全局变量用于保存NDIS维护的协议驱动程序句柄 static NDIS_HANDLE NdisProtocolHandle; NTSTATUS DriverEntry ( IN PDRIVER_OBJECT drvobj, /*I/O系统创建的驱动程序对象 */ IN PUNICODE_STRING regpath ) /*存储驱动程序参数的注册表路径*/ { NDIS_STATUS Status; NDIS_PROTOCOL_CHARACTERISTICS chr; // 协议驱动程序特性变量 NDIS_STRING name; name.Buffer = DEV_NAME; // 协议驱动程序的名称 name.Length = DEV_NAME_LEN; // 协议驱动程序的名称的长度 name.MaximumLength = DEV_NAME_LEN; NdisZeroMemory (&chr, sizeof (chr)); chr.MajorNdisVersion = BRIDGE_NDIS_MAJOR_VERSION; // NDIS主版本号 chr.MinorNdisVersion = BRIDGE_NDIS_MINOR_VERSION; // NDIS次版本号 chr.Name.Length = name.Length; chr.Name.Buffer = (PVOID)name.Buffer; // BridgeXxx等函数就是ProtocolXxx入口函数,其形式必须符合NDIS规定的函数接口规范 chr.OpenAdapterCompleteHandler = BridgeOpenAdapterComplete; chr.CloseAdapterCompleteHandler = BridgeCloseAdapterComplete; chr.ReceiveHandler = BridgeReceive; chr.ReceiveCompleteHandler = BridgeReceiveComplete; chr.TransferDataCompleteHandler = BridgeTransferDataComplete; chr.SendCompleteHandler = BridgeSendComplete; chr.ResetCompleteHandler = BridgeResetComplete; chr.RequestCompleteHandler = BridgeRequestComplete; chr.StatusHandler = BridgeStatus; chr.StatusCompleteHandler = BridgeStatusComplete; chr.BindAdapterHandler = BridgeBindAdapter; chr.UnbindAdapterHandler = BridgeUnbindAdapter; // 登记为一个协议驱动程序,这是该驱动程序与小端口驱动程序质的不同。 NdisRegisterProtocol ( &Status, &NdisProtocolHandle, &chr, (UINT)sizeof(chr) ); return Status; // 返回登记协议驱动程序成功与否 } VOID BridgeBindAdapter( OUT PNDIS_STATUS status, // 返回绑定成功与否的状态变量 IN NDIS_HANDLE bindingcontext, // NDIS传递的绑定的环境 IN PNDIS_STRING devname, // 驱动程序名字 IN PVOID SystemSpecific1, // 需要传给某些NDIS调用,对驱动程序不透明 IN PVOID SystemSpecific2) // 保留给系统使用 { 为协议驱动程序分配资源; 调用NdisOpenAdapter函数打开绑定的网卡驱动程序; 如果打开操作返回状态为NDIS_STATUS_PENDING,则函数返回; 如果打开操作返回状态为NDIS_STATUS_SUCCESS,则调用BridgeOpenAdapterComplete()入口函数; 否则释放分配的资源并返回错误信息; } BridgeOpenAdapterComplete( IN NDIS_HANDLE bindcontext, // 同上 IN NDIS_STATUS status, // NDIS传递的打开网卡操作的状态 IN NDIS_STATUS openerr ) // NDIS传递的打开网卡操作的错误信息 { 如果打开操作无误,则进行网卡的初始化操作; // 将以太网卡设置成混杂模式,以接收以太网段上所有的数据分组 // 或者进行ATM网卡必要的初始化操作 调用NdisCompleteBindAdapter函数通知NDIS打开网卡操作完成; } NDIS_STATUS BridgeReceive(…) // 参数略 { 根据接收的网卡接口、接收分组的源地址和目的地址及驱动程序收集的MAC地址信息表判断是转发该分组还是抛弃; 如果决定抛弃该分组,则返回NDIS_STATUS_NOT_ACCEPTED; 如果决定转发该分组 { 如果NDIS传递的数据缓冲区中包含分组的全部数据 则调用NdisSend函数将分组从另一个网卡上发送出去; 否则调用NdisTransferData函数将剩余的数据拷贝进指定的缓冲区中。 /* 拷贝完成时,NDIS将调用驱动程序的BridgeTransferDataComplete入口函数。在该入口函数中调用NdisSend函数完成将分组从另一个网卡上发送出去的操作 */ } 由于网桥驱动程序的特点,其他入口函数都很简单,这里就不一一分析了。 四.结束语: 遵循NDIS规范开发的网络驱动程序具有良好的层次结构。NDIS负责协调不同层次的驱动程序的工作,完成各个网络层相互之间的接口,使开发者可以专注于具体协议的实现。因而依据NDIS规范开发的驱动程序具有兼容性强、效率高、稳定性好的优点。 参考文献: 【1】《用NT DDK开发网卡驱动程序》 杨凯锋等 【2】Microsoft Corp., Windows NT DDK documentation. 【3】Art Baker著,科欣翻译组译,<<Windows NT设备驱动程序设计指南>>,机械工业出版社,西蒙与舒斯特国际出版公司。 Developing Network Protocol Drivers Using NT DDK Wang DaWei, Yang KaiFeng, Hu Yi, Hong PeiLin Abstract: This article briefly describes the method in developing network protocol drivers based on Windows NT platform, and a concrete example is provided to make the mainpoints clear. Keyword: Protocol driver, Device Driver Kit,NDIS |