windows wdf 驱动开发总结(8)--网络驱动开发(NDIS)

(11) IoCreateDevice

函数功能:creates a device object for use by a driver.

NTSTATUS IoCreateDevice(

  __in      PDRIVER_OBJECT DriverObject,

  __in      ULONG DeviceExtensionSize,

  __in_opt  PUNICODE_STRING DeviceName,

  __in      DEVICE_TYPE DeviceType,

  __in      ULONG DeviceCharacteristics,

  __in      BOOLEAN Exclusive,

  __out     PDEVICE_OBJECT *DeviceObject

);

 

(12) IoCreateSymbolicLink

函数功能:sets up a symbolic link between a device object name and a user-visible name for the device

NTSTATUS IoCreateSymbolicLink(

  __in  PUNICODE_STRING SymbolicLinkName,

  __in  PUNICODE_STRING DeviceName

);

 

SymbolicLinkName [in]

Pointer to a buffered Unicode string that is the user-visible name.

DeviceName [in]

Pointer to a buffered Unicode string that is the name of the driver-created device object.

评论:

IoCreateSymbolicLink returns STATUS_SUCCESS if the symbolic link object was created

 

 

(13) NdisInterlockedDecrement

函数功能:用一个原子操作减1

 

 

(14) NdisGetFirstBufferFromPacket

函数功能:返回链在包上的第一个缓存的描述符及虚地址,以及第一个缓存的长度和所有缓存的总长(以防缓存使分段的)This function returns pointers to the buffer descriptor and base virtual address for the initial buffer chained to a specified packet descriptor, along with the sizes of the initial buffer and total buffer in case the buffer is fragmented.

VOID NdisGetFirstBufferFromPacket(
  PNDIS_PACKET Packet,
  PNDIS_BUFFER* FirstBuffer,
  PVOID* FirstBufferVA,
  PUINT FirstBufferLength,
  PUINT TotalBufferLength
);

Packet

[in] Pointer to the packet descriptor from which this function extracts information about the initial buffer.

FirstBuffer

[out] Pointer to a caller-supplied variable in which this function returns the address of the initial buffer descriptor chained to the specified packet descriptor.

FirstBufferVA

[out] Pointer to a caller-supplied variable in which this function returns the base virtual address of the initial buffer associated with the packet.

FirstBufferLength

[out] Pointer to a caller-supplied variable in which this function returns the number of bytes mapped by the initial buffer descriptor chained to the packet descriptor.

TotalBufferLength

[out] Pointer to a caller-supplied variable in which this function returns the total number of bytes mapped by all buffer descriptors chained to the packet descriptor.

 

(15)      NdisChainBufferAtFront

函数功能:This function links a specified buffer descriptor to the head of the buffer-descriptor chain attached to a packet descriptor.

VOID NdisChainBufferAtFront(
  PNDIS_PACKET Packet,
  PNDIS_BUFFER Buffer
);

Packet

[in, out] Pointer to the packet descriptor.

Buffer

[in, out] Pointer to the caller-supplied buffer descriptor to be added to the chain.

 

评论:

This function links the specified buffer descriptor at the head of the chain for the specified packet. It also resets the valid counts for the packet to FALSE, thus forcing the NdisQueryPacket function to recalculate information about the specified packet if it is called subsequently with that packet.

 

(16)      NdisSendPackets

函数功能:This function forwards a multipacket send request, possibly with associated out-of-band information, to the underlying driver.

VOID NdisSendPackets(
  NDIS_HANDLE NdisBindingHandle,
  PPNDIS_PACKET PacketArray,
  UINT NumberOfPackets
);

NdisBindingHandle

[in] Specifies the handle returned by NdisOpenAdapter that identifies the target NIC or the virtual adapter of the next-lower driver to which the caller is bound.

PacketArray

[in] Points to an array of pointers to packet descriptors. Each packet descriptor in the array has chained buffer descriptors mapping buffers containing the data that the underlying NIC driver should transmit over the wire. Each packet descriptor also has an associated NDIS_PACKET_OOB_DATA block, which the caller has already set up with any time stamp and/or medium-specific out-of-band information, such as packet priority, relevant to the underlying driver.

NumberOfPackets

[in] Specifies the number of pointers in the packet array.

 

 

 

(17)BindAdapterHandler

函数功能:

     VOID NdisProtBindAdapter(

    OUT PNDIS_STATUS                pStatus,

    IN NDIS_HANDLE                  BindContext,

    IN PNDIS_STRING                 pDeviceName,

    IN PVOID                        SystemSpecific1,

    IN PVOID                        SystemSpecific2

    )

