网络驱动程序--数据传输流程

来源:互联网 发布:如何清除淘宝缓存 编辑:程序博客网 时间:2024/06/04 18:32

DMA的英文拼写是“Direct Memory Access”,汉语的意思就是直接内存访问,是一种不经过CPU而直接从内存了存取数据的数据交换模式。PIO模式下硬盘和内存之间的数据传输是由CPU来控制的;而在DMA模式下,CPU只须向DMA控制器下达指令,让DMA控制器来处理数的传送,数据传送完毕再把信息反馈给CPU,这样就很大程度上减轻了CPU资源占有率。

 

DMA模式与PIO模式的区别就在于,DMA模式不过分依赖CPU,可以大大节省系统资源,二者在传输速度上的差异并不十分明显。DMA模式又可以分为Single-Word DMA(单字节DMA)和Multi-Word DMA(多字节DMA)两种,其中所能达到的最大传输速率也只有16.6MB/s。

 

发送步骤

微端口驱动程序发送数据包的调用顺序,取决于微端口所管理的NIC的类型。本节描述总线控制器DMA NICPIO设备以及使用NIC板上内存来发送包的步骤。

 

在总线控制器DMA NIC上发送包

在总线控制器DMA NIC上发送包所涉及的步骤取决于在DMA初始化过程中微端口如何初始化或如何保存系统资源:

n     管理一个总线控制器DMA NIC的串行微端口必须从MiniportInitialize函数中调用NdisMAllocateMapRegisters,以便为DMA操作分配映射寄存器。

n     管理总线控制器DMA NIC的非串行或面向连接的NIC应当在其MiniportInitialize函数中调用NdisMInitializeGatherDma,以将DMA资源初始化。使用NdisMInitializeScatterGatherDma的优点在于系统资源,例如映射寄存器,是动态地为每个DMA传输分配和释放的(因而更好地利用系统资源),而且DMA通过流水线进程得到了映射到虚拟范围的物理地址,从而改进了运行性能。管理总线控制器DMA NIC的非串行或面向连接的微端口可调用NdisMAllocateMapRegisters来静态地分配映射寄存器,而不是调用NdisMInitializeScatterGather-Dma,然而这导致DMA运行性能较差和系统资源利用较差。

 

NdisMAllocateMapRegisters保存DMA资源的微端口

一般而言,管理总线控制器DMA NIC的无连接微端口(串行的或非串行的)输出MiniportSendPackets函数以使其NIC获取最佳性能。管理总线控制器DMA NIC的面向连接微端口总是输出MiniportCoSendPackets

在其Miniport(Co)SendPackets函数接收一个或多个包描述符之后,在DMA初始化期间,用NdisMAllocateMapRegisters分配映射寄存器(见3.2.2.3节)的微端口在用DMA发送包时,执行下述步骤:

1.    微端口调用NdisQueryPacket来读入包的长度,获取第一个缓冲描述符的指针,并确定缓冲描述符的数目及具体段数。

2.    微端口需要确定它是否有可供利用的发送缓冲区和可供利用的发送缓冲区描述符,这种描述符映射将缓冲区映射为环形。若这些资源都不能利用,串行微端口将不能以NDIS_STATUS_RESOURCES为参数调用MiniportSendMiniportSendPackets,只能将数据包返回给NDIS;或者在其内部对包进行排序,并返回NDIS_STATUS_PENDING。非串行微端口或面向连接端口总是在内部对包排序并返回。

3.    DMA设备限制物理段数目的上限,这些段可映射到单个的DMA操作。若让微端口发送一个过于细小的数据片,它必须将连接到包的缓冲区拷贝到单一的发送缓冲区中。微端口应当从它的MiniportInitialize函数分配这一分段缓冲区和缓冲描述符,以便进行映射,微端口调用NdisMoveMemory从协议提供的缓冲区中将数据转移到这一分段缓冲区中。只有当包过于细小时,微端口才必须拷贝发送数据,因为拷贝到分段缓冲区是一项昂贵的操作,如果做的过于频繁会对运行性能会产生负面影响。一般而言,若待发送缓冲区小于256字节,微端口可通过将数据拷贝到分段缓冲区,并将微端口分配的描述符传给NdisMStartBufferPhysicalMapping来改进性能。

