直接 I/O 出错
最常见的直接 I/O 问题未能正确处理零长度缓冲区。 由于 I/O 管理器不为零长度传输创建 MDL,因此零长度缓冲区会在 Irp-MdlAddress> 处生成 NULL 值。
若要映射地址空间,驱动程序应使用 MmGetSystemAddressForMdlSafe,如果映射失败,则返回 NULL,因为驱动程序通过 NULL MdlAddress 时会返回 NULL。 在尝试使用返回的地址之前,驱动程序应始终检查 NULL 返回。
直接 I/O 涉及将用户的地址空间双重映射到系统地址缓冲区,以便两个不同的虚拟地址具有相同的物理地址。 双映射具有以下后果,有时可能会导致驱动程序出现问题:
用户地址虚拟页的偏移量将成为系统页的偏移量。
超出这些系统缓冲区末尾的访问可能会长时间被忽视,具体取决于映射的页面粒度。 除非在页面末尾附近分配调用方缓冲区,否则超出缓冲区末尾的数据将出现在缓冲区中,并且调用方将不知道发生了任何错误。 如果缓冲区的末尾与页面的末尾相吻合,则超出终点的系统虚拟地址可能指向任何内容或可能无效。 这些问题可能很难找到。
如果调用进程具有另一个修改用户内存映射的线程,则当用户的内存映射发生更改时,系统缓冲区的内容将更改。
在这种情况下,使用系统缓冲区存储暂存数据可能会导致问题。 从同一内存位置提取的两个提取可能会产生不同的值。
以下代码片段在直接 I/O 请求中接收字符串,然后尝试将该字符串转换为大写字符:
PWCHAR PortName = NULL; PortName = (PWCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority); // // Null-terminate the PortName so that RtlInitUnicodeString will not // be invalid. // PortName[Size / sizeof(WCHAR) - 1] = UNICODE_NULL; RtlInitUnicodeString(&AdapterName, PortName);
由于缓冲区的格式可能不正确,因此代码将尝试强制 Unicode NULL 作为最后一个缓冲区字符。 但是,如果基础物理内存同时映射到用户和内核模式地址,则进程中的另一个线程可以在此写入操作完成后立即覆盖缓冲区。
相反,如果 NULL 不存在,则对 RtlInitUnicodeString 的调用可能会超过缓冲区的范围,并可能导致 bug 检查是否位于系统映射之外。
如果驱动程序创建并映射自己的 MDL,则它应确保它仅使用已探测的方法访问 MDL。 也就是说,当驱动程序调用 MmProbeAndLockPages 时,它会指定访问方法(IoReadAccess、IoWriteAccess 或 IoModifyAccess)。 如果驱动程序指定 IoReadAccess,则以后不得尝试写入 MmGetSystemAddressForMdl 或 MmGetSystemAddressForMdlSafe 提供的系统缓冲区。