由于有多块网卡,所以一般情况下在此为每个网卡创建一个 Device,初始化一些数据并打开下层适配器,官方做法如下:首先为每个网卡创建一个设备,在该设备扩展 DeviceExtension 中指明一个PNDISPROT_OPEN_CONTEXT自定义数据结构,该数据结构标识了一个我们协议驱动打开的下层网卡所需数据,当然你也可以不记录。然后就是打开下层适配器了,用的参数主要就是一个 DeviceName,好像是对应注册表里面一项网卡的唯一标识吧。唯一要说明的就是ProtocolBindingContext

VOID NdisOpenAdapter( OUT PNDIS_STATUS    Status,

        OUT PNDIS_STATUS    OpenErrorStatus,

        OUT PNDIS_HANDLE    NdisBindingHandle,

        OUT PUINT    SelectedMediumIndex,

        IN PNDIS_MEDIUM    MediumArray,

        IN UINT    MediumArraySize,

        IN NDIS_HANDLE    NdisProtocolHandle,

        IN NDIS_HANDLE    ProtocolBindingContext,

        IN PNDIS_STRING    AdapterName,

        IN UINT    OpenOptions,

        IN PSTRING    AddressingInformation    OPTIONAL);

该参数会在以后的收发数据里面经常出现,且是用户自定义的,只能在打开适配器的时候指定,以后在数据操作时候就可以通过该参数来得知是那个网卡来的数据, 通常 情况下 我 们需 要将该 参 数指 定为上 述PNDISPROT_OPEN_CONTEXT 数据结构。Ndisport调用示例如下:

        NdisOpenAdapter(

            &Status,

            &OpenErrorCode,

            &pOpenContext->BindingHandle,

            &SelectedMediumIndex,

            &MediumArray[0],

            sizeof(MediumArray) / sizeof(NDIS_MEDIUM),

            Globals.NdisProtocolHandle,

            (NDIS_HANDLE)pOpenContext,

            &pOpenContext->DeviceName,

            0,

            NULL);

 

(17)      打开网卡完成函数NdisProtOpenAdapterComplete

VOID

NdisProtOpenAdapterComplete(

    IN NDIS_HANDLE                  ProtocolBindingContext,

    IN NDIS_STATUS                  Status,

    IN NDIS_STATUS                  OpenErrorCode

)

简单的设置事件。

 

(18)      NdisRequest

函数功能:This function forwards a request to the underlying driver that it query the capabilities or status of its NIC or that it set the state of its NIC.

VOID NdisRequest(
  PNDIS_STATUS Status,
  NDIS_HANDLE NdisBindingHandle,
  PNDIS_REQUEST NdisRequest
);

Status

[in] Pointer to a caller-supplied variable that is set on return from this function. The underlying driver determines which NDIS_STATUS_XXX is returned.

NdisBindingHandle

[in] Handle returned by the NdisOpenAdapter function that identifies the target NIC or the virtual adapter of the next-lower driver to which the caller is bound.

NdisRequest

[in] Pointer to a buffered structure specifying the operation requested with a specified OID_XXX code for either a query or a set.

 

评论:

A protocol driver must allocate sufficient memory for the information buffer associated with the OID_XXX of the operation it requests. The driver also must allocate and set up the buffer at NdisRequest before it calls this function.

 

(19)      NdisQueryBuffer NdisQueryBufferSafe

函数功能:retrieves the size of the range, and optionally the base virtual address, from a buffer descriptor.

  NdisQueryBuffer(
    IN PNDIS_BUFFER  Buffer,
    OUT PVOID  *VirtualAddress  OPTIONAL,
    OUT PUINT  Length
    );

Parameters

Buffer

Pointer to the buffer descriptor.

VirtualAddress

Pointer to a caller-supplied variable in which this function returns the base virtual address of the range described.

Length

Pointer to to a caller-supplied variable in which this function returns the number of bytes in the virtual range.

 

VOID
  
NdisQueryBufferSafe(
    IN PNDIS_BUFFER  Buffer,
    OUT PVOID  *VirtualAddress  OPTIONAL,
    OUT PUINT  Length,
    IN MM_PAGE_PRIORITY Priority
    );

Parameters

Buffer

Pointer to the buffer descriptor.

VirtualAddress

Pointer to a caller-supplied variable in which this function returns the base virtual address of the range described, or set to NULL if:

·         System resources are low or exhausted and Priority was set to LowPagePriority or NormalPagePriority.

·         System resources are exhausted and Priority was set to HighPagePriority.

Length

Pointer to a caller-supplied variable in which this function returns the number of bytes in the virtual range.

Priority

Indicates the priority of the request as one of the following:

LowPagePriority

Specifies a low priority. It is acceptable for NdisQueryBufferSafe to fail if system resources are low.

