一个 NET_BUFFER 代表一个单一的网络帧。 (对 LSO/RSC 进行适当的挥手。)
NET_BUFFER_LIST 是相关 NET_BUFFER 的集合。同一个 NET_BUFFER_LIST 上的每个 NET_BUFFER 都属于同一个“流量”(稍后会详细介绍),它们具有所有相同的元数据,并将对其执行所有相同的卸载。因此,我们使用 NET_BUFFER_LIST 对相关数据包进行分组,并让它们共享元数据。
数据路径通常在多个 NET_BUFFER_LIST 的批次上运行。仅出于性能原因将整个批次组合在一起;批次中的多个 NBL 之间没有很多隐含关系。例外:大多数数据路径例程都采用一个 Flags 参数,该参数可以保存对一批中的所有 NBL 进行某些声明的标志,例如,NDIS_RECEIVE_FLAGS_SINGLE_ETHER_TYPE。
总而言之,您确实可以安全地将多个 NET_BUFFER_LIST 分组为一个指示,这对于性能尤为重要。如果您愿意,您可以将不相关的 NBL 组合在一起。但是,如果您正在组合成批的 NBL,请确保清除任何 NDIS_XXX_FLAGS_SINGLE_XXX 样式标志。 (当然,除非您知道标志的承诺仍然有效。例如,如果您合并 2 批 NBL,它们都具有 NDIS_RECEIVE_FLAGS_SINGLE_ETHER_TYPE 标志,并且如果您验证第一个 NBL在每个批次中具有相同的 EtherType,那么保留 NDIS_RECEIVE_FLAGS_SINGLE_ETHER_TYPE 标志实际上是安全的。)
但是请注意,您通常不能将多个 NET_BUFFER 组合到同一个 NET_BUFFER_LIST 中,除非您控制生成负载的应用程序并且您知道 NET_BUFFER 的负载属于同一流量流。流量的确切语义在 NDIS 层中有点模糊,但您可以想象这意味着任何 NDIS 级别的硬件卸载都可以安全地将每个数据包视为相同。例如,IP 校验和卸载需要知道每个数据包具有相同的伪标头。如果所有的数据包都属于同一个 TCP 或 UDP 套接字,那么它们可以被视为同一个流。
另外,与通过 FilterSendNetBufferLists 发送多包相比,在多包接收中将数据包链接在一起的好处要大得多,这是为什么呢?
接收是昂贵的路径,原因有两个。首先,操作系统必须花费 CPU 来解复用来自网络的原始数据包流。网络可以从任何随机套接字向我们发送数据包,或者根本不匹配任何套接字的数据包,并且操作系统必须为任何可能性做好准备。其次,接收路径处理的是不受信任的数据,所以解析时要谨慎。
相比之下,发送路径非常便宜:数据包只是落入微型端口驱动程序,后者设置了 DMA,然后将它们发送到硬件。发送路径中的任何人都不会真正关心数据包 in 中的实际内容(防火墙在 NDIS 看到数据包之前已经运行,因此您看不到该成本;如果微型端口正在执行任何卸载,那是有偿的在硬件的内置处理器上,因此它不会显示在您可以在任务管理器中看到的任何 CPU 上。)
因此,如果您将一批 100 个数据包分解为接收路径上 1 个数据包的 100 次调用,则操作系统必须通过 100 次调用一些昂贵的解析函数。同时,通过发送路径进行 100 次调用并不是很好,但这只是接收路径 CPU 成本的一小部分。