Thursday, May 16, 2013

设备读写方法小结

DO_BUFFERED_IO法:
    在IoCreateDev的时候需要设置返回的设备对象的pDevObj->Flags |= DO_BUFFERED_IO

    由于所有进程共享高2G/1G的虚拟内存,为了避免进程切换带来的风险,所以用户在WriteFile中提供的缓冲区需要被拷贝到内核模式下的缓冲区中,该缓冲区就是AssociatedIRP.SystemBuffer子域,当前层驱动需要读写该缓冲区的多少字节由该层IO_STACK_LOCATION中的Paraments.Read.Length/Paraments.Write.Length决定。
    至于实际读写到多少字节,由IRP的IoStatus.Information设置

DO_DIRECT_IO法:
   //确定pDevObj->Flags是DO_DIRECT_IO
   IrpSp = IoGetCurrentIrpStackLocation(Irp);
   ReadLength = IrpSp->Parameters.Read.Length;
   MdlLength = MmGetMdlByteCount(Irp->MdlAddress);
   //确定ReadLength == MdlLength
   MdlOffset = MmGetMdlByteOffset(Irp->MdlAddress);
   //缓冲区的虚拟地址
   MdlAddress = MmGetMdlVirtualAddress(Irp->MdlAddress); //MdlAddress == MdlAddress->StartVA + MdlAddress->ByteOffset
   //获得虚拟缓冲区在内核模式下的映射地址
   KernelAddress = MmGetSystemAddressForMdlSafe(Irp->MdlAddress);
  
   //设置KernelAddress的内容;
   //笔者曾经做过实验:给定一个足够大的ReadLength,memset都能正常工作, 可见映射地址是连续内存
   memset(KernelAddress,0xAB,MdlLength)

       Irp->IoStatus.Status = STATUS_SUCCESS;

       Irp->IoStatus.Information = ReadLength;

       IoCompleteRequest(Irp,IO_NO_INCREMENT);

其他方法:
    直接访问Irp->UserBuffer,这种方法很危险,所以要访问UserBuffer的时候,先用ProbeForWrite/ProbeForRead探测一下,如果这两个函数抛出异常,代表不可访问,读写失败。

设备在创建的时候,其读写方法(应该)就是上述三种方法之一。但是这里有一个"矛盾"的地方,为了说明问题,必须先详细介绍以下函数:
BOOL WINAPI DeviceIoControl(
  __in         HANDLE hDevice,
  __in         DWORD dwIoControlCode,
  __in_opt     LPVOID lpInBuffer,
  __in         DWORD nInBufferSize,
  __out_opt    LPVOID lpOutBuffer,
  __in         DWORD nOutBufferSize,
  __out_opt    LPDWORD lpBytesReturned,
  __inout_opt  LPOVERLAPPED lpOverlapped
);
现在介绍dwIoControlCode,程序员可以直接用系统预定义的值(winioctl.h,比如IOCTL_DISK_FORMAT_TRACKS),也可以按照如下规范定义:
#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)
DeviceType
    Identifies the device type. This value must match the value that is set in the DeviceType member of the driver's DEVICE_OBJECT structure.
FunctionCode
    Identifies the function to be performed by the driver. Values of less than 0x800 are reserved for Microsoft. Values of 0x800 and higher can be used by vendors.
TransferType
    Indicates how the system will pass data between the caller of DeviceIoControl (or IoBuildDeviceIoControlRequest) and the driver that handles the IRP. Use one of the following system-defined constants:
    METHOD_BUFFERED
    METHOD_IN_DIRECT or METHOD_OUT_DIRECT
    METHOD_NEITHER

读者可能已经发现,DeviceIoControl有两个缓冲区需要设置读写方式,那么TransferType就需要包含这两个信息,在驱动中需要这么处理:
如果TransferType==METHOD_BUFFERED,那么输入输出都采用AssociatedIRP.SystemBuffer,(ZXXU: 这个buf的长度要求是MAX(nInBufferSize,nOutBufferSize)
如果TransferType==METHOD_IN_DIRECT(暗示hDevice是以读方式打开),那么输入采用AssociatedIRP.SystemBuffer,输出采用MDL;METHOD_OUT_DIRECT同理
如果TransferType==METHOD_NEITHER,那么输入缓冲区采用Parameters.DeviceIoControl.Type3InputBuffer表示,输出采用Irp->UserBuffer

No comments:

Post a Comment