对每个数据包使用 NdisFIndicateReceiveNetBufferLists 还是将它们链接在一起接收?

Using NdisFIndicateReceiveNetBufferLists for every packet vs chaining them all together to receive?

我有一个 NDIS 驱动程序,我将接收到的数据包发送给用户服务,然后该服务将这些数据包标记为正常(非恶意),然后我遍历可以接收的数据包,然后发送一个通过将它们中的每一个转换回具有一个 NetBuffer 的正确 NetBufferList,然后我使用 NdisFIndicateReceiveNetBufferLists 指示它们。

这会导致在通过 SMB 传输大文件(从共享中复制文件)时出现问题,从而显着降低传输速度。

作为解决方法,我现在将所有正常的 NBL 链接起来(而不是一个一个地发送),然后通过 NdisFIndicateReceiveNetBufferLists 一次发送所有这些。

我的问题是,此更改会导致任何问题吗?一个一个发送 X 个 NBL 与将它们链接在一起并一次发送所有 NBL 之间有什么区别? (因为它们中的大多数可能与不同的 flows/apps 相关)

此外,与通过 FilterSendNetBufferLists 发送的多数据包相比,在多数据包接收中将数据包链接在一起的好处要大得多,这是为什么呢?

一个NET_BUFFER代表一个网络框架。 (对 LSO/RSC 有一些适当的 hand-waving。)

一个 NET_BUFFER_LIST 是一个 collection 的相关 NET_BUFFER。同一个 NET_BUFFER_LIST 上的每个 NET_BUFFER 都属于同一个“流量”(稍后会详细介绍),它们具有所有相同的元数据,并且将对它们执行所有相同的卸载。因此,我们使用 NET_BUFFER_LIST 对 相关 数据包进行分组,并让它们共享元数据。

数据路径通常对多个 NET_BUFFER_LIST 的批处理进行操作。出于性能原因,整个批次仅组合在一起;一个批次中的多个 NBL 之间没有太多隐含关​​系。例外:大多数数据路径例程采用一个标志参数,该参数可以保存标志,这些标志对一批中的所有 NBL 做出一些声明,例如,NDIS_RECEIVE_FLAGS_SINGLE_ETHER_TYPE.

总而言之,您确实可以安全地将多个 NET_BUFFER_LIST 分组到一个指示中,这对于 perf 尤为重要。如果愿意,您可以将不相关的 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_BUFFERs'有效载荷属于同一个流量流。流量流的确切语义在 NDIS 层中有点模糊,但您可以想象这意味着任何 NDIS-level 硬件卸载都可以安全地将每个数据包视为相同。例如,IP 校验和卸载需要知道每个数据包都具有相同的 pseudo-header。如果所有数据包都属于同一个 TCP 或 UDP 套接字,那么它们可以被视为同一个流。

Also, the benefit of chaining packets together is much greater in multi packet receive compared to multi packet send via FilterSendNetBufferLists, why is that?

接收是昂贵的路径,原因有二。首先,OS 必须花费 CPU 来解复用来自网络的原始数据包流。网络可以从任何 运行dom 套接字向我们发送数据包,或者根本不匹配任何套接字的数据包,并且 OS 必须为任何可能性做好准备。其次,接收路径处理的是不可信的数据,所以要谨慎解析。

相比之下,发送路径非常便宜:数据包直接落到微型端口 driver,后者设置了 DMA,然后将它们发送到硬件。发送路径中没有人真正关心 数据包中的实际内容(在 NDIS 看到数据包之前防火墙已经 运行,所以你看不到那个成本;如果微型端口正在执行任何卸载,这是在硬件的 built-in 处理器上支付的,因此它不会显示在您可以在任务管理器中看到的任何 CPU 上。)

因此,如果您将一批 100 个数据包拆分为接收路径上的 1 个数据包的 100 次调用,OS 必须对一些昂贵的解析函数进行 100 次调用。同时,通过发送路径进行 100 次调用并不是很好,但它只是接收路径 CPU 成本的一小部分。