windows内核开发学习笔记十五:IRP结构

windows内核开发学习笔记十五:IRP结构

  IRP(I/O Request Package)windows内核中,有一种系统组件——IRP,即输入输出请求包。当上层应用程序需要访问底层输入输出设备时,发出I/O请求,系统会把这些请求转化为IRP数据,不同的IRP会启动I/O设备驱动中对应的派遣函数。

一、IRP类型

由于IRP是响应上层应用程序的。可想而知,IRP类型是与上层对底层设备的访问类型相对应。文件相关的I/O函数如:CreateFile/ReadFile/WriteFile/CloseHandle等,操作系统就会将其转为IRP_MJ_CREATE/IRP_MJ_READ/IRP_MJ_WRITE/IRP_MJ_CLOSEIRP类型,这些IRP再被传送到驱动程序的派遣函数中。

IRP列表如下:

名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
RP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或者丢弃输入缓冲区 FlushFileBuffers
    FlushConsoleInputBuffer
    PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown

IRP对应的派遣函数处理过程多数的IRP都来自于Win32 API函数,如CreateFileReadFileWriteFile函数等等。

二、IRP数据结构

typedef struct _IRP {
    PMDL              MdlAddress;
    ULONG             Flags;
    union {
        struct _IRP*   MasterIrp;
        PVOID          SystemBuffer;
    } AssociatedIrp;
    IO_STATUS_BLOCK   IoStatus;
    KPROCESSOR_MODE   RequestorMode;
    BOOLEAN           PendingReturned;
    BOOLEAN           Cancel;
    KIRQL             CancelIrql;
    PDRIVER_CANCEL    CancelRoutine;
    PVOID             UserBuffer;
    union {
        struct {
            union {
                KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
                struct {
                    PVOID    DriverContext[4];
                };
            };
            PETHREAD     Thread;
            LIST_ENTRY   ListEntry;
        } Overlay;
    } Tail;
} IRP, *PIRP;

kd> dt nt!_IRP
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 _MDL
   +0x008 Flags            : Uint4B
   +0x00c AssociatedIrp    :
   +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          :
   +0x038 CancelRoutine    : Ptr32     void 
   +0x03c UserBuffer       : Ptr32 Void
   +0x040 Tail             :
windows内核开发学习笔记十五:IRP结构_第1张图片

除去一些简单的成员,例如Type,Size等,我们来看一下各个成员的具体意义:

  • MdlAddress : 是一个MDL的指针,当内核层和用户层采用共享内存的结构传递数据的时候,这个MDL就代表共享的内存信息(共享物理内存,通过MDL映射)。这个成员生效的标记为:DO_DIRECT_IO, METHOD_IN_DIRECT 或者METHOD_OUT_DIRECT。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。

  • AssociatedIrp : 这个成员是个联合体,其中存在一个SystemBuffer程序;当内核层使用用户层的数据的时候是通过拷贝数据的方式来实现的话,那么拷贝后的数据就放在了AssociatedIrp.SystemBuffer中了。这个成员生效的标记是DO_BUFFERED_IO或者METHOD_BUFFERED。  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() 用作输入 data I/O manager 复制 user-mode data buffer 到 kernel-mode data buffer 里。

          (3) 在读操作里,I/O manager 复制 kernel-mode data buffer 到 user-mode data buffer 里。

  • IoStatus : 返回的状态信息。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,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。UserMode 或者 KernelMode 这两个值之一。
  • PendingReturned : Pending 状态,如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。
  • StackCount : 设备栈的数目。
  • CurrentLocation : 当前处于哪个设备栈的索引。
  • Cancel : IRP是否被取消,如果为TRUE,则表明IoCancelIrp已被调用(该函数用于取消这个请求)。如果为FALSE,则表明没有调用IoCancelIrp函数。
  • CancelIrql(KIRQL) : 是一个IRQL值,表明那个专用的取消自旋锁是在这个IRQL上获取的.
  • CancelRoutine(PDRIVER_CANCEL) : 是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接修改该域(因为可以原子修改),是 driver 中 IRP cancel routine 的地址,需要使用 IoSetCancelRoutine() 设置,避免直接对它进行设置。
  • UserBuffer(PVOID) : 用户层参数的直接地址,设置标记METHOD_NEITHER时候有效,UserBuffer 是保存 user-mode 的 data buffer,与 kernel-mode data buffer 进行数据交换。
  • Tail.Overlay 是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。

å¨è¿éæå¥å¾çæè¿°

在这个图中,以水平方向从左到右是这个联合的三个可选成员,在垂直方向是每个结构的成员描述:

  • Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY)和Tail.Overlay.DriverContext(PVOID[4])是Tail.Overlayare内一个未命名联合的两个可选成员(只能出现一个)。I/O管理器把DeviceQueueEntry作为设备标准请求队列中的连接域。当IRP还没有进入某个队列时,如果你拥有这个IRP你可以使用这个域,你可以任意使用DriverContext中的四个指针;Tail.Overlay.DeviceQueueEntry主要用在StartIo相关例程上面。
  • Tail.Overlay.ListEntry(LIST_ENTRY)仅能作为你自己实现的私有队列的连接域;这个成员比较重要,因为如果需要自己实现IRP异步执行的队列,那么就需要使用这个成员将IRP挂入到自己的队列中。

 

三、IRP对应的派遣函数处理过程

多数的IRP都来自于Win32 API函数,如CreateFileReadFileWriteFile函数等等。

一种简单的IRP派遣函数的实现就是:将该IRP中的状态置为成功(pIRP->IoStatus =STATUS_SUCCESS ),然后结束该IRP请求(调用I噢CompleteRequest函数),并返回成功状态。

NTSTATUS status = STATUS_SUCCESS;  
// 完成IRP  
pIrp->IoStatus.Status = status;  
pIrp->IoStatus.Information = 0;  // bytes xfered  
IoCompleteRequest( pIrp, IO_NO_INCREMENT );  

KdPrint(("Leave HelloDDKDispatchRoutin\n"));  

return status;  
VOID 
  IoCompleteRequest(
    IN PIRP  Irp,
    IN CCHAR  PriorityBoost
    );

ReadFile为例,处理过程如下:。

  • ReadFile调用ntdll中的N他ReadFile。其中ReadFile函数是Win32 API,而NtReadFile函数是Native API
  • ntdll中的N他ReadFile进入内核模式,并调用系统服务中的N他ReadFile函数。
  • 系统服务函数N他ReadFile创建IRP_MJ_WRITE类型的IRP,然后将这个IRP函数发送到对应驱动程序的派遣函数中。
  • 在对应的派遣函数中一般会通过IoCompleteRequest函数将IRP请求结束。

你可能感兴趣的:(系统内核,操作系统,windows内核,驱动开发,C/C++,操作系统,Windows内核,系统内核)