NormalPagePriority

Specifies a normal priority. It is acceptable for NdisQueryBufferSafe to fail if system resources are low.

HighPagePriority

Specifies a high priority. It is not acceptable for NdisQueryBufferSafe to fail unless system resources are exhausted.

 

(20)    NdisOpenAdapterEx

函数功能:A protocol driver calls the NdisOpenAdapterEx function from its ProtocolBindAdapterEx function to set up a binding between the protocol driver and an underlying driver.

NDIS_STATUS NdisOpenAdapterEx(

  __in   NDIS_HANDLE NdisProtocolHandle,

  __in   NDIS_HANDLE ProtocolBindingContext,

  __in   PNDIS_OPEN_PARAMETERS OpenParameters,

  __in   NDIS_HANDLE BindContext,

  __out  PNDIS_HANDLE NdisBindingHandle

);

 

NdisProtocolHandle [in]

The handle returned by the NdisRegisterProtocolDriver function.

ProtocolBindingContext [in]

The handle for a caller-supplied context area in which the protocol driver maintains state information for this binding.

OpenParameters [in]

A pointer to an NDIS_OPEN_PARAMETERS structure that is set up by the caller.

BindContext [in]

The handle that identifies the NDIS context area for the bind operation. NDIS passed this handle to the BindContext parameter of the ProtocolBindAdapterEx function.

NdisBindingHandle [out]

A pointer to a caller-supplied variable. NDIS writes a handle at NdisBindingHandle that identifies the binding between the caller and the miniport adapter specified in the AdapterName member at OpenParameters . The caller uses this handle in subsequent calls to NdisXxx functions.

 

(21) PacketReceivePacketPacketReceive 区别

NIC从网络上接收到数据并通知 NDIS时,作为已经在protocolChar 结构中注册过的函数,NDIS 将调用 PacketReceiveIndicate PacketReceivePacket 二者之一作为接收处理函数将 NIC 从网络上接收到的数据缓存起来。其中,PacketReceiveIndicate作为协议驱动程序的ProtocolReceive函数是必须要提供的,而PacketReceivePacket作为协议驱动程序的ProtocolReceivePacket函数提供与否是可选的。二者的主要区别在于当 NIC 支持一次接收多个数据包时使用ProtocolReceivePacket更富效率。

 

Windows内核实验教程摘录:

 (22)NDIS驱动程序通常需要考虑一下几个问题:

1)驱动程序初始化;2)数据传输处理;3)数据包消息接收;4)中断处理;5)传输终结处理;从接口层次我们可以更清楚地理解NDIS驱动的工作原理。

      

2)函数框架

   协议驱动框架与一般形式的WDM驱动的框架稍有不同,我们来看一下NDIS协议驱动程序的函数设置:

       DriverEntry:这是协议驱动程序的初始化函数,并且必须被命名为DriverEntry。而其他由协议驱动程序导出的函数则可以任意命名,因为NDIS最终得到的只是这些函数的入口地址,在系统初始化期间会调用DriverEntry完成协议驱动程序的加载。

    DriverEntry中通过调用NdisRegisterProtocol完成协议驱动程序的注册,该函数的第3个参数中填写了所有输出函数的入口地址。如果在DriverEntry中申请某种资源失败的话。即需要将先前得到的各种资源释放掉并调用NdisDeregisterProtocol返回适当的错误信息。

    ProtocolBindAdapter。这是协议驱动程序必须提供的函数,用以支持即插即用。

    每当出现协议驱动程序可以使用的NIC时,ProtocolBindAdapter负责完成其动态绑定操作,另外,ProtocolBindAdapter还将继DriverEntry之后完成进一步的初始化工作,包括申请足够的内存以维护每个绑定的运行时状态信息以及调用NdisOpenAdapter进行向下层驱动程序的绑定。

    ProtocolUnbindAdapter。这是协议驱动程序必须提供的函数,用来支持即插即用;

    ProtocolBindAdapter相反,ProtocolUnbindAdapter会释放先前为维护每个绑定的运行时状态信息而申请的各种资源,并调用NdisCloseAdapter释放掉早先由NdisOpenAdapter建立的绑定。

    ProtocolOpenAdapterComplete。这是一个必须提供的函数。如果协议驱动程序调用NdisOpenAdapter的返回值为NDIS_STATUS_PENDING,那么将由ProtocolOpenAdapterComplete负责完成绑定操作。

    ProtocolCloseAdapterComplete。这是一个必须提供的函数。如果协议驱动程序调用NdisCloseAdapter的返回值为NDIS_STATUS_PENDING,那么将由ProtocolCloseAdapterComplete负责完成卸载操作。

 

    ProtocolReceive。协议驱动程序必须提供此函数用于在无连接的网络中接收数据包;

    每当NIC从网络上接收到数据包时将调用此函数,ProtocolReceive与下层的NdisMxxxIndicateReceive相对应,都是每收到一个数据包就要马上进行处理。在这种方式下,NIC将每个从网络上接收到的数据包分为包头和数据两部分上交,并分别放在HeaderBufferLookaheadBuffer中。如果LookaheadBuffer中所含的数据并不完整,则还需要调用NdisTransferData完成剩余数据的传输工作。

    ProtocolReceiveComplete。这是一个必须提供的函数。先前由NIC递交给ProtocolReceive的数据包在protocolReceiveComplete中可以进行 一些后期的处理工作;

    ProtocolTransferDataComplete。这是一个必须提供的函数,除非协议驱动程序独占了一个在底层使用NdisMIndicateReceivePacket递交数据包的NIC驱动程序。如果协议驱动程序调用NdisTransferData的返回值为NDIS_STATUS_PENDING,并且数据最终完成的拷贝到协议驱动程序提供的数据包中,那么ProtocolTransferDataComplete将会被自动调用。

    ProtocolReceivePacket。这是一个可选的函数。如果底层的NIC驱动程序使用NDIS提供的NdisMIndicateReceivePacket递交数据包,那么协议驱动程序就应该提供一个ProtocolReceivePacket函数。当NIC支持一次接收多个数据包时,协议驱动程序应该提供这个函数。因为这时下层驱动程序向上层传递的经常是完整的数据包。ProtocolReceivePacketNdisMIndicateReceivePacket相对应,在收到一个或多个完整的数据包后再一并进行处理,因此使用该函数效率更高。

