这种方式需要在创建完设备对象后,为设备设置属性DO_DIRECT_IO
和缓冲读写设备不同,直接方式读写设备,操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址空间中再映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。无论操作系统如何切换进程,内核模式地址保持不变。
操作系统(IO管理器)先将用户模式的地址锁定后,操作系统(IO管理器)用内存描述符表(MDL 数据结构)记录这段内存。如下图。用户模式的这段缓冲区在虚拟内存上是连续的,但是在物理内存上可能是离散的。
MDL记录这段虚拟内存,这段虚拟内存的大小存储在Mdl->ByteCount里,这段虚拟内存所在第一个页首地址是Mdl->StartVa,这段虚拟内存的首地址对于第一个页首地址的偏移量是Mdl->ByteOffset。因此,这段虚拟内存的首地址应该是:
Mdl->StartVa + Mdl->ByteOffset
下面是DDK定义的几个宏,方便我们得到这几个数值。
#define MmGetMdlByteCount(Mdl) ((Mdl)->ByteCount)
#define MmGetMdlByteOffset(Mdl) ((Mdl)->ByteOffset)
#define MmGetMdlVirtualAddress(Mdl) \
((PVOID)((PCHAR)((Mdl)->StartVa) + ((Mdl)->ByteOffset))
例子:
// 读
NTSTATUS Read(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PIO_STATCK_LOCATION IrpSp;
ULONG ReadLength;
ULONG MdlLength;
ULONG MdlOffset;
PVOID MdlAddress;
ULONG KernelAddress;
// 获得当前堆栈单元
IrpSp = IoGetCurrentIrpStackLocation(Irp);
// 获取应用层要读的字节数
ReadLength = IrpSp->Parameters.Read.Length;
// 得到应用层被锁定的虚拟缓冲区的长度
MdlLength = MmGetMdlByteCount(Irp->MdlAddress);
// 得到锁定缓冲区的偏移量
MdlOffset = MmGetMdlByteOffset(Irp->MdlAddress);
// 得到锁定缓冲区的虚拟地址
MdlAddress = MmGetMdlByteOffset(Irp->MdlAddress);
// 获得虚拟缓冲区在内核模式下的映射地址
KernelAddress = MmGetSystemAddressForMdlSafe(Irp->MdlAddress);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
retrun STATUS_SUCCESS;
}