4.    微端口调用NdisMStartBufferPhysicalMapping映射以前分配的共享内存缓冲区,它的内容将被发送。这一调用产生一个布尔变量,WriteToDevice,它表明数据是否由主机内存转移到NIC或由NIC转移到主机内存。对于发送,WriteToDevice设为TRUE。微端口也必须提供用于映射缓冲区的映射寄存器的索引,而且这一索引必须是在微端口初始化期间调用NdisMAllocateMapRegisters返回的寄存器之一。

5.    若微端口为其发送缓冲区分配缓冲内存,它将调用NdisFlushBuffer,为发送缓冲区提供地址并指定WriteToDevice参数为TRUE,以表明传输方向是由主机到NICNdisFlushBuffer刷新处理器缓冲内存线,保证NIC所见的内存内容与微端口内容相同。因为高速缓存线破裂对于发送数据不构成影响,微端口可以刷新数据而不必涉及整个缓冲内存。为保证所有平台上缓冲内存的一致性,微端口也应调用NdisM-UpdateSharedMemory

6.    微端口为传输数据而对NIC编程,例如,写入控制寄存器,NIC则进行DMA传输。

7.    当发送完成时,微端口调用NdisMCompleteBufferPhysicalMapping释放映射寄存器。如果NIC在发送完成时发生中断,那么这一调用由MiniportHandleInterrupt来执行。若NIC轮询,这一调用在时钟停止且发送结束时,由提供给NdisMSetPeriodTimerNdisMSetTimer(若端口不用周期时钟)的MiniportTimer函数来执行。

8.    微端口调用NdisMSendCompleteNdisMCoSendComplete

 

NdisMInitializeScatterGatherDma初始化DMA资源的微端口。

一般而言,管理总线控制器DMA NIC的非串行微端口输出一个MiniportSendPackets函数以使其NIC达到最佳性能。管理总线控制器DMA NIC的面向连接微端口总是输出一个MiniportCoSendPackets函数。

在其Miniport(Co)SendPackets函数中接收一个或多个包描述符后,在DMA初始化期间,用NdisMInitializeScatterGatherDma初始化系统资源(见3.2.2.3节)的总线控制器DMA微端口在用DMA发送包时,执行下述步骤:

1.    微端口用ScatterGatherPacketInfo的一个InfoType调用NDIS_PER_PACKET_INFO_ FROM_PACKETNDIS_PER_PACKET_INFO_FROM_PACKET将一指针返回给SCATTER_GATHER_LIST结构。此结构设定SCATTER_GATHER_ELEMENT结构的一个数组,数组的每一个值设定了DMA数据邻近范围的基本物理地址和长度,以便发送。

2.    微端口可能需要确定它是否有可供利用的发送缓冲区和可供利用的发送缓冲区描述符,这种描述符映射将缓冲区映射为环形。若这些资源都不能利用,非串行或面向连接的微端口在内部将包排序并返回。

3.    DMA设备具有物理段数目的上限,这些段可映射到单个的DMA操作。若让微端口发送一个过于细小的数据片,它必须将连接到包的缓冲区拷贝到单一的发送缓冲区中。微端口应由它的MiniportInitialize函数调用NdisMAllocateSharedMemory来分配这一分段缓冲区和缓冲描述符,以便进行映射。微端口调用NdisMoveMemory从协议提供的缓冲区将数据转移到这一分段缓冲区中。只有当包过于细小时,微端口才必须拷贝发送数据,因为拷贝到分段缓冲区是一项昂贵的操作,如果做的过于频繁会对运行性能产生负面影响。一般而言,若待发送缓冲区小于256字节,微端口可通过将数据拷贝到分段缓冲区来改进性能。

