直接 I/O 出错

最常见的直接 I/O 问题是无法正确处理零长度缓冲区。 由于 I/O 管理器不会为零长度传输创建 MDL,因此零长度缓冲区会在 Irp-MdlAddress> 上生成 NULL 值。

若要映射地址空间,驱动程序应使用 MmGetSystemAddressForMdlSafe,如果映射失败,它将返回 NULL ,就像驱动程序通过 NULLMdlAddress 一样。 在尝试使用返回的地址之前,驱动程序应始终检查 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 时,它会指定 (IoReadAccessIoWriteAccessIoModifyAccess) 的访问方法。 如果驱动程序指定 IoReadAccess,则以后不得尝试写入由 MmGetSystemAddressForMdlMmGetSystemAddressForMdlSafe 提供的系统缓冲区。