ProtocolSendComplete。这是一个必须提供的函数。每次通过NdisSend发送一个数据包时,如果返回为NDIS_STATUS_PENDING,则需要由ProtocolSendComplete完成最后的操作。如果是通过NdisSendParkents每次发送一组数据包,则不论该操作完成与否都需要调用ProtocolSendComplete

ProtocolResetComplete。这是一个必须提供的函数,如果先前对NdisReset的调用返回NDIS_STATUS_PENDING,那么当重置操作完成时协议驱动程序会自动调用ProtocolResetComplete.

ProtocolRequestComplete。这是一个必须提供的函数,如果先前对NdisRequest的调用返回NDIS_STATUS_PENDING,那么当查询或设置操作完成时协议驱动程序会自动调用ProtocolRequestComplete

13.ProtocolStatus。这是一个必须提供的函数。ProtocolStatus用于处理底层的NIC驱动程序报告的状态变化。

ProtocolStatusComplete。这是一个必须提供的函数。ProtocolStatusComplete是由NDIS负责调用的,它与ProtocolStatus相互配合,用于报告由NDIS驱动程序或NIC驱动程序启动的重置操作的开始或结束。

15.ProtocolPnPEvent.这是一个必须提供的函数。NDIS调用ProtocolPnPEvent来声明一个即插即用事件或电源管理事件。

16.ProtocolUnload。这是一个可选的函数。当用户请求卸载协议驱动程序时,对于每个已绑定的适配器NDIS都将调用ProtocolUnbindAdapter,最后调用ProtocoUnload完成一些收尾工作。

3)数据包的组织与管理

NDIS中,数据的接收和发送都是以数据包为单位进行的。一个数据包由以下几部分组成:

⒈一个数据包描述符。其所含信息包括整个数据包所占用的物理页面的数量、数据包的长度、指向第一个和最后一个缓冲区描述符的指针以及数据包池的句柄等等。

⒉一组缓冲区描述符。每个缓冲区描述符用来描述一片存储区域,其中包括起始虚拟地址、偏移量、该存储区域的大小以及指向下一个缓冲区描述符的指针等信息。

⒊由缓冲区描述符所描述的虚拟存储区域,该区域可能横跨几个页面。这些页面最终被映射到物理内存中。

构造一个数据包需要申请各种资源,只有成功得到了各种所需资源,才能构造出一个可以用于收发数据的数据包。首先要做到的是得到一个数据包描述符,而数据包描述符必须从数据包池中分配,其步骤如下:

1)在驱动程序初始化或是每次向NIC绑定时调用NdisAllocatePacketPool请求分配一个数据包池,其中含有指定数量的数据包描述符;

2)从NdisAllocatePacketPool申请得到的数据包池中请求分配一个数据包描述符;

在得到了数据包描述符之后,还要通过调用NdisChainBufferAtBack或者NdisChainBufferAtFront向上面链接一个或多个缓冲区描述符。而缓冲区描述符所描述的存储区域则可由内核提供的NdisAllocateMemory函数得到。然后,还要用缓冲区描述符对刚刚得到的存储区域加以映射。整个过程如下:

首先在驱动程序初始化或是每次向NIC绑定时调用NdisAllocateBufferPool函数请求分配一个缓冲池,今后所需的缓冲区描述符即由此获得。然后调用NdisAllocateMemory函数请求分配一片存储区域,最后调用NdisAllocateBuffer函数分配并建立一个缓冲区描述符用来描述刚刚由函数NdisAllocateMemory得到的存储区域,需要注意的是由NdisAllocateMemory返回的虚拟基址和长度将在调用NdisAllocateBuffer时作为参数传入,用来初始化一个缓冲区描述符。

 

4)Ndis协议驱动设计思想

4.1 从网络上截获数据包的思想

    因为我们的目的是把网络上的包抓下来,所以程序理所当然地应该这样设计:每当上层应用程序请求读取数据时,就为该请求生成一个相应的IRP,并将此IRP置于一个读取等待队列之中;当NIC从网络上接收到数据包时,由NDIS负责调用相应的接收函数。接收函数从读取等待队列首部取出一个IRP,并将从网络上接收到的数据包拷贝到由该IRP所指定的缓冲区中,至此,上层应用程序便成功接收到了它所需的数据。那么程序大体是这样的,当用户需要一个包时,调用一个上层函数进行读取,下层接到这个读请求,把包送到上层。

 

4.2)解决丢包的策略

这种接收方式虽然实现起来比较简单,但是有一个天生的缺陷就是丢包。如果上层应用程序一直没有读取数据的请求,那么NIC从网络上接收到的数据包就会直接被丢弃。因此,要想解决丢包这个问题。就必须对接收方式尽心切底的改变。该造后的程序设计思想主要是增加了缓冲区以及对该缓冲区实施有效地管理。协议驱动程序负责维护一个接收缓冲区,该缓冲区以队列的形式组织。当NIC通知NDIS已从网络上接收到数据包时,我们可以先将这些数据缓存起来,当上层应用程序需要读取数据时,该读操作的数据源不是直接从网络得到,而是经过有效管理的存放着数据缓冲区。这样丢包的问题便得到解决。

当然,如果上层应用程序还是一直没有读取数据的请求,或者上层应用程序处理数据的数据低于NIC从网络上接收数据的速度,再或者网络出现峰值流量时,缓冲区自然会逐渐被填满,最终还是会出现丢包的情况。但是在正常情况下,这种增设缓冲区的方法已经足以避免丢包情况发生了。

实际编程中,正是通过增设缓冲区的方法解决了丢包的问题,把数据包先保存起来,当数据读请求时上交所有存在缓冲区的数据包,然后清空。

 

4.3)NDIS协议驱动重要功能的实现

4.3.1)绑定

    PacketBindAdapter函数是一个驱动程序中必不可少的函数,它是操作系统自动调用的函数,在驱动程序安装的时候,操作系统自动查找当前机器上安装的网卡驱动程序(NIC Driver),并对每一个网卡自动运行这个函数,其目的是在驱动程序中对每个网卡进行记录,以备以后使用,这个函数中的主要内容就是实现对网卡的登记注册。

    具体实现步骤如下:

    1)从DeviceName中取得网卡的名字,并分配空间存储起来;

    2)调用IoCreateDevice为这个网卡建立一个设备对象(DeviceObject);

3) 创建deviceobjcet->deviceExtentionOpen_Install结构,用来记录这个设备的一切信息;

4)为这个设备对象建立符号链接(Symbol_Link);

5)调用NdisAllocatePacketPool为这个设备分配包缓冲池;

6)初始化Open_Instance结构(open变量)的一些域;

7)调用NdisOpenAdapter完成这个设备的初始化工作;

8)设置Open_Instance结构中的一些域的初值;

9)把这个设备加入到Globals.AdapterList的队列中去;

从上面的分析可以看出,这个函数所做的主要工作就是一些初始化和赋初值的工作,它的作用主要是将系统中拥有的所有网卡都记录起来。由于协议驱动陈通过续是网卡驱动程序为基础工作的,这些信息时他们必须拥有的,否则协议驱动程序不能正常的工作。

 

(23)ExInterlockedInsertTailList

函数功能:inserts an entry at the end of a doubly linked list of LIST_ENTRY structures.

PLIST_ENTRY ExInterlockedInsertTailList(

  __inout  PLIST_ENTRY ListHead,

  __inout  PLIST_ENTRY ListEntry,

  __inout  PKSPIN_LOCK Lock

);

Parameters

ListHead [in, out]

A pointer to the LIST_ENTRY structure that serves as the list header.

ListEntry [in, out]

A pointer to the LIST_ENTRY structure that represents the entry to be inserted into the list.

Lock [in, out]

A pointer to a KSPIN_LOCK structure that serves as the spin lock used to synchronize access to the list. The storage for the spin lock must be resident and must have been initialized by calling KeInitializeSpinLock. You must use this spin lock only with the ExInterlockedXxxList routines.

Return Value

ExInterlockedInsertTailList returns a pointer to the last entry of the list before the new entry was inserted. If the list was empty, the routine returns NULL.

 

 

4.3.2I/O控制

    这部分的功能是由PacketIoControl函数实现的;这个函数主要功能是实现对驱动程序以及它所绑定的设备的控制,究竟是对驱动程序本身的控制还是对它所绑定的设备的控制是由传入的参数Deviceobject决定,如果它等于Global->ControlDeviceObject,那么它就是对驱动程序的控制,否则是对绑定设备的控制。

    程序开始,首先确定控制类型,由于用户态和核心态程序的信息交换都是通过IRP来实现的,所以这个控制类型是存放在IRP的堆栈中.(irpSp->Parameters.DeviceIoControl.IoControlCode),因为我们要实现无丢包的读取,所以这个函数必须实现以下5个方面的I/O控制:

    ⒈枚举网络适配器;

⒉记录上层用户是否发出读请求;

3.设置缓冲区的大小;

.重置适配器;

.实现NDIS请求。NDIS请求包括:(1)设置OID(2)查询OID

整个函数可以分为以下几个明显的部分,每个部分分别实现其中的一项功能。下面逐一介绍各自的实现。

1)枚举网络适配器。这个功能主要是通过PacketGetAdapterList函数来实现,这个功能在PacketIoControl函数中简单实现了以下几个功能:

1)判断设备变量是否为全局控制设备变量即GlobalControlDeviceObject域,如果不是说明上层调用错误,返回错误信息。

2)调用PacketGetadapterList寻找网络适配器列表,放在输出缓冲区Irp->AssociatedIrp.SystemBuffer中。

3)执行标准语句组完成Irp

PacketGetAdapterList函数是自定义的函数。它的功能十分容易理解,就是取得系统中所有网络适配器的列表,首先从头到尾搜索一遍GlobalsAdapterList,确定这个列表的长度(第一个循环),把整个长度与所给缓冲区的长度比较,如果前者大,那么返回资源不够的错误信息,如果所给的缓冲区够大,那么依次把每个适配器的名字和符号链接拷贝到缓冲区里面,成功返回。

2)记录上层是否发出读请求。这个功能主要通过修改设备结构中的变量Start实现。主要通过以下几个步骤:

1)取设备信息结构变量Open=DeviceObject->DeviceExtension.

2)判断Start域的值,根据要求修改。

3)完成Irp,返回

 

3)设置缓冲区大小,这个功能的实现与2相似,主要是通过修改设备结构中的变量bufsize来实现,主要有以下几个步骤;

1)  取设备信息结构变量Open=DeviceObject->DeviceExtension

2)取要设置的bufsize的值(在Irp->AssociateIrp.SystemBuffer中),修改open>bufsize;

3)完成Irp,返回。

 

4)重置适配器,这个功能主要是调用系统函数NdisReset来实现的,不过在此之前,这个函数把当前的这个重置请求加入到了ResetIrpList中,这样做的目的主要是帮助PacketResetComplete函数找到Irp,事实上,在执行ResetIO控制时,系统进行了以下操作:

1)执行NdisReset函数;

2)由于NdisReset不是阻塞函数,在Reset操作完成之前PacketIoContrl函数就已经返回了;

3)当NDis执行完Reset操作以后,在PacketResetComplete函数中执行难标准语句组完成Irp

注意:当NDIS执行完Reset操作要完成IRP的时候,它必须知道哪个Irp要求Reset操作。而Irp是作为参数传到PacketIoControl函数中,PacketResetComplete无法知道这个Irp。这样就需要一个数据结构记录这个Irp,这个数据结构就是ResetIrpList,在PacketIoControl函数中,把当前的Irp加入到ResetIrpList中,然后执行NdisReset后马上返回。过了一段时间,Reset操作完成,Ndis自动调用PacketResetComplete函数,从ResetIrpList取出这个Irp,执行标准语句组完成Irp,不过,需要注意的是,Reset操作并不一定总是完不成的,那坑内在调用NdisReset函数的一瞬间完成,这时,NdisReset传出来的状态变量status就不再是Ndis_STATUS_PENDINg. 如果出现这种情况,Ndis将不会自动调用PacketResetComplete函数执行后续操作。所以,如果这种情况发生,程序员必须显式的制定驱动程序调用PacketResetComplete

