📄 ndis开发.htm
字号:
NIC驱动程序,其上层是一个向上层提供TDI(传输驱动程序接口)支持的传输驱动程序(也可能是多层结构)。从理论上讲,一个中间层驱动程序也可以是基于其他中间层驱动程序或作为其他中间层驱动程序的低层出现的,尽管这种方案未必能展现更好的性能。<BR>中间层驱动程序的一个示例是LAN仿真中间层驱动程序,其上层是一个早期传输驱动程序,下层是一个非LAN介质的微端口
NIC驱动程序。该驱动程序从上层接收LAN格式的数据包并将其转换为本地网卡的介质格式,然后将其发送到那个NIC的NDIS
微端口。接收数据时,该驱动程序将低层网卡驱动程序送来的数据包转换为LAN兼容格式,最后向上层传输驱动程序提交这些转换过的数据包。<BR>例如,NDISWAN就具有一些上述特征。NDISWAN将数据包从上层的传输LAN格式转换为WAN数据包格式,或者将数据包从低层的网卡驱动WAN格式转换为LAN数据包格式。另外,如果低层NIC硬件不支持这些功能,那么NDISWAN也可提供诸如压缩、加密和端对端协议(PPP)等的数据格式化功能。NDISWAN为在NDIS
API和网卡驱动程序之间进行通信提供了一个专用接口,同时,NDISWAN也将协议绑定映射为活动连接请求。<BR>另一个中间层驱动程序的例子是ATM
LANE
(LAN仿真)驱动程序,它将数据包从上层无连接的传输格式转换为下层面向连接的网卡支持的ATM格式。<BR>图1.1说明了中间层驱动程序结构</FONT></P>
<P><FONT face=Verdana size=2></FONT> </P>
<P><FONT face=Verdana size=2>图表 1 中间层驱动程序结构</FONT></P>
<P><FONT face=Verdana
size=2>NDIS中间层驱动程序在NDIS中起着转发上层驱动程序送来的数据包,并将其向下层驱动程序发送的接口功能。当中间层驱动程序从下层驱动程序接收到数据包时,它要么调用NdisMXxxIndicateReceive函数,要么调用NdisMindicateReceivePacket函数向上层指示该数据包。<BR>中间层驱动程序通过调用NDIS打开和建立一个对低层NIC驱动程序或者NDIS中间层驱动程序的绑定。中间层驱动程序提供MiniportSetInformation和MiniportQueryInformation函数来处理高层驱动程序的设置和查询请求,某些情况下,可能还要将这些请求向低层NDIS驱动程序进行传递,如果其下边界是面向无连接的可通过调用NidsRequest实现这一功能,如果其下边界是面向连接的则通过调用NidsCoRequest实现该功能。<BR>中间层驱动程序通过调用NDIS提供的函数向网络低层NDIS驱动程序发送数据包。例如,下边界面向无连接的中间层驱动程序必须调用NdisSend或NdisSendPackets来发送数据包或者包数组,而在下边界面向连接的情况下就必须调用NdisCoSendPackets来发送包数组数据包。如果中间层驱动程序是基于非NDIS
NIC驱动程序的,那么在调用中间层驱动程序的MiniportSend或Miniport(Co)SendPackets函数之后,发送接口对NDIS将是不透明的。<BR>NDIS提供了一组隐藏低层操作系统细节的NdisXxx函数和宏。例如,中间层驱动程序可以调用NdisMInitializeTimer来创建同步时钟,可以调用NdisInitializeListHead创建链表。中间层驱动程序使用符合NDIS标准的函数,来提高其在支持Win32接口的微软操作系统上的可移植性。</FONT></P>
<P><FONT face=Verdana size=2>1.2 NDIS中间层驱动程序的用途</FONT></P>
<P><FONT face=Verdana size=2>NDIS中间层驱动有几个方面的用途,包括:<BR>局域网仿真(LAN Emulation)
– NDIS中间层驱动可以使一个非局域网NIC驱动(如,ATM)犹如一个局域网NIC驱动(如,Ethernet)。<BR>包过滤(Packet
Filtering) -
可以拦截和修改高层TDI(传输驱动程序)和底层NIC驱动程序之间的网络包(Packets):<BR>通过或过滤掉(Pass/Drop
Packets)<BR>延迟或重新排序( Delay/Reorder Packets)<BR>加密或解密(Packet
Encryption/Decryption)<BR>压缩或解压(Packet
Compression/Decompression)<BR>路由包(Route Packets):<BR>NAT网络地址转换(Network
Address Translation)<BR>LBFO负载平衡和失效替换(Adapter Load Balancing And
Fail-Over)</FONT></P>
<P><FONT face=Verdana size=2>1.3 NDIS中间层驱动程序的开发环境</FONT></P>
<P><FONT face=Verdana size=2>OS : Microsoft Windows 2000
Server<BR>IDE : Microsoft Visual C++ V6.0
<BR>DDK : Windows 2000 Device Drivers Kit</FONT></P>
<P><FONT face=Verdana size=2>2 NDIS中间层驱动程序的开发</FONT></P>
<P><FONT face=Verdana size=2>2.1 可分页和可丢弃代码</FONT></P>
<P><FONT face=Verdana
size=2>每一个MiniportXxx函数或ProtocolXxx函数都运行在一个特定的IRQL上,在中间层驱动程序中这些函数可使用的IRQL从PASSIVE_LEVEL一直到DISPATCH_LEVEL(包括DISPATCH_LEVEL)。<BR>总是运行在IRQL
PASSIVE_LEVEL上的中间层驱动程序函数可通过调用NDIS_PAGEABLE_FUNCTION宏将其标记为可分页代码。驱动程序设计者应尽可能的将程序代码设计为可分页的,为那些必须驻留内存代码释放系统空间。运行在IRQL
PASSIVE_LEVEL的驱动程序函数,当其既不调用运行在IRQL >=DISPATCH_LEVEL的任何函数,也不被运行在IRQL
>=DISPATCH_LEVEL的任何函数调用时,可将其标注为可分页的。例如,一个获取自旋锁的函数,而获取自旋锁将促使获取线程提升到IRQL
DISPATCH_LEVEL上运行。一个运行在IRQL
PASSIVE_LEVEL的函数,如ProtocolBindAdapter,如果被标注为可分页的,就不能再调用运行在IRQL
>=DISPATCH_LEVEL NDIS的函数。关于运行在IRQL 上的NDIS函数的更多信息,请参阅在线DDK的“Network
Drivers
Reference”,其中列出了每一个NdisXxx函数的IRQL。<BR>中间层驱动程序的DriverEntry函数以及只在DriverEntry中调用的代码,应该用NDIS_INIT_FUNCTION宏将其设定为仅用作系统初始化功能。假定NDIS_INIT_FUNCTION宏标识的代码仅在系统初始化时运行,这样该部分代码将只有在初始化时才会被映射,在DriverEntry返回后,NDIS_INIT_FUNCTION宏标识的这部分代码将被丢弃。</FONT></P>
<P><FONT face=Verdana size=2>2.2 共享资源的访问同2.3 步</FONT></P>
<P><FONT face=Verdana
size=2>如果驱动程序分配的资源能够被两个驱动程序函数同时共享,或者中间层驱动程序能够运行在SMP(对称多处理)机器上,这样相同的驱动程序函数能够从多个处理器同时访问该资源,那么对这些共享资源的访问必须进行同步。例如,驱动程序维持一个共享队列,使用自旋锁来对队列的访问进行同步,自旋锁在队列创建之前调用NdisAllocateSpinLock进行初始化。<BR>当然,也不必过分地保护共享资源,例如,对于队列,一些读操作不进行串行化也是可以成功执行的。但任何针对队列链接的操作都必须进行同步。自旋锁应该尽量少使用,并且每次都要尽可能缩短其使用的时间。关于自旋锁更深入的讨论可参阅“Kernel_Mode
Drivers Design Guide”。</FONT></P>
<P><FONT face=Verdana size=2>2.4 中间层驱动程序的DriverEntry函数</FONT></P>
<P><FONT face=Verdana
size=2>为了使加载程序能够准确地识别,必须将中间层驱动程序的初始入口点明确地指定为DriverEntry的形式。所有其他的驱动程序导出函数,像这里所描述的MiniportXxx和ProtocolXxx函数,由于其地址传给了NDIS,因此在设计时可由开发者任意指定名称。任何内核模式驱动程序DriverEntry的定义具有以下形式:<BR>NTSTAUS<BR>DriverEntry(<BR> IN
PDRIVER_OBJECT DriverObject,<BR> IN PUNICODE_STRING
RegistryPath<BR>);<BR>如果除了ProtocolXxx之外,驱动程序还导出一组标准的内核模式驱动程序函数,那么必须将这些标准函数的地址写入要传给DriverEntry的驱动程序对象中。<BR>在中间层驱动程序中,DriverEntry至少应该完成以下工作:<BR>调用NdisMInitializeWrapper并保存在NdisWrapperHandle中返回的句柄;<BR>传递上一步保存的句柄,调用NdisIMRegisterLayeredMiniport注册驱动程序的MiniportXxx函数;<BR>如果驱动程序随后要绑定到低层NDIS驱动程序上,则调用NdisRegisterProtocol注册驱动程序的ProtocolXxx函数;<BR>如果驱动程序导出了MiniportXxx和ProtocolXxx函数,那么调用NdisIMAssociateMiniport向NDIS通告有关驱动程序的微端口低边界和协议高边界信息;<BR>DriverEntry能够为中间层驱动程序分配的所有共享资源初始化自旋锁,例如驱动程序用于跟踪运行中的连接和发送任务的构件和内存区。<BR>当DriverEntry不能为驱动程序分配用于网络I/O操作的所有资源时,它就应该释放先前已经分配的任何资源并返回一个适当的错误状态。例如,如果DriverEntry已经调用了NdisMInitializeWrapper函数,那么当后续操作出错时必须调用NdisTerminateWrapper复位系统状态。<BR>中间层驱动程序的DriverEntry函数能够执行一些全局初始化操作。然而,如果驱动程序提供了在2.4节所描述的,实现对低层设备的打开和绑定功能的ProtocolBindAdapter函数,那么驱动程序就能够让ProtocolBindAdapter来分配绑定相关的系统资源,ProtocolBindAdapter根据要求为DeviceName设备进行资源分配和绑定操作。DriverEntry必须初始化该包裹程序并注册微端口驱动程序。如果中间层驱动程序导出了一组ProtocolXxx函数的话,也要注册协议驱动程序。<BR>如果中间层驱动程序仅向NDIS导出了一组MiniportXxx函数,只要向NDIS库注册这些函数即可,如下所述。</FONT></P>
<P><FONT face=Verdana size=2>2.4.1 注册NDIS中间层驱动程序</FONT></P>
<P><FONT face=Verdana
size=2>NDIS中间层驱动程序必须在DriverEntry函数的环境中向NDIS注册其MiniportXxx函数和ProtocolXxx函数。驱动程序通过调用NdisIMRegisterLayeredMiniport对MiniportXxx函数进行注册,该调用导出中间层驱动程序的MiniportXxx函数。当虚拟NIC被初始化时,以及随后当驱动程序将要基于该NIC接收和发送数据包时,注意驱动程序的控制过程。<BR>对于指定的虚拟NIC,NDIS将在NdisIMInitializeDeviceInstance的环境中调用中间层驱动程序的MiniportInitialize函数对其进行初始化操作。如果中间层驱动程序导出了多个虚拟NIC,那么为使其可用于网络请求,驱动程序必须为每一个NIC调用NdisIMInitializeDeviceInstance函数进行初始化。这样可根据网络业务量,生成相应的数量的虚拟NIC。<BR>如果NDIS中间层驱动程序也导出了一组ProtocolXxx函数,则必须调用相应的NdisRegisterProtocol函数向NDIS库注册这些函数。</FONT></P>
<P><FONT face=Verdana
size=2>2.4.1.1 注册中间层驱动程序的Miniport<BR>中间层驱动程序通过调用NdisIMRegisterLayeredMiniport导出MiniportXxx函数。<BR>NdisIMRegisterLayeredMiniport以如下方式进行声明:<BR>NDIS_STATUS<BR>NdisIMRegisterLayeredMiniport(<BR> IN
NDIS_HANDLE NdisWrapperHandle,<BR> IN PNDIS_MINIPORT_CHARACTERISTICS
MiniportCharacteristics,<BR> IN UINT
CharacteristicsLength,<BR> OUT PNDIS_HANDLE
DriverHandle<BR>);<BR>中间层驱动程序必须保存NdisIMRegisterLayeredMiniport返回的DriverHandle句柄,并且在驱动程序中调用NdisIMInitializeDeviceInstance函数,请求中间层驱动程序的MiniportInitialize函数对虚拟NIC进行初始化时,将该句柄输入NDIS。当中间层驱动程序成功的绑定到一个或多个低层NIC驱动程序上或者当其绑定在一个非NIC设备驱动程序上后,将调用NdisIMInitializeDeviceInstance函数,使得中间层驱动程序可以初始化Miniport组件来接受虚拟NIC上的I/O请求。<BR>NdisWrapperHandle句柄是由先前的NdisMInitializeWrapper函数返回的。<BR>中间层驱动程序必须完成以下操作:<BR>用NdisZeroMemory函数零初始化一个NDIS_MINIPORT_CHARACTERISTICS类型的构件;<BR>保存所有驱动程序导出的强制性的和非强制的MiniportXxx函数的地址,并将所有非强制的MiniportXxx入口指针设为NULL;<BR>当其他类型的NDIS驱动程序的有效主版本是0x03、0x04、0x05时,如果要导出任何新的V4.0或
V5.0的MiniportXxx函数,中间层驱动程序的主版本必须是4.0并提供4.0或5.0版的MiniportCharacteristics构件。<BR>对于MiniportCharacteristics,如果驱动程序不用导出MiniportXxx函数,则必须将其设为NULL,但是如果要导出函数的话,则必须将其设为某个有效的MiniportXxx函数地址值。<BR>HaltHandler<BR>当低层NIC超时并且NDIS已经中止了网卡驱动程序时,或者操作系统正在执行一个可控的系统关闭操作时,NDIS将调用该函数。<BR>InitializeHandler<BR>作为中间层驱动程序调用NdisIMInitializeDeviceInstance初始化微端口的结果,调用该函数对虚拟网卡进行初始化。<BR>QueryInformationHandler<BR>该函数接收OID_XXX请求,这个请求来自于高层驱动程序(用NdisRequestQueryInformation请求类型作参数,调用NdisRequest)。<BR>ResetHandler<BR>在高层协议驱动程序调用NdisReset的指示下,NDIS能够调用中间层驱动程序的MiniportReset函数。然而,协议驱动程序并不启动复位功能,通常,NDIS启动低层网卡驱动程序复位操作并调用中间层驱动程序的ProtocolStatus和ProtocolStatusComplete函数通知中间层驱动程序低层微端口正在复位网卡。<BR>SetInformationHandler<BR>该函数接收OID_XXX请求,这个请求来自于高层驱动程序(用NdisRequestSetInformation请求类型作参数,调用NdisRequest)。<BR>SendHandler<BR>NDIS调用该函数向低层网卡(或设备)驱动程序发送单个数据包。如果中间层驱动程序不支持MiniportSendPackets函数,那么MiniportSend函数(或MiniportWanSend函数)是必须提供的。除非中间层驱动程序总是基于那些每次只发送单一数据包或将自身绑定到低层WAN
NIC的驱动程序,否则MiniportSendPackets函数应该总是以SendPacketsHander方式提供,而不是以该SendHander处理程序方式提供,关于这方面的更多讨论请参阅2.9节。<BR>SendPacketsHander<BR>该函数接收用于指定网上传输数据包的包描述符指针数组。除非中间层驱动程序绑定到低层WAN
NIC驱动程序上并提供了MiniportWanSend函数,否则驱动程序应提供对MiniportSendPackets而不是MiniportSend函数支持。换句话说,不管中间层驱动程序是基于每次只能传送单个数据包的网卡驱动程序;还是基于每次可以传送多个数据包的网卡驱动程序,也不管中间层驱动程序是基于每次只能传送单个数据包的协议驱动程序;还是基于每次可以传送多个数据包的协议驱动程序,MiniportSendPackets函数都能实现最好的性能,关于这方面的更多讨论可参阅2.9节。<BR>TransferDataHandler<BR>该函数用于传输在前视缓冲区中没有指示的接收数据包的剩余部分,该前视缓冲区由中间层驱动程序传递给NdisMXxxIndicateReceive函数。这个被指示的数据包可以是中间层驱动程序的ProtocolReceive函数或者是ProtocolReceivePackets处理程序接收的转换数据包。如果中间层驱动程序通过调用(除NdisMWanIndicateReceive之外)NdisMXxxIndicateReceive函数向上层驱动程序指示接收数据包,那么该处理程序是必须提供的。如果中间层驱动程序总是通过调用NdisMIndicateReceivePacket向上层驱动程序指示接收数据包,则不必要提供MiniportTransferData函数。<BR>ReturnPacketHandler<BR>该函数接收返回包描述符(该包描述符先前通过NdisMIndicateReceivePacket调用向高层指示),从而释放指示给高层驱动程序的资源的控制权。在高层驱动程序处理完所有指示之后,中间层驱动程序分配的描述符及所描述的资源将返回给MiniportReturnPacket函数。当然,如果中间层驱动程序总是通过调用介质相关的NdisMXxxIndicateReceive函数向上层指示数据包,或者在调用NdisMIndicateReceivePacket之前总是将OOB数据块(与每一个描述符相关的)状态设置为NDIS_STATUS_RESOUCES,则不必提供MiniportReturnPacket函数。<BR>CheckForHangHandler<BR>该函数以NDIS规定的时间间隔调用,或者以中间层驱动程序规定的时间间隔运行,二者只居其一。如果提供了该处理程序,那么MiniportCheckForHang函数每两秒钟被调用一次(或者按驱动程序要求的间隔调用)。关于MiniportCheckForHang函数的更多信息可参见在线DDK的“Network
Drivers
Reference”或者该手册的第二部分。通常情况下,由于驱动程序无法确定低层网卡是否悬挂,NDIS中间层驱动程序并不提供对MiniportCheckForHang函数的支持。如果驱动程序基于状态不能到达NDIS的非NDIS驱动程序,中间层驱动程序可能会提供对该处理程序的支持。<BR>由于驱动程序并不管理中断设备,不为使用中的IRQL分配缓冲区,或者因为NDIS并不调用MiniportReconfigure函数(在ReconfigureHandler情况下),因此中间层驱动程序不会提供以下的几个微端口处理程序函数。<BR>DisableInterruptHandler<BR>EnableInterruptHandler<BR>HandleInterruptHandler<BR>ISRHandler<BR>AllocateCompleteHandler<BR>ReconfigureHandler</FONT></P>
<P><FONT face=Verdana
size=2>2.4.1.2 注册中间层驱动程序的协议<BR>中间层驱动程序通过调用NdisRegisterProtocol向NDIS注册ProtocolXxx函数。<BR>NdisRegisterProtocol以如下方式进行声明:<BR>VOID<BR>NdisRegisterProtocol(<BR>OUT
PNDIS_STATUS Status,<BR>OUT PNDIS_HANDLE NdisProtocolHandler,<BR>IN
NDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,<BR>IN UINT
CharacteristicsLength<BR>);<BR>在调用NdisRegisterProtocol函数之前,中间层驱动程序必须完成以下操作:<BR>零初始化一个NDIS_PROTOCOL_CHARACTERISTICS类型的结构。中间层驱动程序能够使用4.0或者5.0版的ProtocolCharacteristics结构。协议驱动程序必须支持即插即用功能,因此NDIS不再支持3.0版的协议驱动程序。<BR>保存所有驱动程序支持的强制性的和非强制的ProtocolXxx函数的地址;<BR>该调用的返回句柄NdisProtocolHandler对中间层驱动程序是不透明的,中间层驱动程序必须保存该句柄,并在将来NDIS中间层驱动程序的协议部分的函数调用中作为输入参数传递,例如,打开低层适配器的函数调用。<BR>2.4.1.2.1 注册下边界面向无连接的中间层驱动程序的ProtocolXxx函数<BR>下边界面向无连接中间层驱动程序能够导出的(包括可选的和必须的)协议处理程序函数如下所列:<BR>BindAdapterHandler<BR>这是一个必须提供的函数。NDIS调用该函数请求中间层驱动程序绑定到低层NIC或虚拟NIC上,NIC名作为该处理程序的一个参数传递。关于动态绑定的更多的信息参见2.4节.<BR>UnbindAdapterHandler<BR>这是一个必须提供的函数。NDIS调用ProtocolUnbindAdapter释放对低层NIC或虚拟NIC的绑定,网卡名作为该处理程序的一个参数传递。当绑定成功关闭时,ProtocolUnbindAdapter将调用NdisCloseAdapter函数并释放相关资源。<BR>OpenAdapterCompleteHandler<BR>这是一个必须提供的函数。如果中间层驱动程序对NdisOpenAdapter的调用返回NDIS_STATUS_PENDING,则接着调用ProtocolOpenAdapterComplete来完成绑定。<BR>CloseAdapterCompleteHandler<BR>这是一个必须提供的函数。如果中间层驱动程序对NdisCloseAdapter的调用返回NDIS_STATUS_PENDING,则接着调用ProtocolCloseAdapterComplete来完成绑定释放。<BR>ReceiveHandler<BR>这是一个必须提供的函数。ProtocolReceive函数以指向包含网络接收数据的前视缓冲区的指针为参数被调用执行。如果该缓冲区包含的不是完整的网络数据包,ProtocolReceive以数据包描述符作为参数,调用NdisTransferData接收该数据包的剩余部分。如果低层驱动程序调用NdisMIndicateReceivePacket指示接收数据包,那么传给ProtocolReceive函数的前视缓冲区将总是完整的网络数据包。<BR>ReceivePacketHandler<BR>这是一个可选函数。如果中间层驱动程序所基于的NIC驱动程序指示的是数据包描述符指针数组,或者调用NdisMIndicateReceivePacket函数指示接收带外数据,那么驱动程序应提供ProtocolReceivePacket函数。如果开发者不能确定中间层驱动程序的执行环境,也应提供函数,因为在能够产生多包指示的低层NIC驱动程序上,中间层驱动程序将获得更好的性能。<BR>ReceiveCompleteHandler<BR>这是一个必须提供的函数。当先前指示给ProtocolReceive
函数的数据包被处理后,将调用ProtocolReceiveComplete函数。<BR>TransferDataCompleteHandler<BR>如果ProtocolReceive要调用NdisTransferData函数,则必须提供该处理程序。如果复制接收数据包剩余部分的NdisTransferData函数调用返回NDIS_STATUS_PENDING,那么当传输操作完成后,将调用ProtocolTransferDataComplete函数。<BR>ResetCompleteHandler<BR>这是一个必须提供的函数。当NdisReset函数(返回NDIS_STATUS_PENDING)调用启动的复位操作完成时,ProtocolResetComplete函数将被调用。通常情况下,中间层驱动程序并不调用NdisReset,但由于上层驱动程序可能会调用该函数,所以中间层驱动程序可能只是向低层NDIS驱动程序转发复位请求而已。<BR>RequestCompleteHandler<BR>这是一个必须提供的函数。当NdisRequest函数(返回NDIS_STATUS_PENDING)调用启动的查询或设置操作完成时,ProtocolRequestComplete函数将被调用。<BR>SendCompleteHandler<BR>这是一个必须提供的函数。对每一个调用NdisSend函数传输的数据包,当其返回NDIS_STATUS_PENDING作为发送状态时,将调用ProtocolSendComplete函数完成发送操作。如果调用NdisSendPackets发送一组数据包,那么对于每一个传送给NdisSendPackets的数据包,ProtocolSendComplete将被调用一次。中间层驱动程序仅仅根据送给ProtocolSendComplete的状态参数就能确定调用NdisSendPackets函数的发送操作的状态。<BR>StatusHandler<BR>这是一个必须提供的函数。NDIS用低层NIC驱动程序发起的状态通知来调用ProtocolStatus函数。<BR>StatusCompleteHandler<BR>这是一个必须提供的函数。NDIS调用ProtocolStatusComplete函数来指示状态改变操作已经完成,该状态先前被指示给ProtocolStatus函数。<BR>PnPEventHandler<BR>这是一个必须提供的函数。NDIS调用ProtocolPnPEvent来指示即插即用事件或电源管理事件。更多信息可参见2.9节。<BR>UnloadHandler<BR>这是一个可选函数。NDIS调用ProtocolUnload函数来响应用户卸载中间层驱动程序的请求。对于每一个绑定的适配器,NDIS在调用ProtocolUnbindAdapter之后,将调用ProtocolUnload函数卸载驱动程序。ProtocolUnload执行驱动程序决定的清除操作。<BR>2.4.1.2.2 注册下边界面向连接的中间层驱动程序的ProtocolXxx函数<BR> 下边界面向连接的中间层驱动程序必须注册如下的面向无连接的和面向连接的微端口所共有的协议处理程序函数:<BR>BindHandler<BR>UnbindHandler<BR>OpenAdapterCompleteHandler<BR>CloseAdapterCompleteHandler<BR>ReceiveCompleteHandler<BR>ResetCompleteHandler<BR>RequestCompleteHandler<BR>StatusCompleteHandler<BR>PnPEventHandler<BR>这些函数已经在上一小节作了汇总。<BR>下边界面向连接的中间层驱动程序还必须注册如下的面向连接协议函数:<BR>CoSendCompleteHandler<BR>这是一个必须提供的函数。对于传给NdisCoSendPackets的每一个数据包都要调用一次ProtocolCoSendComplete函数。中间层驱动程序仅仅根据送给ProtocolCoSendComplete的状态参数就能确定调用NdisCoSendPackets的发送操作的状态。<BR>CoStatusHandler<BR>这是一个必须提供的函数。NDIS用低层NIC驱动程序发起的状态通知来调用ProtocolCoStatus函数。<BR>CoReceivePacketsHandler<BR>这是一个必须提供的函数。当绑定的面向连接NIC驱动程序或者集成微端口呼叫管理器(MCM)通过调用NdisMCoIndicateReceivePackets指示一个指针数组时,NDIS将调用中间层驱动程序的ProtocolCoReceivePacketHandler函数。<BR>CoAfRegisterNotifyHandler<BR>如果中间层驱动程序是一个使用呼叫管理器或MCM驱动程序的呼叫管理服务的面向连接客户,那么就必须注册ProtocolCoAfRegisterNotify函数,该函数用来确定中间层驱动程序能否使用呼叫管理器或MCM(已经通过注册地址族,公布其服务)的服务。</FONT></P>
<P><FONT face=Verdana size=2>2.5 中间层驱动程序的动态绑定</FONT></P>
<P><BR><FONT face=Verdana
size=2>中间层驱动程序必须提供ProtocolBindAdapter和ProtocolUnbindAdapter函数以支持对低层NIC的动态绑定。当NIC可用时,NDIS调用中间层驱动程序(能够绑定到NIC)的ProtocolBindAdapter函数实现动态绑定操作。<BR> VOID<BR> ProtocolBindAdapter(<BR> OUT
PNDIS_STATUS Status,<BR> IN NDIS_HANDLE
BindContext,<BR> IN PNDIS_STRING
DeviceName,<BR> IN PVOID
SystemSpecific1,<BR> IN PVOID
SystemSpecific2<BR> );<BR>BindContext句柄代表绑定请求的NDIS环境,中间层驱动程序必须保存该句柄,并且在中间层驱动程序完成绑定相关操作,并准备接受发送请求时,将该句柄作为NdisCompleteBindAdapter的参数返回NDIS。<BR>绑定时的操作包括为该绑定分配NIC相关的环境区域并进行初始化,接着调用NdisOpenAdapter绑定到DeviceName参数指定的适配器。DeviceName可以是低层NIC驱动程序管理的NIC,也可以是介于被调用的中间层驱动程序和管理适配器的NIC驱动程序之间,由中间层NDIS驱动程序导出的控制传输请求的虚拟NIC。通常情况下,可能仅有一个基于NIC驱动程序的中间层NDIS驱动程序,实现早期的高层协议驱动程序支持的介质格式和低层NIC驱动程序支持的介质格式之间的转换。<BR>注意,中间层驱动程序向NdisOpenAdapter传递的DeviceName必须与先前向ProtocolBindAdapter函数传递的DeviceName(一个Unicode字符串缓冲区指针)相同,驱动程序不能复制该指针并将指针副本传递给NdisOpenAdapter函数。<BR>中间层驱动程序能够在已分配的绑定相关环境区域或者另一个驱动程序可访问位置保存BindContext。如果NdisOpenAdapter返回NDIS_STATUS_PEDDING,则必须保存BindContext值,在这种情况下,中间层驱动程序直到打开适配器的操作完成,并且其ProtocolOpenAdapterComplete函数已调用完成,才能调用NdisCompleteBindAdapter函数完成绑定工作。BindContext必须从某个已知位置获得,并由ProtocolOpenAdapterComplete传递给NdisCompleteBindAdapter函数。<BR>如果中间层驱动程序将适配器相关信息存入注册表,那么SystemSpecific1将指向注册表路径,该值将被传给NdisOpenProtocolConfiguration函数以获取用于读写适配器信息的句柄。<BR>SystemSpecific2预留系统使用。<BR>如果中间层驱动程序要将内入数据包从一种介质格式转化为另一种格式,那么ProtocolBindAdapter能够分配数据包描述符池和每一个绑定所需的缓冲描述符。关于分配和管理数据包的要求可参阅2.5节。另外,如果中间层驱动程序仅仅用Protocol(Co)Receive函数接收内入数据,那么驱动程序应该分配数据包池和缓冲池来复制接收的数据。</FONT></P>
<P><FONT face=Verdana size=2>2.5.1 打开中间层驱动程序下层的适配器</FONT></P>
<P><FONT face=Verdana
size=2>ProtocolBindAdapter函数通过DeviceName参数值打开低层NIC或虚拟网卡,从而建立到低层NIC驱动程序的绑定,它也能够从注册表中读取所要求的附加配置信息。NdisOpenProtocolConfiguration用于获取指向中间层驱动程序存储适配器相关信息的注册表主键句柄。中间层驱动程序通过调用NdisOpenConfigurationKeyByIndex函数或者NdisOpenConfigurationKeyByName函数打开并获取主键(由NdisOpenProtocolConfiguration函数打开)下的子键句柄。然后,中间层驱动程序能够调用NdisRead(Write)Configuration函数读写注册表主键或子键下的相关信息。NdisRead(Write)Configuration函数在在线DDK的“Network
Drivers
Reference”中有详细的描述。<BR>典型地,ProtocolBindAdapter使用环境区域(代表对DeviceName绑定)存储所有绑定相关信息(与绑定适配器相关联的)。<BR>绑定操作最终由NdisOpenAdapter函数调用来实现,该函数声明如下:<BR> VOID<BR> NdisOpenAdapter(<BR> OUT
PNDIS_STATUS Status,<BR> OUT PNDIS_STATUS
OpenErrorStatus,<BR> OUT
PNDIS_HANDLE NdisBindingHandle,<BR> OUT PUNIT
SelectedMediumIndex,<BR> IN PNDIS_MEDIUM
MediumArray,<BR> IN UINT
MediumArraySize,<BR> IN NDIS_HANDLE
NdisProtocolHandle,<BR> IN NDIS_HANDLE
ProtocolBindingContext,<BR> IN PNDIS_STRING
AdapterName,<BR> IN UINT
OpenOptions,<BR> IN PSTRING
AddressingInformation<BR> );<BR>中间层驱动程序在ProtocolBindingContext中传递代表绑定相关环境区域(已经分配并初始化)的句柄。NDIS在未来与绑定相关的调用中,将向中间层驱动程序返回该环境。例如,在对Protocol(Co)Receive或Protocol(Co)Status函数的调用中。相似地,当NdisOpenAdapter
调用返回时,NDIS将向中间层驱动程序传递该NdisProtocolHandle句柄。驱动程序必须保存该句柄,通常保存在绑定相关的环境区域,在以后与该绑定相关的调用中,中间层驱动程序将向NDIS传送该句柄,例如NdisSend或Ndis(Co)SendPackets函数调用。<BR>ProtocolBindAdapter也能够通过MediumArray传递所支持的介质类型。如果NdisOpenAdapter函数调用成功,低层NIC驱动程序将选择一种其所支持的介质类型,并通过SelectedMediumHandle返回其从MediumArray中所选介质的索引。<BR>ProtocolBindAdapter可以通过NdisProtocolHandle传递前面对NdisRegisterProtocol函数成功调用所返回的值。<BR>如果NdisOpenAdapter返回了一个错误,那么中间层驱动程序应该收回为绑定相关环境区域分配的内存空间,并释放为绑定分配的其他所有资源。典型地,ProtocolBindAdapter通过调用NdisWriteErrorLogEntry,用适当的描述信息记录任何失败的绑定操作。</FONT></P>
<P><FONT face=Verdana size=2>2.5.2 微端口(Miniport)初始化</FONT></P>
<P><FONT face=Verdana
size=2>当成功打开低层NIC并准备在虚拟NIC或NIC上接收请求和发送数据之后,ProtocolBindAdapter将对NdisIMIntializeDeviceInstance进行一次或多次调用来请求对一个或多个网卡进行初始化操作。NdisIMIntializeDeviceInstance调用中间层驱动程序的MiniportInitialize函数执行指定网卡的初始化。当MiniportInitialize函数返回后,上层NDIS驱动程序就能够进行对中间层驱动程序的虚拟NIC(s)的绑定操作了。<BR> MiniportInitialize函数必须分配和初始化虚拟NIC相关的环境区域。作为初始化操作的一部分,MiniportInitialize必须用相应的环境句柄调用NdisMSetAttributeEx函数,NDIS将在以后对MiniportXxx函数调用中传递该环境句柄。MiniportInitialize也必须设置AttributeFlags参数(将被传递给NdisMSetAttributeEx函数)中的NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER标记。中间层驱动程序通过设置NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER标记来标识NDIS驱动程序类型。<BR>另外,如果当中间层驱动程序队列中的发送和请求操作超时时,不想让NDIS调用MiniportCheckForHang(或MiniportReset)函数,那么MiniportInitialize必须对AttributeFlags参数(将被传递给NdisMSetAttributeEx函数)中的NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT和NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT标记进行设置。通过设置超时标记来通知NDIS将由中间层驱动程序负责处理虚拟NIC超时操作。因为中间层驱动程序并不操纵低层NIC,因此它无法控制到底花了多长时间完成未决发送和请求操作,驱动程序通常既不提供MiniportCheckForHang函数也不处理虚拟NIC超时。<BR>然而,如果中间层驱动程序已经注册了CheckForHangHandler句柄的入口点,并且没有请求NDIS忽略数据包和请求超时,也没有改变超事间隔,那么,在默认情况下,将每隔两秒对MiniportCheckForHang函数进行一次调用。如果MiniportCheckForHang函数返回TRUE,驱动程序将调用MiniportReset函数。如果驱动程序支持MiniportCheckForHang函数,那么可以通过调用NdisMSetAttributeEx函数来明确指定一个不同的TimeInSeconds值,改变默认的两秒调用间隔设置。<BR>中间层驱动程序必须像一个非串行驱动程序那样进行操作,并且通过设置将要传递给NdisMSetAttributeEx函数的AttributeFlags参数中的NDIS_ATTRIBUTE_DESERIALIZE标记来进行注册。非串行驱动程序对MiniportXxx函数的操作进行串行化,并且对所有引入的发送数据包在内部进行排队,而不是依靠NDIS来保存发送队列。<BR>中间层驱动程序也必须设置AttributeFlags参数(将被传给NdisMSetAttributeEx函数)中的NDIS_ATTRIBUTE_NO_HALT_SUSPEND标记,防止NDIS在低层微端口过渡到低功耗状态之前中断驱动程序。<BR>中间层驱动程序要确保所保持的状态信息是完全初始化过的。如果中间层驱动程序请求发送相关的资源(例如MiniportSend或MiniportSendPackets将要向相邻低层发送的数据包的包描述符),那么如果在调用NdisIMInitializeDeviceInstance之前,ProtocolBindAdapter还没有分配数据包池,则分配数据包池。</FONT></P>
<P><FONT face=Verdana size=2>2.5.3 中间层驱动程序查询和设置操作</FONT></P>
<P><FONT face=Verdana
size=2>当成功绑定到低层NIC并完成虚拟NIC(s)的初始化操作之后,中间层驱动程序就可以查询低层NIC驱动程序的操作特性,设置其内部状态,也可以协商一些参数(如为低层NIC驱动程序预留的缓冲区大小等)。下边界面向无连接的中间层驱动程序通过调用NdisRequest实现该功能,下边界面向连接的中间层驱动程序则通过调用Ndis(Co)Request函数实现该功能。<BR>中间层驱动程序也能够接收来自协议驱动程序的MiniportQueryInformation和MiniportSetInformation函数的查询和设置请求,它要么响应这些请求要么将这些请求传给低层驱动程序。<BR>在在线DDK的“Network
Drivers
Reference”中包含了中间层驱动程序开发者所关心的全部通用的、面向连接的、非介质相关的OID,以及介质相关的OID的详细信息。接下来将讨论几个标准且常用的通用分类OID、面向连接OID以及一些介质相关OID。<BR>2.5.3.1 发布设置和查询请求<BR> 典型地,下边界面向无连接的中间层驱动程序通过发布OID_GEN_MAXIMUM_FRAME_SIZE请求,查询低层NIC驱动程序所支持的帧最大长度,该请求返回值不包括帧头部分的长度。<BR> 下边界面向无连接的中间层驱动程序能够用OID_GEN_MAXIMUM_TOTAL_SIZE请求,查询绑定从而确定低层NIC驱动程序所管理的NIC所能接纳的最大数据包,中间层驱动程序必须对发送数据包进行设置,使其满足这一尺寸要求。如果上层驱动程序发送一个超出NIC驱动程序(中间层驱动程序所绑定的)所能支持尺寸的数据包,那么将会出现错误。<BR>下边界面向无连接的中间层驱动程序能够用OID_GEN_CURRENT_LOOKAHEAD请求,查询前视数据缓冲区的大小。如果中间层驱动程序提交这一查询请求,NDIS将返回对低层NIC驱动程序的给定绑定的最新前视缓冲区尺寸。如果中间层驱动程序进行相应的设置请求,那么它将指示所提出的前视缓冲区尺寸,但中间层驱动程序并不能保证低层NIC驱动程序能够按照所指示尺寸设置前视缓冲区。<BR>下边界面向无连接的中间层驱动程序用OID_GEN_LINK_SPEED请求,查询低层NIC驱动程序的链接速率,并用该请求的返回值修改其保存的内部超时设置。下边界面向连接的中间层驱动程序用OID_GEN_CO_LINK_SPEED请求,查询低层NIC驱动程序的链接速率,并且也能够用OID_GEN_CO_LINK_SPEED请求,设置低层NIC驱动程序的链接速率。<BR>如果中间层驱动程序绑定到WAN
NIC驱动程序上的,那么直到接收到一个连结指示(指示本地节点和远程节点连结建立)时才能确定链接速率。关于链接指示的描述请参阅第二部分第八章的“广域网微端口驱动程序做出的指示”。<BR>中间层驱动程序也必须确定低层NIC驱动程序的操作特性的设置,下边界面向无连接的中间层驱动程序用OID_GEN_MAC_OPTIONS请求来实现这一功能,下边界面向连接的中间层驱动程序用OID_GEN_CO_MAC_OPTIONS请求来实现这一功能。<BR>下边界面向无连接的中间层驱动程序通常发布的是OID_GEN_MAXIMUM_SEND_PACKETS查询(特别是在中间层驱动程序导出了MiniportSendPackets函数情况下),驱动程序能够在以后响应高层驱动程序的OID_GEN_MAXIMUM_SEND_PACKETS查询时,向上层传递该查询的返回值。<BR>中间层驱动程序也能够通过介质相关OID查询相关介质的当前地址,例如,下边界面向无连接的中间层驱动程序可以发布OID_WAN_CURRENT_ADDRESS、OID_802_3_CURRENT_ADDRESS、OID_802_5_CURRENT_ADDRESS或者OID_FDDI_LONG_CURRENT_ADDRESS查询,下边界面向连接的中间层驱动程序可以OID_ATM_WAN_CURRENT_ADDRESS查询。<BR>如果必要,中间层驱动程序能够发布一个设置请求,来通知NDIS其操作特性的有关信息。下边界面向无连接的中间层驱动程序用OID_GEN_PROTOCOL_OPTIONS调用NdisRequest函数来实现这一功能,而下边界面向连接的中间层驱动程序用OID_GEN_CO_PROTOCOL_OPTIONS调用NdisRequest来实现这一功能。<BR>绑定到支持WAN的NIC的中间层驱动程序同时必须完成以下设置信息请求:<BR>用OID_WAN_PROTOCOL_TYPE请求,通知低层NIC驱动程序其协议的类型,该类型以单字节的网络层协议标识符形式提供;<BR>用OID_WAN_HEADER_FORMAT请求,通知低层NIC驱动程序其发送数据包的头格式。</FONT></P>
<P><FONT face=Verdana
size=2>2.5.3.2 响应设置和查询请求<BR>因为NDIS中间层驱动程序可被高层NDIS驱动程序绑定,所以它也可以接收MiniportQueryInformation和MiniportSetInformation函数的查询和设置请求。在某些情况下,中间层驱动程序所起的作用仅仅是将这些请求传递给低层驱动程序。另外,当这些请求是关于其在上边界导出的介质时,也能够对这些查询和设置请求进行响应。注意中间层驱动程序必须将其从上层NDIS驱动程序接收到的OID_PNP_XXX请求,传递给低层Miniport驱动程序处理。<BR>通常情况下,中间层驱动程序所接收到的通用OID,与其向低层NIC驱动程序提交的OID是相似甚至相同的,中间层驱动程序所接收到的介质相关OID将是高层驱动程序所期望的介质类型。</FONT></P>
<P><FONT face=Verdana size=2>2.5.4 作为面向连接客户程序注册中间层驱动程序</FONT></P>
<P><FONT face=Verdana
size=2>下边界面向连接的中间层驱动程序必须作为面向连接客户程序进行注册。面向连接客户程序使用呼叫管理器或集成微端口呼叫管理器(MCM)的安装调用(call-setup)和卸载(tear-down)服务完成相关功能,也可以使用面向连接的微端口或MCM的接收和发送功能进行发送和接收数据操作。关于面向连接通信的更多信息请参阅第一部分第四章。<BR>当呼叫管理器或MCM从ProtocolBindAdapter函数中调用Ndis(M)CmRegisterAddressFamily注册地址族时,NDIS将调用绑定上的所有协议驱动程序的ProtocolCoRegisterAfNotify函数。如果中间层驱动程序在注册协议时调用了ProtocolCoRegisterAfNotify函数,那么NDIS将对中间层驱动
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -