I/O Request Packet

来源:互联网 发布:巴黎必去景点 知乎 编辑:程序博客网 时间:2024/06/18 14:32
第5章 I/O Request Packet5.1 数据结构在处理 I/O 请求上,有两个重要的数据结构:IRP(I/O request packet)和 IO_STACK_LOCATION5.1.1 IRP 的结构下面是在 windbg 里得到的 IRP 结构:nt!_IRP   +0x000 Type             : Int2B   +0x002 Size             : Uint2B   +0x004 MdlAddress       : Ptr32 _MDL   +0x008 Flags            : Uint4B   +0x00c AssociatedIrp    : <unnamed-tag>   +0x010 ThreadListEntry  : _LIST_ENTRY   +0x018 IoStatus         : _IO_STATUS_BLOCK   +0x020 RequestorMode    : Char   +0x021 PendingReturned  : UChar   +0x022 StackCount       : Char   +0x023 CurrentLocation  : Char   +0x024 Cancel           : UChar   +0x025 CancelIrql       : UChar   +0x026 ApcEnvironment   : Char   +0x027 AllocationFlags  : UChar   +0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK   +0x02c UserEvent        : Ptr32 _KEVENT   +0x030 Overlay          : <unnamed-tag>   +0x038 CancelRoutine    : Ptr32     void    +0x03c UserBuffer       : Ptr32 Void   +0x040 Tail             : <unnamed-tag>MdlAddress 用来描述 user-mode buffer 的 MDL(memory descriptor list),这个域仅用于“direct I/O”。假如最上层的 device object 的 flags 标志设置为 DO_DIRECT_IO 时:(1) I/O 建立 IRP_MJ_READ 和 IRP_MJ_WRITE 时使用 MDL。(2) I/O 建立 IRP_MJ_DEVICE_CONTROL 时假如 control 代码为 METHOD_IN_DIRECT 或 METHOD_OUT_DIRECT,使用 MDL。MDL 描述 user-mode virtual buffer 也包含对应的 physical address,driver 使用它能尽快地访问 user-mode buffer。 AssociatedIrq 是一个 union 成员,它的结构如下:union {        struct _IRP *MasterIrp;                                 // 此 IRP 是 associate IRP,它指向 master IRP        LONG IrpCount;                                          // 此 IRP 是 master IRP,它指示 associate IRP 的个数        PVOID SystemBuffer;                                     // 此 IRP 是 master IRP,它指向 system buffer} AssociatedIrp;                                                // 用于和 user-mode buffer 进行数据交换。AssociatedIrq.SystemBuffer 指向 kernel-mode nonpaged 的 data buffer 区域,它使用在下面情形:(1) 在 IRP_MJ_READ 和 IRP_MJ_WRITE 操作里,假如最上层的 device object 的 flags 提供了 DO_BUFFERED_IO(2) 在 IRP_MJ_DEVICE_CONTROL 操作里,假如 I/O control code 指示需要 buffer。调用 WriteFile() 或者 DeviceIoControl() 用作输入 dataI/O manager 复制 user-mode data buffer 到 kernel-mode data buffer 里。(3) 在读操作里,I/O manager 复制 kernel-mode data buffer 到 user-mode data buffer 里。IoStatus 是一个结构体,包含了两个域:Status 与 Information。IoStatus.Status 接收 NTSTATUS 码,而 IoStatus.Information 是 ULONG 类型,接收一个确切的值依赖于 IRP 的类型和完成的状态。一个通常的用法是:Information 域保存传送数据的 bytes 数(例如在 IRP_MJ_READ 操作上)。它的结构类似如下:typedef struct _IO_STATUS_BLOCK{        union {                ULONG Status;                PVOID Pointer;        };        ULONG Information;} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;RequestorMode 是 UserMode 或者 KernelMode 这个值之一。CancelRoutine 是 driver 中 IRP cancel routine 的地址,需要使用 IoSetCancelRoutine() 设置,避免直接对它进行设置。UserBuffer 是保存 user-mode 的 data buffer,与 kernel-mode data buffer 进行数据交换。最后是 Tail 成员,它是一个 union 变量,这个 Tail 比较复杂,它的结构如下:union{struct{union{KDEVICE_QUEUE_ENTRY DeviceQueueEntry;struct{PVOID DriverContext[4];};};PETHREAD thread;PCHAR AuxiliaryBuffer;struct{LIST_ENTRY ListEntry;union{struct _IO_STACK_LOCATION *CurrentStackLocation;ULONG PacketType;};};PFILE_OBJECT OriginalFileObject;} Overlay;KAPC Apc;PVOID CompletionKey;} Tail;Tail union 包括三个部分:Overlay,Apc 以及 CompletionKey。5.1.2 I/O stack当 kernel-mode 程序建立一个 IRP 时,它同时也建立相应的 IO_STACK_LOCATION 结构的数组,每个元素被称为 stack location。一个 stack location 包含着 IRP 的 type 和 parameter 信息,也包含着 completion routine 地址,它的结构如下:nt!_IO_STACK_LOCATION   +0x000 MajorFunction    : UChar   +0x001 MinorFunction    : UChar   +0x002 Flags            : UChar   +0x003 Control          : UChar   +0x004 Parameters       : <unnamed-tag>   +0x014 DeviceObject     : Ptr32 _DEVICE_OBJECT   +0x018 FileObject       : Ptr32 _FILE_OBJECT   +0x01c CompletionRoutine : Ptr32     long    +0x020 Context          : Ptr32 Void其中,Parameters 成员定义为一个复杂的 union 结构,如下:union{//// NtCreateFIle 参数//struct {PIO_SECURITY_CONTEXT SecurityContext;ULONG Options;USHORT POINTER_ALIGNMENT FileAttributes;USHORT ShareAccess;ULONG POINTER_ALIGNMENT EaLength;} Create;//// NtCreateNamedPipeFile 参数//struct{PIO_SECURITY_CONTEXT SecurityContext;ULONG Options;USHORT POINTER_ALIGNMENT Reserved;USHORT SharedAccess;PNAMED_PIPE_CREATE_PARAMETERS Parameters;} CreatePipe;//// NtCreateMailsotFIle  参数//struct{PIO_SECURITY_CONTEXT SecurityContext;ULONG Options;USHORT POINTER_ALIGNMENT Reserved;USHORT SharedAccess;PMAILSLOT_CREATE_PARAMETERS Parameters;} CreateMailslot;//// NtReadFile 参数//struct{ULONG Length;ULONG PINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} Read;//// NtWriteFile 参数//struct{ULONG Length;ULONG POINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} Write;//// NtQueryDirectoryFile 参数//struct{ULONG Length;PSTRING FileName;FILE_INFORMATION_CLASS FileInformationClass;ULONG POINTER_ALIGNMENT FileIndex;} QueryDirectory;//// NtNotifyChangeDirectoryFile 参数//struct{ULONG Length;ULONG POINTER_ALIGNMENT CompletionFilter;} NotifyDirectory;//// NtQueryInformationFile 参数//struct{ULONG Length;FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;} QueryFile;//// NtSetInformationFile 参数 //struct{ULONG Length;FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;PFILE_OBJECT FileObject;union{struct{BOOLEAN ReplaceIfExists;BOOLEAN AdvanceOnly;};ULONG ClusterCount;HANDLE DeleteHandle;};} SetFile;//// NtQueryEaFile 参数//struct{ULONG Length;PVOID EaList;ULONG EaListLength;ULONG POINTER_ALIGNMENT EaIndex;} QueryEa;//// NtSetEaFIle 参数//struct{ULONG Length;} SetEa;//// NtQueryVolumeInformationFile 参数//struct{ULONG Length;FS_INORTMATION_CLASS POINTER_ALIGNMENT FsInformationClass;} QueryVoume;// // NtSetVolumeInformationFile  参数//struct {ULONG Length;FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass;} setValue;//// NtFsControlFile  参数//struct{ULONG OutputBufferLength;ULONG POINTER_ALIGNMENT InputBufferLength;ULONG POINTER_ALIGNMENT  FsControlCode;PVOID Type3InputBuffer;} FileSystemControl;//// NtLockFile/NtUnlockFile 参数//struct{PLARGE_INTEGER Length;ULONG POINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} LockControl;//// NtDeviceIoControlFile 参数//struct{ULONG OutputBufferLength;ULONG POINTER_ALIGNMENT InputBufferLength;ULONG POINTER_ALIGNMENT IoControlCode;PVOID Type3InputBuffer;} DeviceIoControl;//// NtQuerySecurityObject 参数//struct{SECURITY_INFORMATION SecurityInformation;ULONG POINTER_ALIGNMENT Length;} QuerySecurity;//// NtSetScurityObject 参数//struct{SECURITY_INFORMATION SecurityInformation;PSECURITY_DESCRIPTOR SecurityDescriptor;} SetSecurity;//// MountVoluem  参数//struct{PVPB Vpb;PDEVICE_OBJECT DeviceObject;} MountVolume;//// VerifyVolume 参数//struct{PVPB Vpb;PDEVICE_OBJECT DeviceObject;} VerityVolume;//// Scsi 内部 device control//struct{struct _SCSI_REQUEST_BLOCK *Srb;} Scsi;//// NtQueryQuotaInformationFile  参数//struct{ULONG Length;PSID StartSid;PFILE_GET_QUOTA_INFORMATION SidList;ULONG SidListLength;} QueryQuota;//// NtSetQuotaInformationFile  参数//struct{ULONG Length;} SetQuota;//// IRP_MN_QUERY_DEVICE_RELATIONS 参数//struct{DEVICE_RELATION_TYPE Type;} QueryDevceRelations;//// IRP_MN_QUERY_INTERFACE 参数//struct{CONST GUID *InterfaceType;USHORT Size;USHORT Version;PINTERFACE Interface;PVOID InterfacespecificData;} QueryInterface;//// IRP_MN_QUERY_CAPABILITIES 参数//struct{PDEVICE_CAPABILITIES Capabilities;} DeviceCapabilities;//// IRP_MN_FILTER_RESOURCE_REQUIREMENTS 参数//struct{PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList;} FilterResourceRequirements;//// IRP_MN_READ_CONFIG 和 IRP_MN_WRITE_CONFIG 参数//struct{ULONG WhichSpace;PVOID Buffer;ULONG Offset;ULONG POINTER_ALIGNMENT Length;} ReadWriteConfig;//// IRP_MN_SET_LOCK 参数//struct{BOOLEAN Lock;} SetLock;//// IRP_MN_QUERY_ID 参数//struct{BUS_QUERY_ID_TYPE IdType;} QueryId;//// IRP_MN_QUERY_DEVICE_TEXT 参数//struct{DEVICE_TEXT_TYPE DeviceTextType;LCID POINTER_ALIGNMENT LocaleId;}  QueryDeviceText;//// IRP_MN_DEVICE_USAGE_NOTIFICATION 参数//struct{BOOLEAN InPath;BOOLEAN Reserved[3];DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type;} UsageNotification;//// IRP_MN_WAIT_WAKE 参数//struct{SYSTEM_POWER_STATE PowerState;} WaitWake;//// IRP_MN_POWER_SEQUENCE 参数//struct{PPOWER_SEQUENCE PowerSequence;} PowerSquence;//// IRP_MN_SET_POWER 和 IRP_MN_QUERY_POWER 参数//struct{ULONG SystemCotext;POWER_STATE_TYPE POINTER_ALIGNMENT Type;POWER_STATE POINTER_ALIGNMENT State;POWER_ACTION POINTER_ALIGNMENT ShutdownType;} Power;//// StartDevice  参数//struct{PCM_RESOURCE_LIST AllocatedResources;PCM_RESOURCE_LIST AllocatedResourcesTranslated;} StartDevice;//// WMI Irps 参数//struct{ULONG_PTR ProviderId;PVOID DataPath;ULONG BufferSize;PVOID Buffer;} WMI;//// 其它 device 提供的参数//struct{PVOID Argument1PVOID Argument2;PVOID Argument3;PVOID Argument4;} Others;} Parameters;Parameters 为每个类型的 request 提供参数,例如:Create(IRP_MJ_CREATE 请求),Read(IRP_MJ_READ 请求),StartDevice(IRP_MJ_PNP 的子类 IRP_MN_START_DEVICE)MajorFunction 和 MinorFunction 分别的 IRP 的主功能号和子功能号。DeviceObject 是 stack entry 相应的 device object,IoCallDriver() 将填写此域。FileObject 指向 kernel file object。driver 经常使用这个 FileObject 指向关联的 IRP 在一个 request 队列里。CompletionRoutine 是提供一个 I/O completion routine,不要直接设置这个域,而是使用 IoSetCompletionRoutine() 来设置。Context 是任意的值,传递给 completion routine 作为参数。不要直接设置这个域,而是使用 IoSetCompletionRoutine() 来设置。5.2 IRP 处理的标准模式下面几个阶段:I/O manager ---> Dispatch routine ---> StartIo routine ---> ISR ---> DPC routine ---> I/O manager5.2.1 建立一个 IRPIRP 的生存期从调用 I/O manager function 建立 IRP 开始,你可以使用下面 4 个 function 来建立一个新的 IRP:(1) IoBuildAsynchronousFsdRequest(): 建立一个 IRP 不希望等待。这个函数只适合用于某类的 IRP(2) IoBuildSynchronousFsdRequest(): 建立一个 IRP 需要等待完成。(3) IoBuildDeviceIoControlRequest(): 建立一个同步的 IRP_MJ_DEVICE_CONTROL 或者 IRP_MJ_INTERNAL_DEVICE_CONTROL(4) IoAllcateIrp(): 建立一个 asynchronous 的 IRP>>> 建立 synchronous IRP使用 IoBuildSynchronousFsdRequest() 或 IoBuildDeviceIoControlRequest() 来建立同步的 IRP,同步 IRP 是属于创建者线程。由它有一个物主,由此有下面的一系列结果:(1) 假如物主线程终止,I/O manager 自动取消属于该线程的同步 IRP 的 pending(2) 由于创建线程拥有这个同步 IRP 的缘故,你不能在任意线程 context 里创建同步 IRP,当物主线程终止后不能请求 I/O manager 取消 IRP。(3) 调用 IoCompleteRequest(),I/O manager 自动清同步 IRP,并且置你必须提供的 event 置 signaled 状态。(4) 在 I/O manager 置 event object signaled 状态后,event object 仍存在时你必须小心地处理 event object。必须在 PASSIVE_LEVEL 里调用这两个函数,特别是不能在 APC_LEVEL 级别上。因为:在获得 fast mutex 后进入到 APC_LEVEL,然后 I/O manager不能提交 special APC routine 去处理所有 complete 处理。PIRP Irp = IoBuildSycnhronousFsdRequest(...);ExAcqurireFastMutex(...);NTSTATUS status = IoCallDriver(...);if (status == STATUS_PENDING){        KeWaitForSingleObject(...);             // 错误:不要这样做}ExReleaseFastMutex(...);在上面的代码里,使用 KeWaitForSingleObject() 等待会进入死锁:当完成执行 IoCompleteRequest(),这个 APC routine 运行将设置 event,因为已经在 APC_LEVEL 级别上,APC routine 不能运行去设置 event signled。假如你需要发送一个 synchronous IRP 到其它 driver,考虑下面的选择:(1) 使用定期的 kernel mutex 来代替 fast mutex,kernel mutex 将返回到 PASSIVE_LEVEL 级别上,并不会抑制 special APC 执行。(2) 使用 KeEnterCriticalRegion() 来抑制所有除了 special APC 外,然后使用 ExAcquireFastMutexUnsafe() 来获得 mutex。(3) 使用 asynchronous IRP(代替 synchronous IRP),完成后 signaled event。这两个函数所建立的 IRP 为下表所示:support function                                IRP 类型------------------------------------------------------------------------------------IoBuildSynchronousFsdRequest()                  IRP_MJ_READ                                                IRP_MJ_WRITE                                                IRP_MJ_FLUSH_BUFFERS                                                IRP_MJ_SHUTDOWN                                                IRP_MJ_PNP                                                IRP_MJ_POWER(仅用于 IRP_MN_POWER_SEQUENCE)-------------------------------------------------------------------------------------IoBuildDeviceControlRequest()                   IRP_MJ_DEVICE_CONTROL                                                IRP_MJ_INTERNAL_DEVICE_CONTROL>>> 建立异步 IRPIoBuildAsynchronousFsdRequest() 和 IoAllocateIrp() 这两上函数建立异步的 IRP,这些可以建立 IRP 如下表所示:support function                                IRP 类型--------------------------------------------------------------------------------------IoBuildAsynchronousFsdRequest()                 IRP_MJ_READ                                                IRP_MJ_WRITE                                                IRP_MJ_FLUSH_BUFFERS                                                IRP_MJ_SHUTDOWN                                                IRP_MJ_PNP                                                IRP_MJ_POWER(仅用于 IRP_MN_POWER_SEQUENCE)---------------------------------------------------------------------------------------IoAllocateIrp()                                 任何(但必须初始化 Marjor function 表)异步 IRP 不属于创建者线程,当 IRP 完成后,I/O manager 不调用 APC 以及不清理 IRP。考虑下面的问题:(1) 当线程终止后,I/O manager 不会取消任何异步 IRP 的 pending(2) 可以在任意线程 context 里创建(3) 由于 IRP 完成后 I/O manager 不清理 IRP,你必须提供一个complete routine 去释放 buffer 以及调用 IoFreeIrp() 释放 IRP 所使用的内存。(4) 当长时期没发生操作时,你可能需要提供一个 cancel routine。(5) 由于不需要等待异步 IRP 的完成,你可以创建 IRP 在 IRQL <= DISPATCH_LEVEL 级别上,当获得 fast mutex 发送异步 IRP 是可以的。5.2.2 dispatch routine当建立一个 IRP 后,可以使用 IoGetNextIrpStackLocation() 来获得 first stack location,然后需要对 stack location 进行初始化。如果是使用 IoAllcateIrp() 建立的需要填写相关的 MajorFunction 表。PEDEVICE_OBJECT DeviceObject;PIO_STACK_LOCATION stack = IoGetNextIrpStackLoction(Irp);stack->MajorFunction = IRP_MJ_Xxx;// ... stack 初始化代码NTSTATUS status = IoCallDriver(DeviceObject, Irp);IoGetNextIrpStackLocation() 是一个宏用来获得当前的 stack location,它的定义如下:#define IoGetNextIrpStackLocation(Irp)          ((Irp)->Tail.Overlay.CurrentStackLocation - 1)>>> What IoCallDriver DoesIoCallDriver() 看起来像下面:NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject,                PIRP Irp){        IoSetNextIrpStackLocation(Irp);        PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);        Stack->DeviceObject = DeviceObject;        ULONG fcn = Stack->MajorFunction;        PDRVIER_OBJECT Driver = DeviceObject->DriverObject;        return (*Driver->MajorFunction[fcn]))(DeviceObject, Irp);}IoCallDriver() 简单地调用 stack 指针对应的 driver 的 dispatch routine。>>> location device objects除了使用 IoAttachDeviceToDeviceStack() 外,可以使用另外的两个方法:IoGetDeviceObjectPointer() 和 IoGetAttachedDeviceReference()NTSTATUS IoGetDeviceObjectPointer(    IN PUNICODE_STRING  ObjectName,    IN ACCESS_MASK  DesiredAccess,    OUT PFILE_OBJECT  *FileObject,    OUT PDEVICE_OBJECT  *DeviceObject    );假如你知道 device object 的名字,那么你可以使用这个函数 IoGetDeviceObjectPointer() 得到 device object,像下面的用法:PUNICODE_STRING devname;ASSESS_MASK access;PDEVICE_OBJECT DeviceObject;PFILE_OBJECT FileObject;NTSTATUS status;ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);status = IoGetDeviceObjectPointer(devname, access, &FileObject, &DeviceObject);这个函数返回两个指针:一个指向 FILE_OBJECT, 一个指向 DEVICE_OBJECT。PIRP Irp = IoXxx(...);PIO_STACK_LOCATION Stack = IoGetNextIrpStackLocation(Irp);ObReferenceObject(FileObject);Stack->FileObject = FileObject;IoCallDriver(DeviceObject, Irp);ObDereferenceObject(FileObject);------------------------------------IoGetDeviceObjectPointer() 执行一些步骤来定位返回的两个指针:(1) 使用 ZwOpenFile() 打开一个命名的 device object,它将引发 object manager 建立一个 file object 和发送 IRP_MJ_CREATE 到目标设备,ZwOpenFile() 返回 file handle(2) 调用 ObReferenceObjectByHandle() 得到代表 file handle 的 FILE_OBJECT 结构地址,这个地址以 FileObject 返回。(3) 调用 IoGetRelatedDeviceObject() 得到被 FileObject 引用的 DEVICE_OBJECT 结构地址,这个地址以 DeviceObject 返回。(4) 调用 ZwClose() 关闭 Handle 5.2.3 Dispatch routine 职责一个 Dispatch routine 的原型,看起来像下面:NTSTATUS DispatchXxx(PDEVICE_OBJECT fdo, PRIP Irp){        PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;                ...        return STATUS_Xxx;}1.你通常需要访问当前 stack location 去检测参数或都检查 minor 功能号2.你通常也需要访问你建立的 device extension 结构(在 AddDevice() 时候初始化的)。3.最后要返回到 IoCallDriver() 调用上。传送一个 NTSTATUS 值。>>> IRP 的完成完成一个 IRP 必须填允 IRP 的 IoStatus 域内的 Status 与 Information 值,然后调用 IoCompleteRequest() 函数。这个 Status 值是在 NTSTATUS.h 文件里定义的 status code 之一。而 Information 值依赖于 IRP 的类型而定,大多时候当 IRP 失败后 Information 设置为 0当 IRP 引发数据的传送操作,通常设置 Information 值为传送的字节数。
原创粉丝点击