5)实现Ndis请求。Ndis请求主要有2类:设置OID和查询OId,这两个功能主要通过调用系统函数NdisRequest实现的,具体步骤如下:

  1)从系统缓冲区中取出要进行操作的OID结构,OidData=Irp->AssociatedIrp.SystemBuffer;

  2)分配一个pRequest变量,用来存储Irp和当前的Request内容(OID的内容)

  3)判断OID的数据结构长度是否正确。

4)判断请求是设置OID还是查询OID,并针对相应的请求进行相应的变量设置;

5)调用NdisRequest

6)错误处理。

7)类似Reset请求,如果NdisRequest已经完成(status不是返回的NDIS_STATUS_PENDING,那么驱动程序要显示地调用PacketRequestComplete函数来完成Irp

 

4.3.3)数据接收

    下面以协议驱动搞程序将NIC从网络上接收到的数据包递交给上层请求读取数据的应用程序这一情景为主线,分析一下协议驱动程序在整个过程中所起的作用,以及各相关函数的工作流程。需要注意的是以下提到的函数名称均为实际程序中的函数,是PacketXXX的形式,而不是标准的ProtocolXXX的形式。

    NIC从网络上接收到数据包并通知NDIS时,作为已经在protocolChar结构中注册过的函数,NDIS将调用PacketReceiveIndicatePacketReceivePacket二者之一作为接收处理函数将NIC从网络上接收到的数据缓存起来。其中,PacketReceiveIndicate作为协议驱动程序的ProtocolReceive函数是必须提供的,而PacketReceivePacket做为协议驱动程序的ProtocolReceivePacket函数提供与否是可选的。二者的主要区别在于当NIC支持一次接收多个数据包时使用ProtocolReceivePacket效率更高。

    当进入函数PacketReceiveIndicate之后,首先由ProtocolBindingContext这一参数得到保存了重要数据的Open结构,这里维护着数据包驱动程序的运行状态信息。在之前调用NdisOpenAdapter时需要提供这一环境的句柄,open结构的存储空间按是通过在调用IoCreateDevice时指定第2个参数的值为sizeof(OPEN_INSTANCE)而得以分配的。在这之后的IoIncrement用于对设备使用情况计数并负责同步,接下来再做一个简单的判断,我们只关心和处理以太网的数据包,如果不是的话则直接返回。下面的工作是构造一个数据包,用来接收NIC从网络上收到的数据包。

     1步:先调用NdisAllocatePacketPacketPool里请求分配一个数据包描述符。这里PacketPool是在PacketBindAdapter中通过调用NdisAllocatePacketPool得到的。为了将收到的数据包组织成队列形式的缓冲区,我们在由每个数据包描述符中的ProtocolReserved域所指向的PACKET_RESERVED结构中放置了一个用于构造双向链表的LIST_ENTRY指针组件,而PACKET_RESERVED结构的存储空间则是通过在调用NdisALLocatePacketPool时将参数ProtocolReservedLength的值置为sizeof(PACKET_RESERVED)而得以分配的。当然如果调用NdisAllocatePacket请求分配数据包描述符失败的话,说明用来保存数据包的缓冲区已满,直接通过goto语句跳转到ERROR处,等待善后处理。这是一种资源不足的情况,后面再请求分配内存或者buffer descriptro时也可能出现这样的情况,处理方法类似。

2步:由HeaderBufferSizePacketSize相加得到整个数据包的大小,并通过NdisAllocateMemory为其分配存储空间。

3步:调用NdisAllocateBuffer请求分配一个缓冲区描述符来描述刚刚通过调用NdisAllocateMemory而得到的存储空间。

4步:将缓冲区描述符链接到先前得到的数据包描述符上。

至此,一个完整的数据包就组装好了,下面的工作就是数据传输了。由于NIC设计的原因,从以太网接收到的数据包被分为包头和数据两部分分别传输。NdisMoveMappedMemory函数负责把包头部分拷贝前面组装的数据包之中,之后再调用NdisTransferData将数据部分拷贝过来。NdisTransferData返回之后有两种可能的情况,一是数据传输成功完成,二是由于某种原因导致数据传输失败。如果NdisTransferData返回的status值为NDIS_STATUS_PENDING,那么NDIS将负责自动调用PacketTRansferDAtaComplete函数,否则得话控制流继续向下,由PacketReceiveIndicate负责调用PacketTransferDataComplete。总之,不论在哪种情况下,PacketTransferDataComplete这一函数都会用到。