4.    微端口为其传输缓冲区分配缓冲内存,它调用NdisFlushBuffer,来提供发送缓冲区的地址,并将WriteToDevice参数设为TRUE以指示传输的方向是从主机到NICNdisFlushBuffer刷新处理器缓冲内存线,保证NIC所见的内存内容与微端口内容相同。因高速缓存线破裂对于发送数据不构成影响,微端口可刷新数据而不必涉及整个缓冲内存。为保证所有平台上缓冲内存的一致性,微端口也应调用NdisMUpdateSharedMemory

5.    微端口为传输数据而给NIC编程,例如,写入控制寄存器,NIC则进行DMA传输。

微端口调用NdisMSendCompleteNdisCoSendComplete。对Ndis(Co)SendComplete的调用释放用于DMA传输的映射资源。

 

PIO设备上发送单包

对使用可编程I/OPIO)来发送数据的NIC来说,控制它的微端口在其发送函数中执行下列步骤:

1.         调用NdisQueryPacket得到包的长度,然后微端口检查是否提供有用于发送数据包的传输资源,如果没有,微端口返回NDIS_STATUS_RESOURCE或者内部将包排队,并从其发送处理程序返回。如果微端口中数据包排进了队列,微端口必须为这个包返回NDIS_STATUS_PENDING。如果微端口支持带外(OOB)数据,它将使用NDIS宏从OOB块中读取任何有关的数据。如果提供了发送资源,NIC驱动程序将继续。

2.         调用NdisQueryPacket得到第一个包描述符缓冲区的指针。

3.         调用NdisQueryBuffer获得包含待发送数据缓冲区的虚拟地址以及这个缓冲区的字节数。

4.         调用NdisRawWritePortXxx将缓冲区的长度写到端口上。管理PIO NIC的微端口保证将正确的数据传输给NIC,所以,这样的微端口不调用NdisFlushBuffer

5.         调用NdisRawWritePortBufferXxx写缓冲区——其地址是从NdisQueryBuffer返回的。

6.         调用NdisGetNextBuffer得到包的下一个缓冲区。

7.         重复36,直到所有的包缓冲区都被成功地送出。

8.         MiniportSend总是把发送状态作为MiniportSend状态返回。如果这个状态不是NDIS_STATUS_PENDING,微端口将包描述符和资源返回给调用者。串行微端口的MiniportSendPackets函数在包描述符的OOBSend成员中返回发送的状态。如果微端口对任何一个包返回未决状态,在它使用包资源而且准备将包返回给调用者时,它必须调用NdisMSendCompleteNDIS库在它提交给非串行和面向连接微端口Miniport(Co)SendPackets函数的所有包描述符中忽略了OOB数据块。NDIS假定这样的微端口(与NdisM(Co)SendComplete异步)将包描述符传递给MiniportSendPacketsMiniportCoSendPackets函数。因此,Miniport(Co)SendPackets常常忽略NDIS_PACKET_OOB_DATA块的Status成员,但它可以将这一成员设置为某个状态,这个状态与后来传给NdisM(Co)SendComplete的状态相同。

 

使用板上内存发送包

对板上内存出现在主机的I/O空间的NIC,支持它的微端口从MiniportInitialize中调用NdisMMapIoSpace,以将适配器的板上内存映射到主机内存中,并给微端口一个可用于引用内存的虚拟地址。在这种类型的NIC上,微端口发送数据时,先将待发送的缓冲区转移到映射内存中,然后以一种设备独特的方式,调用NdisMRegisterIoRange写微端口声明的端口(或寄存器),这些动作将使得NIC把数据放到网络介质上。如果微端口缺乏发送资源,它就会或者将包排队,或者将包返回给NDIS,就象以前所描述的那样。

如果微端口有可以利用的发送资源,它将调用:

1.         NdisQueryPacket,得到包含待发送缓冲区的包的起始和长度。

2.         NdisQueryBuffer,得到缓冲区描述符。

3.         NdisMoveToMappedMemory,每次转移一个缓冲区到映射的适配器内存缓冲区。

4.         NdisGetNextBuffer,从数据包中回收下一个缓冲区,然后再调用NdisMoveToMappedMemory,重复这两个调用直到所有的缓冲区全部发送出去。

5.         NdisMRawWritePortXxxNdisWriteRegisterXxx引起设备特有方式发送的发生。

 

在开始拷贝之前,微端口用从NdisQueryPacket返回的长度来查明包含所有包缓冲区的板上内存是否有足够的空间。如果NIC内存对当前的发送没有足够的自由空间,串行微端口将返回一个NDIS_STATUS_RESOURCE状态,在这种情况下,NDIS将包排队且在以后微端口指示有可供使用的资源时重新发送。从另一个方面来说,微端口也可以返回NDIS_STATUS_PENDING,然后内部将包排队。在这种情况下,非串行微端口、面向连接的微端口以及WAN微端口必须返回NDIS_STATUS_PENDING,内部对包进行排队。

 

从属(SlaveDMA设备发送包

从属DMA NIC使用系统DMA控制器,将提供给发送处理程序(一般为MiniportSend)的包发送到用于网络传输的NIC上,从属DMA NIC的微端口授权NDISDMA传输期间确保高速缓存的一致性。

对任何一个微端口有发送资源的数据包,从属DMA MIC的微端口进行下列步骤:

1.         调用NdisMSetupDmaTransfer建立用于发送的系统DMA控制器。微端口通过调用NdisMRegisterDmaChannel传递一个句柄给在微端口初始化期间分配的DMA通道,并传递一个指向包含待发送数据缓冲区的主机内存的指针,同时传递一个缓冲区的偏移量,并将数据的长度、指示传输方向的布尔值从主机传给设备。在NdisMSetupDmaTransfer返回中,控制器通过编程来传输数据,也包括确保缓冲区的高速缓存一致性。

2.         执行设备相关的步骤以引起传输(例如调用NdisRawWritePortXxx)。

3.         当传输完成时,微端口调用NdisMCompleteDmaTransfer

 

一般来说,微端口为传输建立了完整的缓冲区。然而若缓冲区的大小大于NICDMA约束,微端口将用长度和偏移量参数来发送缓冲区的一段;如果待发送缓冲区小于256字节,微端口可通过将数据拷贝到分段缓冲区,并传递一个微端口分配的缓冲区包描述符到NdisMSetuoDmaTransfer以提高性能。

 

------------------------------------------------------------------

 

接收数据的步骤

微端口怎样处理接收数据依赖于其所管理的NIC类型的一些问题。本节将讨论下列各个设备类型接收数据的处理过程:

n         总线控制器DMA NIC

n         PIO NIC

n         带板上内存的非DMA NIC

对这些设备有一些公共的操作,它们包括:

n         微端口调用NdisRawWritePortXxx函数为其NIC编程。

n         指示一个缓冲区之前,指示一个完整包结构的微端口必须调用NdisAdjustBuffer-Length以调整描述缓冲区的缓冲区描述符的长度。微端口必须将缓冲区描述符的长度设为缓冲区中数据的长度。当包返回时,微端口必须再调用这个函数以将它重置为真实缓冲区的长度。

n         微端口不再启用更多的接收操作,直到当前数据已经被指示,并且一个新的缓冲区集已经被分配给接收环或者被有关的协议驱动程序拷贝为被指示的数据。

 

在总线控制器DMA NIC上接收数据

当接收到一个或多个包时,微端口进行下列操作步骤:

调用NdisFlushBufferNdisMUpdateShareMemory确保接收到的数据在微端口访问的内存区域中的一致性。

调用NdisAdjustBufferLength调整缓冲区描述符域的大小(这个描述符域在指示之前映射网络接口卡(NIC)的接收/环状缓冲区)以匹配缓冲区中数据的实际长度。当包返回时,微端口必须再调用NdisAdjustBufferLength将接收/环状缓冲区回调到原来所分配的大小。

如果微端口通过调用NdisMIndicateReceivePacketNdisMCoIndicateReceive-Packet指示一个包数组,它必须设置与每个包描述符有关的OOB数据块的Status成员。如果微端口因为将要用尽接收缓冲区,而决定必须强迫协议驱动程序拷贝指示的数据时,它应当设置OOB块的Status成员为NDIS_STATUS_RESOURCE。否则微端口将把Status成员设为NDIS_STATUS_SUCCESSNDIS_STATUS_ FAILURENDIS_STATUS_PENDING或者由驱动程序确定的状态。

如果无连接微端口通过调用NdisMXxxIndicateReceive指示一个单包,那么微端口指示出所有接收数据,与原先大小一样指示出其整个大小。

在包指示(第3步)或拷贝(第4步)之后,微端口代替环上的缓冲区描述符。缓冲区描述符被下列之一代替:

n         一个先前分配,当前可用的包的缓冲区描述符。

n         一个协议驱动程序强迫拷贝和返回的包的缓冲区描述符。

n         一个通过调用NdisAllocateBuffer分配的缓冲区描述符,它是为另一个描述(调用NdisMAllocateShareMemoryAsync分配的)缓冲区的缓冲区描述符而使用的。

接收变长帧的NIC的微端口必须对帧进行链接和拆链,依靠接收数据的数量来构造不同大小的帧。一些微端口在使用固定长度帧的介质上接收数据,这种微端口能立即分配固定大小的缓冲区,将它们链接进包描述符,不必进行链接和拆除缓冲区就能使用它。

如果微端口调用NdisMAllocateSharedMemoryAsync来分配缓冲区,在分配完成时,将调用MiniportAllocateComplete函数。

 

 

 

PIO NIC上接收数据

可编程I/O网络接口卡的微端口通过调用NdisRawReadPortXxx和使用先前由NdisMRegisterIoPortRange声明的端口,从NIC端口读取数据。具有代表性的这样的一个设备通过调用具有过滤器特性的NdisXxxIndicateReceive函数,在MiniportTransferData函数中向有关的协议驱动程序传输剩余部分数据,将接收包的预先分配部分指示给绑定协议驱动程序。在数据传输之后,接收中断重新启用,数据就可以被新的数据所覆盖了。

PIO NIC的微端口不应因当前传输到上层协议驱动程序的时间延迟,而导致新的数据丢失。如果NIC有相对较小的板上FIFO缓冲区,微端口能在其MiniportInitialize函数预分配一个缓冲区,然后将接收数据转移到所指示的缓冲区,转移的过程试图保持FIFO尽可能地空,以利于接收新的数据。

换句话说,当调用微端口的MiniportTransferData函数时,微端口能指示数据直接从NIC端口拷贝,直接从NIC移动接收的剩余数据。这种接收技术招致了一个时间延迟——它等价于调用NdisXxxIndicateReceive指示接收数据到调用MiniportTransferData,将接收的数据移进MiniportTransferData提供的缓冲区之间的时间。

如果微端口调用NdisMIndicateReceivePacket或者NdisMCoIndicateReceivePacket,在它作调用之前,它将数据传输到链接到预分配包的缓冲区中。

 

在内存映射设备上接收数据

管理带板上内存的NIC的微端口通常直接从NICMiniportTransferData接收的数据传输到调用MiniportTransferData提供的缓冲区中。象可编程I/OPIONIC微端口一样,如果因为直接从NIC向协议驱动程序提供的缓冲区传输数据而引起等待时间,使得可能有新传来的数据丢失,那么微端口就能进行这样的接收。

微端口调用NdisMXxxIndicateReceive指出等价于预先分配数据大小的数据量,或者如果缓冲区较小,而且微端口也已经完成了这个缓冲区,它将指示出整个缓冲区。MiniportTransferData调用NdisMoveFromMappedMemoryNIC的板上内存传输数据到协议驱动程序包或缓冲区中。数据被传输之后,微端口再启用中断,以使新的数据能被接收。

如果微端口调用NdisMIndicateReceivePacketNdisMCoIndicateReceivePacket,那么在调用之前,它将数据传输到链接至预分配包的缓冲区中。