NDIS 自旋锁为处理器之间的同步提供了完整的内存屏障。这个障碍确实等同于您的代码所暗示的MemoryBarrier() 例程。如果您只是多线程,那么您提供的代码示例将是足够的同步。
(其实NdisWriteRegisterUlong()也提供了内存屏障,所以你的代码示例中存在冗余内存屏障。如果在释放自旋锁和寄存器访问之间没有其他代码,你可以通过切换到WRITE_REGISTER_NOFENCE_ULONG 来消除其中一个内存屏障。但稍后,我们将在那里添加一些代码,以便该屏障变得有意义。)
但是,由于您在处理器和设备之间共享数据,因此理论上您还需要一个原语来同步设备对内存的访问。 KeFlushIoBuffers() 就是这么原始。请注意,某些 DMA 例程(BuildScatterGatherList 等)将在内部刷新缓冲区,因此您不必总是直接调用 KeFlushIoBuffers()。但是,如果您只是将命令写入环形缓冲区而不在每次写入后调用任何 DMA 例程,那么 DMA 不可能知道何时代表您刷新 IO 缓冲区。在这种情况下,您必须自己冲洗。
沉迷于思想实验可能会有所帮助:如果您要(以某种方式)找到一个单处理器系统,MemoryBarrier() 可以实现为无操作。但是您仍然需要其他东西来在处理器和连接 PCIe 的网络适配器之间进行同步。 KeFlushIoBuffers() 是别的东西。
KeFlushIoBuffers() 在 PCIe 总线是高速缓存一致性时是无操作的,例如,因为它在架构上需要在大多数 x86 和 x64 平台上。因此,如果您只在 x64 上测试您的代码,那么实际上很容易忘记包含 IO 刷新。这就是为什么您可能不会看到其他驱动程序使用此例程的次数与理论上应该使用的一样多。
作为旁注,我建议使用 NT 同步原语(KeAcquireSpinLock 等)而不是 NDIS 同步原语。 NDIS 锁被设计为操作系统抽象层,因此可以为 Windows 9x、Windows CE、OS/2 和 Windows NT 重新编译相同的源代码。如今,大多数开发人员只需要担心 NT,就可以安全地直接使用 NT 例程。 NDIS 套路不会增加价值,如果你只使用 NT 套路,你的代码会被更多人理解。
您的最终代码可能如下所示:
KeAcquireSpinLock(&command_buffer_lock);
write_to_command_buffer();
KeReleaseSpinLock(&command_buffer_lock);
KeFlushIoBuffers(command_buffer_mdl, FALSE, TRUE);
WRITE_REGISTER_ULONG(&bar->command_buffer_doorbell);
如果从来没有其他处理器竞相访问共享内存,那么您根本不需要自旋锁!例如,如果您只从专用线程中访问共享内存,那么您可以将代码更新为:
write_to_command_buffer();
KeFlushIoBuffers(command_buffer_mdl, FALSE, TRUE);
WRITE_REGISTER_ULONG(&bar->command_buffer_doorbell);