PacketTransferDataComplete函数的实现非常简单,如果先前调用NdisTransferData传输数据成功完成的话,则将数据包插入缓冲区队列中,等待上层应用程序读取。然后再调用IoDecrementIoIncrementIoDecrement两个函数必须成对的使用。

下面再来看一下另一个函数PacketReceivePacket,它完成的工作与PacketReceiveIndicatePacketTransferDataComplete这两个函数共同完成的工作相同,因此当NIC支持同时接收多个数据包时使用PacketReceivePacket的效率更高。二者的不同点在于当NDIS调用PacketReceivePacket时是将包头和数据作为一个整体一起交付的,因此没有必要像PacketReceiveIndicate中的做法那样,将包头和数据分别拷贝到数据包中,这里,我们可以使用NDIS提供的NdisCopyFromPacketToPacket函数一次完成拷贝。

NIC从网络上接收到数据包后,NDIS通过调用PacketReceiveIndicate或者PacketReceivePacket已经将数据包缓存起来。

下面就要等待上层应用程序从缓冲区中读取数据了。

上层发出一个读的请求,IO管理器在接收了该IO请求之后,,将把它传递到数据包协议驱动程序中的最高层驱动程序之前,分配并初始化一个Irp。构造irp的重要依据是上层应用程序在调用ReadFile时所传入的参数。其中,参数nNumberOfBytesToRead被填写到IO堆栈单元中的Parameters.Read.Length处。另外,由于Globals.DriverObjectFlags域被设置为DO_DIRECT_IO,则IRP将相应使用Associatedirp.SystemBuffer域描述该缓冲区。

数据包协议驱动程序所代表的DriverObjectMajorFunction[IRP_MJ_READ]中注册的函数为PacketRead,因此当上层应用程序调用ReadFile时,该IO请求将交由PacketRead函数处理。PacketRead函数负责将缓冲队列中的数据包全部取出,捆绑后一并上交。之所以将缓存的数据包一并上交,而不是上层应用程序每次调用一次ReadFile上交一个数据包,主要是出于以下考虑:一方面,前面已经提到将素有数据包一并上交可以立即清空缓冲区,供NIC从网络上新接收的数据包使用,减少丢包可能;另一个方面;由于众所周知IO操作的速度相对较慢,因此在上层应用程序应尽量减少IO操作的次数。下面看一下PacketRead操作的具体工作流程。

进入PacketRead函数之后,首先由DeviceObject->DeviceExtension得到保存于此的Open结构。接下来是调用IoIncremet对设备的使用情况计数,另外还要检查PacketBindAdapter函数是否已成功打开了NIC以及上层应用程序提供的接收缓冲区是否足够大。做完这些检查之后,将Irp中的MdlAddress作为缓冲区描述符链接到pReservedPacket上以供拷贝数据时使用。拷贝数据的操作由一个循环完成的,每次循环要完成3项任务。

1)第一步通过调用ExinterlockedRemoveHeadList从缓冲队列中取出一个数据包;

PLIST_ENTRY ExInterlockedRemoveHeadList(

  __inout  PLIST_ENTRY ListHead,

  __inout  PKSPIN_LOCK Lock

);

Parameters

ListHead [in, out]

A pointer to the LIST_ENTRY structure that serves as the list header.

Lock [in, out]

A pointer to a KSPIN_LOCK structure that serves as the spin lock used to synchronize access to the list. The storage for the spin lock must be resident and must have been initialized by calling KeInitializeSpinLock. You must use this spin lock only with the ExInterlockedXxxList routines.

Return Value

returns a pointer to the LIST_ENTRY structure removed from the list. If the list was empty, the routine returns NULL.

 

2)第二步将该数据包的数据拷贝到pReservedPacket中;

 

3)第3步将先前缓存数据包所占用的资源都释放掉,以供将来使用。当缓冲队列中的数据包已经全部被取走或者上层应用程序所提供的接收缓冲区已被填满时,循环结束退出。在这之后还有一项工作要完成,就是填写Irp中的IoStatus结构。该结构包含2个域,分别为StatusInformation,这两个域的值将直接影响ReadFile的返回值以及lpNumberOfBytesRead的最终值。当上层应用程序正确得到了它所需要的数据时,status被赋值为STATUS_SUCCESS,Information则是实际完成传输的字节数,当然,最后不要忘记与IoIncrement成对使用的IoDecrement

至此,数据接收介绍完毕,总结:当NIC从网络上接收到数据包时会通知NDIS,由NDIS负责调用PacketReceiveIndicate或者PacketReceivePacketNIC接收到的数据包放到缓冲区队列中保存起来。当上层应用程序通过ReadFile请求读取数据时,PacketRead函数再将数据包从缓冲队列中依次取出,一并上交。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值