面向10 GbE FPGA流量的NDIS过滤驱动高效ACK生成方案问询
高效ACK发送优化方案
针对10 GbE线速UDP场景下接收路径直接发送ACK的瓶颈问题,可通过以下几种方式优化:
延迟批量发送ACK
不要针对每个接收到的UDP包立即发送ACK,而是将生成好的ACK NBL暂存在队列中,当队列达到指定阈值(如64/128个)或经过固定短定时(如1ms)后再批量提交发送。这能大幅减少NdisFSendNetBufferLists()的调用次数,降低内核态调用的固定开销。可利用NDIS定时器(NdisSetTimerObject)定期触发批量发送,或在每次接收处理完成时检查队列长度,达到阈值就提交。将ACK发送移至独立工作上下文
接收路径(FilterReceiveNetBufferLists)属于高优先级处理路径,在此处执行发送操作会抢占接收处理的CPU时间,还可能因发送阻塞导致接收延迟。建议:- 创建NDIS工作项(
NdisAllocateWorkItem)或内核线程; - 接收路径中仅完成ACK NBL的生成与入队操作,快速返回以继续处理接收流量;
- 由工作项/独立线程在PASSIVE_LEVEL上下文下调用
NdisFSendNetBufferLists()发送ACK队列中的数据包。
示例代码片段:
// 接收路径仅执行入队 NdisAppendNblToCountedQueue(&ackQ, pAckNbl); // 触发工作项(避免重复触发) if (!InterlockedCompareExchange(&g_IsAckWorkerActive, 1, 0)) { NdisQueueWorkItem(pFilter->AckWorkItem); } // 工作项处理函数 VOID AckWorkerRoutine(IN PVOID Context) { PNDIS_FILTER Filter = Context; do { PNBL_COUNTED_QUEUE tempQ = {0}; // 原子转移队列至临时变量 NdisAcquireSpinLock(&Filter->AckQueueLock); NdisTransferNblCountedQueue(&tempQ, &Filter->ackQ); NdisReleaseSpinLock(&Filter->AckQueueLock); if (!NdisIsNblCountedQueueEmpty(&tempQ)) { NdisFSendNetBufferLists(Filter->FilterHandle, NdisGetNblChainFromNblCountedQueue(&tempQ), PortNumber, NDIS_SEND_FLAGS_DISPATCH_LEVEL); // 发送完成后需回收NBL至复用池 } } while (!NdisIsNblCountedQueueEmpty(&Filter->ackQ)); InterlockedExchange(&g_IsAckWorkerActive, 0); }- 创建NDIS工作项(
预分配ACK专用内存与NBL池
动态分配内存是高开销操作,尤其在高吞吐量场景下。提前预分配固定资源:- 使用
NdisAllocateNetBufferListPool创建ACK专用的NBL池,设置匹配批量发送阈值的池大小; - 预分配非分页内存作为ACK数据包缓冲区,绑定到NBL的NetBuffer中;
- 生成ACK时直接从池中取出空闲NBL,填充包头后入队,发送完成后回收复用,避免重复分配释放。
- 使用
优化发送标志与NBL链式提交
- 适配上下文设置
sendFlags:在DISPATCH_LEVEL发送时设置NDIS_SEND_FLAGS_DISPATCH_LEVEL避免IRQL切换开销;若ACK无需本地环回,添加NDIS_SEND_FLAGS_SKIP_LOOPBACK_CHECK跳过环回检测,减少处理步骤; - 始终以链式方式提交多个NBL,一次调用
NdisFSendNetBufferLists()提交整个NBL链,利用NDIS批量处理优化。
- 适配上下文设置
避免接收路径IRQL阻塞
FilterReceiveNetBufferLists通常运行在DISPATCH_LEVEL IRQL,此时执行发送若遇资源等待(如发送队列满),会导致接收路径长时间阻塞。将发送移至PASSIVE_LEVEL上下文(如工作项),即使发送等待也不会阻塞高优先级的接收处理。
内容的提问来源于stack exchange,提问作者Rabbit_Drivers
相关产品推荐
相关产品推荐

