驱动所创建的设备一般有3种方式:缓冲区方式,直接方式和其他方式(对应DO_BUFFERED_IO, DO_DIRECT_IO和0)。
比如:
fdo->Flags |= DO_BUFFERED_IO
DO_BUFFERED_IO指定当前设备以缓冲区方式工作。
通常用户模式和内核模式交互的时候,用户模式会传一个buffer进来,注意:这个buffer是用户模式的虚拟地址。尽管驱动程序可以访问用户模式的虚拟地址,但是这里有个问题,就是当进程切换的时候,同一个用户模式的虚拟地址对应不同的物理地址,这样当驱动去读取这个地址的时候就会出错,这是灾难性的错误。所以,内核模式的驱动一般都不会直接使用用户模式的虚拟地址(特殊需要也可以使用,但是需要很高的技术要求,而且需要很小心)。
缓冲区方式(DO_BUFFERED_IO)
I/O
管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。驱动程序将使用这个系统缓冲区工作。
I/O
管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。
系统缓冲区的地址用 Irp->AssociatedIrp.SystemBuffer记录,缓冲区的长度记录在IO_STACK_LOCATION里面,比如WriteFile的系统缓冲区的长度记录在IO_STACK_LOCATION的Parameters.Write.Length里面。
以缓冲区方式工作的设备,无论是读还是写,都会发生用户模式地址和内核模式地址的数据复制。这个过程是由操作系统负责,内核模式地址的空间的分配和回收都由操作系统负责,驱动程序无需关心。
缓冲区方式比较简单地解决了用户模式地址传入驱动的问题,同时一个很明显的缺点是:
用户模式地址和内核模式地址的数据复制会影响性能。
适用:少量内存操作。
直接方式(DO_DIRECT_IO)
I/O
管理器锁定了包含用户模式缓冲区的物理内存页,并创建一个称为
MDL(
内存描述符表
)
的辅助数据结构来描述锁定页。驱动程序将使用
MDL
工作。
Irp
->
MdlAddress指向MDL。通过DDK的三个宏:MmGetMdlByteCount, MmGetMdlVirtualAddress, MmGetMdlByteOffset可以得到锁定缓冲区长度,锁定缓冲区起始地址(也就是用户模式程序的buffer的起始虚拟地址)和偏移量。
MDL的长度(通过MmGetMdlByteCount得到)应该和IO_STACK_LOCATION里面的Parameters.Read.Length(假设是ReadFile)相等,不然就出错了。
调用MmGetSystemAddressForMdlSafe可以将用户模式虚拟地址映射成内核模式的地址,这样驱动程序就可以使用这个内核模式的虚拟地址了。
这也就是说直接方式下用户模式地址和内核模式地址指向同一块物理内存。无论进程怎么切换,内核模式地址都不变,这样就可以找到正确的物理内存。
跟缓冲区方式相比,直接方式操作稍微复杂一些,但是省去了用户模式地址和内核模式地址的数据复制,提高了性能。
其他方式(0)
I/O
管理器仅简单地把用户模式的虚拟地址传递给驱动程序。使用用户模式地址的驱动程序应十分小心。
一般很少使用,仅限高手。
总体来讲,优先考虑缓冲区方式,如果内存比较大,考虑直接方式,其他方式在特殊情况下使用(尽量少用,因为比较难控制)。